API v1

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.

Base URLhttps://wa.naogaonit.com/api

All 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:

ParameterRequiredDescription
x-api-keyone ofHTTP header (recommended for server-to-server calls)
Authorizationone ofHeader Authorization: Bearer YOUR_KEY
api_keyone ofQuery string or form field, e.g. ?api_key=YOUR_KEY (simplest for GET)
Keep keys on your server. Never put them in browser JavaScript or mobile apps – call the API from your backend.

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

ParameterRequiredDescription
torequiredRecipient phone number (aliases: number, phone)
messagerequiredText to send (alias: text). Supports WhatsApp formatting: *bold*, _italic_, ~strike~
deviceoptionalWhich of your WhatsApp numbers sends it (a device_id from Dashboard → Devices). Default: your first connected device
waitoptionaltrue = 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:

ParameterRequiredDescription
fileone ofUpload the file with multipart/form-data (easiest from PHP with CURLFile)
urlone ofA public https:// link – the server downloads it (GET works too)
base64one ofThe file as base64 or a data: URL (JSON body)
torequiredRecipient
captionoptionalText under the image / video / document (or use message)
typeoptionalForce image, video, audio or document (auto-detected otherwise)
filenameoptionalFile 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"}]}'
Only message people who expect it. Mass messaging strangers gets WhatsApp numbers banned.

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.

ParameterRequiredDescription
torequiredPhone number to verify
appoptionalName shown in the message (default: WA Gateway)
purposeoptionalFree label for your logs, e.g. register, login, reset
lengthoptionalDigits, 4–10 (default 6)
expiryoptionalSeconds the code is valid, 30–3600 (default 300)
templateoptionalCustom message. Placeholders: {code} (required), {app}, {minutes}, {purpose}
codeoptionalUse 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

ParameterRequiredDescription
otp_idone ofThe otp_id from the send call (recommended)
toone ofOr the phone number – checks the latest pending code for that number
coderequiredThe 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:

  1. User enters a phone number → your server calls /api/otp/send and stores otp_id in the session.
  2. User types the code from WhatsApp → your server calls /api/otp/verify.
  3. If valid is true → 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_4Fq0aZ9xYw1b

Check 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

ParameterRequiredDescription
grouprequiredGroup id (…@g.us) – your number must be an admin
numbersrequiredArray of phone numbers (or a comma-separated string), max 500 per job
deviceoptionalDevice 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.

Only add people who agreed to join. Adding strangers to groups gets numbers reported and banned.

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:

ParameterRequiredDescription
messageoptionalIncoming message: message_id, device_id, from, name, type, text, is_group, timestamp
message.statusoptionalDelivery receipt: message_id, to, status (sent / delivered / read / failed)
connectionoptionalDevice 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

HTTPcodeMeaning
400missing_to, invalid_to, missing_message, invalid_url, …A parameter is missing or invalid – see error
401no_api_key, invalid_api_keyAPI key missing or wrong
402quota_exceededYour plan limit is reached (messages per day/month, OTPs, recipients…). Upgrade or wait for the next period
403feature_not_in_plan, key_disabled, no_permission, account_suspended, not_group_adminNot allowed for this key, plan or account
404not_on_whatsapp, device_not_foundThe number has no WhatsApp account, or the device id is wrong
413file_too_largeFile is larger than 16 MB
429rate_limited, otp_cooldownToo many requests – see the Retry-After header
503not_connected, no_deviceNo 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.