WA Gateway API
Send WhatsApp messages and one-time passwords (OTP) from any website or software. Every endpoint accepts GET query parameters, POST form data or POST JSON – use whatever is easiest for you.
https://wa.naogaonit.com/apiAll responses are JSON with "ok": true on success, or "ok": false plus error and code on failure.
Authentication
Create an account, then create an API key in Dashboard → API Keys. Send it in one of these ways:
| Parameter | Required | Description |
|---|---|---|
x-api-key | one of | HTTP header (recommended for server-to-server calls) |
Authorization | one of | Header Authorization: Bearer YOUR_KEY |
api_key | one of | Query string or form field, e.g. ?api_key=YOUR_KEY (simplest for GET) |
Phone numbers
Use the international format with country code: 8801712345678. Spaces, dashes, + and a leading 00 are removed automatically.
Local numbers starting with 0 get the default country code 880, so 01712345678 works too.
To message a group, use its ID (1203...@g.us, see GET /api/groups).
Send a message
GETPOST/api/send
| Parameter | Required | Description |
|---|---|---|
to | required | Recipient phone number (aliases: number, phone) |
message | required | Text to send (alias: text). Supports WhatsApp formatting: *bold*, _italic_, ~strike~ |
device | optional | Which of your WhatsApp numbers sends it (a device_id from Dashboard → Devices). Default: your first connected device |
wait | optional | true = wait until WhatsApp accepted the message. Default false (returns immediately as queued) |
curl "https://wa.naogaonit.com/api/send?api_key=YOUR_API_KEY&to=8801712345678&message=Hello%20from%20my%20website"curl -X POST https://wa.naogaonit.com/api/send \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to":"8801712345678","message":"Your order #1023 has shipped 🚚"}'<?php
$ch = curl_init('https://wa.naogaonit.com/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['x-api-key: YOUR_API_KEY', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'to' => '8801712345678',
'message' => 'Your order #1023 has shipped 🚚',
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['ok']) {
echo 'Queued: ' . $response['message_id'];
} else {
echo 'Error: ' . $response['error'];
}<?php
$url = 'https://wa.naogaonit.com/api/send?' . http_build_query([
'api_key' => 'YOUR_API_KEY',
'to' => '8801712345678',
'message' => 'Hello from PHP',
]);
$response = json_decode(file_get_contents($url), true);const res = await fetch('https://wa.naogaonit.com/api/send', {
method: 'POST',
headers: { 'x-api-key': process.env.WA_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ to: '8801712345678', message: 'Hello from Node.js' }),
});
const data = await res.json();
console.log(data.ok ? data.message_id : data.error);import requests
r = requests.post('https://wa.naogaonit.com/api/send',
headers={'x-api-key': 'YOUR_API_KEY'},
json={'to': '8801712345678', 'message': 'Hello from Python'})
print(r.json())Response:
{
"ok": true,
"message_id": "msg_4Fq0aZ9xYw1b",
"direction": "out",
"to": "8801712345678",
"type": "text",
"status": "queued",
"source": "api",
"created_at": "2026-01-01T10:00:00.000Z",
"queue_size": 1
}
Messages are sent one by one with a short random pause (anti-spam protection). Use message status or webhooks to track delivery.
Send media / files
POST/api/send
Send images, videos, voice/audio and documents (PDF, Word, Excel…) up to 16 MB. Give the file in one of three ways – the type is detected automatically:
| Parameter | Required | Description |
|---|---|---|
file | one of | Upload the file with multipart/form-data (easiest from PHP with CURLFile) |
url | one of | A public https:// link – the server downloads it (GET works too) |
base64 | one of | The file as base64 or a data: URL (JSON body) |
to | required | Recipient |
caption | optional | Text under the image / video / document (or use message) |
type | optional | Force image, video, audio or document (auto-detected otherwise) |
filename | optional | File name shown for documents, e.g. invoice.pdf |
curl -X POST https://wa.naogaonit.com/api/send \
-H "x-api-key: YOUR_API_KEY" \
-F "to=8801712345678" \
-F "caption=Your invoice" \
-F "file=@invoice-1023.pdf"<?php
$ch = curl_init('https://wa.naogaonit.com/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['x-api-key: YOUR_API_KEY'],
CURLOPT_POSTFIELDS => [
'to' => '8801712345678',
'caption' => 'Your invoice',
'file' => new CURLFile('/path/to/invoice-1023.pdf'),
],
]);
$response = json_decode(curl_exec($ch), true);curl -X POST https://wa.naogaonit.com/api/send \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"to":"8801712345678","url":"https://example.com/product.jpg","caption":"New arrival!"}'curl -X POST https://wa.naogaonit.com/api/send \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"to":"8801712345678","base64":"data:image/png;base64,iVBORw0KGgo...","caption":"Logo"}'Media sending must be included in your plan. Links must be public internet addresses.
Bulk messages
POST/api/send/bulk
Queue many messages in one request (JSON body). The maximum per request depends on your plan. Messages are sent one by one with a random 3–8 second pause.
curl -X POST https://wa.naogaonit.com/api/send/bulk \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"messages":[{"to":"8801712345678","message":"Hi Ali"},{"to":"8801812345678","message":"Hi Sara"}]}'OTP: send a code
GETPOST/api/otp/send
Generates a random code, sends it via WhatsApp and returns an otp_id. The code itself is never returned or stored in plain text.
| Parameter | Required | Description |
|---|---|---|
to | required | Phone number to verify |
app | optional | Name shown in the message (default: WA Gateway) |
purpose | optional | Free label for your logs, e.g. register, login, reset |
length | optional | Digits, 4–10 (default 6) |
expiry | optional | Seconds the code is valid, 30–3600 (default 300) |
template | optional | Custom message. Placeholders: {code} (required), {app}, {minutes}, {purpose} |
code | optional | Use your own code instead of a generated one (then you may also verify it yourself) |
curl "https://wa.naogaonit.com/api/otp/send?api_key=YOUR_API_KEY&to=8801712345678&purpose=login"curl -X POST https://wa.naogaonit.com/api/otp/send \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"to":"8801712345678","purpose":"register","app":"MyShop"}'{
"ok": true,
"otp_id": "otp_Xr8f2LqA0b1c",
"to": "8801712345678",
"status": "pending",
"expires_in": 300,
"expires_at": "2026-01-01T10:05:00.000Z",
"message_id": "msg_4Fq0aZ9xYw1b"
}
Asking again for the same number within the resend cooldown (default 60 s, set in Dashboard → OTP) returns 429 otp_cooldown. A new code makes older codes for that number invalid.
OTP: verify a code
GETPOST/api/otp/verify
| Parameter | Required | Description |
|---|---|---|
otp_id | one of | The otp_id from the send call (recommended) |
to | one of | Or the phone number – checks the latest pending code for that number |
code | required | The code the user typed |
curl "https://wa.naogaonit.com/api/otp/verify?api_key=YOUR_API_KEY&otp_id=otp_Xr8f2LqA0b1c&code=482913"curl -X POST https://wa.naogaonit.com/api/otp/verify \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"otp_id":"otp_Xr8f2LqA0b1c","code":"482913"}'// correct
{ "ok": true, "valid": true,
"reason": "verified",
"message": "Code is correct." }
// wrong
{ "ok": true, "valid": false,
"reason": "wrong_code",
"message": "Wrong code. 4 attempt(s) left.",
"attempts_left": 4 }
Always check valid. Possible reason values: verified, wrong_code, too_many_attempts, expired, already_used, replaced, not_found. A code can be used only once and is blocked after 5 wrong attempts.
OTP: full examples
Typical flow for registration, login with 2-step verification or password reset:
- User enters a phone number → your server calls
/api/otp/sendand storesotp_idin the session. - User types the code from WhatsApp → your server calls
/api/otp/verify. - If
validistrue→ create the account / finish login / allow the password reset.
<?php
session_start();
const WA_URL = 'https://wa.naogaonit.com/api';
const WA_KEY = 'YOUR_API_KEY';
function wa_api(string $path, array $data): array {
$ch = curl_init(WA_URL . $path);
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20,
CURLOPT_HTTPHEADER => ['x-api-key: ' . WA_KEY, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($data),
]);
$res = json_decode(curl_exec($ch) ?: '{}', true);
curl_close($ch);
return $res ?: ['ok' => false, 'error' => 'No response'];
}
// STEP 1 – user submitted their phone number
if (isset($_POST['phone'])) {
$r = wa_api('/otp/send', ['to' => $_POST['phone'], 'purpose' => 'login']);
if ($r['ok']) {
$_SESSION['otp_id'] = $r['otp_id'];
$_SESSION['phone'] = $r['to'];
echo 'We sent a code to your WhatsApp.';
} else {
echo 'Error: ' . htmlspecialchars($r['error']);
}
}
// STEP 2 – user submitted the code
if (isset($_POST['code'])) {
$r = wa_api('/otp/verify', ['otp_id' => $_SESSION['otp_id'] ?? '', 'code' => $_POST['code']]);
if (!empty($r['valid'])) {
$_SESSION['verified_phone'] = $_SESSION['phone'];
unset($_SESSION['otp_id']);
echo 'Verified! ✅'; // create account / log the user in
} else {
echo htmlspecialchars($r['message'] ?? $r['error']);
}
}use Illuminate\Support\Facades\Http;
// config/services.php: 'wa' => ['url' => env('WA_URL'), 'key' => env('WA_KEY')]
$wa = Http::withHeaders(['x-api-key' => config('services.wa.key')])
->baseUrl(config('services.wa.url') . '/api');
// send
$r = $wa->post('/otp/send', ['to' => $request->phone, 'purpose' => 'register'])->json();
session(['otp_id' => $r['otp_id'] ?? null]);
// verify
$r = $wa->post('/otp/verify', ['otp_id' => session('otp_id'), 'code' => $request->code])->json();
if ($r['valid'] ?? false) {
// phone verified
}const WA = { url: 'https://wa.naogaonit.com/api', key: process.env.WA_API_KEY };
const wa = (path, body) => fetch(WA.url + path, {
method: 'POST',
headers: { 'x-api-key': WA.key, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}).then((r) => r.json());
app.post('/login/otp', async (req, res) => {
const r = await wa('/otp/send', { to: req.body.phone, purpose: 'login' });
if (!r.ok) return res.status(400).json({ error: r.error });
req.session.otpId = r.otp_id;
res.json({ sent: true, expiresIn: r.expires_in });
});
app.post('/login/verify', async (req, res) => {
const r = await wa('/otp/verify', { otp_id: req.session.otpId, code: req.body.code });
if (!r.valid) return res.status(401).json({ error: r.message });
req.session.verified = true;
res.json({ ok: true });
});import requests
WA_URL, WA_KEY = 'https://wa.naogaonit.com/api', 'YOUR_API_KEY'
def wa(path, data):
return requests.post(WA_URL + path, json=data, headers={'x-api-key': WA_KEY}, timeout=20).json()
@app.post('/otp/send')
def otp_send():
r = wa('/otp/send', {'to': request.form['phone'], 'purpose': 'register'})
session['otp_id'] = r.get('otp_id')
return {'sent': r['ok'], 'error': r.get('error')}
@app.post('/otp/verify')
def otp_verify():
r = wa('/otp/verify', {'otp_id': session.get('otp_id'), 'code': request.form['code']})
return {'valid': r.get('valid', False), 'message': r.get('message')}Plugins & SDKs
Don’t want to write HTTP calls yourself? Download a ready-made integration – pre-configured for this server:
WordPress & WooCommerceWHMCSPHP classNode.jsPythonPostman collection
Details and install steps: Integrations.
Message status
GET/api/messages/{message_id}
Status goes queued → sent → delivered → read, or failed (with error). List recent messages with GET /api/messages?page=1&limit=25&status=failed.
curl -H "x-api-key: YOUR_API_KEY" https://wa.naogaonit.com/api/messages/msg_4Fq0aZ9xYw1bCheck numbers
GETPOST/api/check
Find out if numbers are on WhatsApp (max 50 per call). numbers = array or comma-separated list.
curl "https://wa.naogaonit.com/api/check?api_key=YOUR_API_KEY&numbers=8801712345678,8801812345678"Groups
Manage the WhatsApp groups of your numbers. Member lists and adding members work for groups where your number is an admin, and need a plan with group tools.
GET/api/groups
All groups of the device, with is_admin and the member count. Use a group id as to in /api/send to message the group.
GET/api/groups/members?group=1203…@g.us
{ "ok": true,
"group": { "id": "120363…@g.us", "name": "VIP Customers", "participants": 5 },
"members": [ { "phone": "8801711000001", "role": "admin", "id": "8801711000001@s.whatsapp.net" }, … ] }
POST/api/groups/add
| Parameter | Required | Description |
|---|---|---|
group | required | Group id (…@g.us) – your number must be an admin |
numbers | required | Array of phone numbers (or a comma-separated string), max 500 per job |
device | optional | Device id – default: first connected device |
curl -X POST https://wa.naogaonit.com/api/groups/add \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"group":"120363000000000001@g.us","numbers":["8801712345678","8801812345678"]}'Members are added in the background, one at a time with the normal sending pause. Check progress with GET /api/groups/job – each number ends as added, exists (already a member) or failed with a reason (e.g. the person’s privacy settings require an invite link). Your plan sets a daily limit; exceeding it returns 402 quota_exceeded.
Devices & status
GET/api/status
Your WhatsApp devices, plan limits and current usage. Use a device_id as the device parameter to choose the sending number.
{
"ok": true,
"devices": [ { "device_id": "dev_Ab12Cd34Ef56", "name": "Shop", "status": "open", "connected": true, "number": "8801700000000", "queue_size": 0 } ],
"plan": { "name": "Starter", "limits": { "messagesPerDay": 1000, ... }, "features": { "media": true, ... } },
"usage": { "today": { "sent": 12, "received": 3, "otp": 4 }, "month": { "sent": 230, ... } }
}
Groups the number is in: GET /api/groups?device=dev_….
Webhooks
Set a webhook URL in Dashboard → Settings (if your plan includes webhooks). Your URL receives a POST with JSON for these events:
| Parameter | Required | Description |
|---|---|---|
message | optional | Incoming message: message_id, device_id, from, name, type, text, is_group, timestamp |
message.status | optional | Delivery receipt: message_id, to, status (sent / delivered / read / failed) |
connection | optional | Device connected / disconnected / logged out |
{
"event": "message",
"timestamp": 1767261600000,
"data": { "message_id": "msg_…", "from": "8801712345678", "name": "Ali", "type": "conversation", "text": "Hi!", "is_group": false }
}
If a webhook secret is set, verify the X-Signature header = hex(HMAC-SHA256(raw body, secret)):
<?php
$raw = file_get_contents('php://input');
$ok = hash_equals(hash_hmac('sha256', $raw, 'YOUR_WEBHOOK_SECRET'), $_SERVER['HTTP_X_SIGNATURE'] ?? '');
if (!$ok) { http_response_code(401); exit; }
$event = json_decode($raw, true);const sig = crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(req.get('x-signature') || ''))) return res.sendStatus(401);Errors & limits
| HTTP | code | Meaning |
|---|---|---|
| 400 | missing_to, invalid_to, missing_message, invalid_url, … | A parameter is missing or invalid – see error |
| 401 | no_api_key, invalid_api_key | API key missing or wrong |
| 402 | quota_exceeded | Your plan limit is reached (messages per day/month, OTPs, recipients…). Upgrade or wait for the next period |
| 403 | feature_not_in_plan, key_disabled, no_permission, account_suspended, not_group_admin | Not allowed for this key, plan or account |
| 404 | not_on_whatsapp, device_not_found | The number has no WhatsApp account, or the device id is wrong |
| 413 | file_too_large | File is larger than 16 MB |
| 429 | rate_limited, otp_cooldown | Too many requests – see the Retry-After header |
| 503 | not_connected, no_device | No linked WhatsApp device is connected right now |
Each API key has a per-minute limit (set per key, capped by your plan). Response headers X-RateLimit-Limit and X-RateLimit-Remaining show your usage. Your current usage and limits: GET /api/status or Dashboard → Billing.