/api/v1/healthNo keyHealth
No key — is the service up.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/health
{
"success": true,
"service": "sendto-cloudapi-mini",
"now": "2026-09-26T10:00:00.000Z"
}A simple REST API with one key and signed events delivered in real time — start in a sandbox whose messages reach no one.
curl -X POST https://whatsapp.nawa-tech.com/api/v1/messages/text \
-H "x-api-key: sk_..." \
-H "Content-Type: application/json" \
-d '{"clientMessageId":"order-10045","to":"9665XXXXXXXX","templateName":"order_ready","templateLang":"ar","templateParams":["Ahmed","#10045"]}'const r = await fetch('https://whatsapp.nawa-tech.com/api/v1/messages/text', {
method: 'POST',
headers: { 'x-api-key': process.env.SENDTO_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ clientMessageId: 'order-10045', to: '9665XXXXXXXX',
templateName: 'order_ready', templateLang: 'ar', templateParams: ['Ahmed', '#10045'] }),
});
const out = await r.json(); // { success, environment, data: { status, messageId, ... } }
Per-minute request limit per account (429 with Retry-After) · batches up to 1000 text and 50 media messages · media up to 5MB · requests up to 20MB.
/api/v1/healthNo keyNo key — is the service up.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/health
{
"success": true,
"service": "sendto-cloudapi-mini",
"now": "2026-09-26T10:00:00.000Z"
}/api/v1/readinessx-api-keyWhether the invitation template is approved and your numbers are ready.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/readiness \ -H "x-api-key: sk_..."
/api/v1/limitsx-api-keyYour numbers' capacity today and the reset time.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/limits \ -H "x-api-key: sk_..."
/api/v1/sender/healthx-api-keyoverall, readyForSending, aggregateCapacity, qualityRating and your account caps (fromApplication).
curl -X GET https://whatsapp.nawa-tech.com/api/v1/sender/health \ -H "x-api-key: sk_..."
/api/v1/templatesx-api-keyYour templates: status, category, variable count, header, buttons, usable.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/templates \ -H "x-api-key: sk_..."
/api/v1/messages/textx-api-key200 with the result · 202 when scheduled or deferred by a limit (reason sent as message.deferred) · a repeated clientMessageId returns the original result (duplicate: true).
| Field | Type | Required | Description |
|---|---|---|---|
| clientMessageId | string | yes | Your message ID (8–128 chars) — used for matching and 48h de-duplication |
| to | string | yes | Recipient number in international digits (8–20) |
| templateName | string | Approved template — required outside the 24h conversation window | |
| templateLang | string | Template language (ar, en …) | |
| templateParams | string[] | Template variables in order | |
| message | string | Free text — only inside the conversation window | |
| kind | "notification" | "invitation" | Default notification · invitations need eventId or expiresAt | |
| eventId | string | A registered event (POST /events) | |
| expiresAt | ISO-8601 | Invitation expiry | |
| scheduledFor | ISO-8601 | Not sent before this time (up to 1 year) | |
| deadline | ISO-8601 | After this it expires unsent and is refunded | |
| mediaBase64 / mediaId | string | jpeg/png image or PDF up to 5MB (or a previous upload ID) with type: image|document | |
| sentFrom · fromPolicy | string · "prefer"|"strict"|"auto" | Preferred sending number and its policy | |
| meta | object ≤ 4KB | Echoed back verbatim in every event | |
| buttonUrl | https URL | Your link for the template button (wallet card, booking page) — we store it, the guest opens our link which redirects to it, and we count opens · only for templates whose button is on our domain | |
| buttonPayloads | string[] (1–10 · كلٌّ 1–128) | Quick-reply button payloads in template order — echoed back verbatim in message.received (raw accounts; default: clientMessageId) |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/messages/text \
-H "x-api-key: sk_..." \
-H "Content-Type: application/json" \
-d '{"clientMessageId":"order-10045","to":"9665XXXXXXXX","templateName":"order_ready","templateLang":"ar","templateParams":["Ahmed","#10045"]}'{
"success": true,
"environment": "live",
"data": {
"clientMessageId": "order-10045",
"messageId": "wamid.HBgM…",
"status": "sent",
"sentFrom": "9665XXXXXXXX",
"fallbackUsed": false,
"sentAt": "2026-09-26T10:00:01.000Z",
"duplicate": false
}
}/api/v1/messages/otpx-api-keySent with a Meta authentication template (fixed Meta text) and a copy-code button — top priority · not blocked by marketing opt-out · if not sent within its TTL (10 minutes by default) it expires instead of arriving late · the code is erased from our records afterwards.
| Field | Type | Required | Description |
|---|---|---|---|
| to | string | yes | International digits |
| code | string (1–15) | yes | The code — no spaces or links (Meta limit) |
| clientMessageId · ttlSeconds · meta | string · integer (60–3600) · object | Optional — ttlSeconds defaults to 600 |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/messages/otp \
-H "x-api-key: sk_..." \
-H "Content-Type: application/json" \
-d '{"to":"9665XXXXXXXX","code":"482913"}'{
"success": true,
"environment": "live",
"data": {
"clientMessageId": "otp_…",
"messageId": "wamid.HBgM…",
"status": "sent",
"sentFrom": "9665XXXXXXXX",
"duplicate": false
}
}/api/v1/messages/bulkx-api-keyUp to 1000 text and 50 media messages · 202 with five lists: accepted, rejected (per-message code), duplicates (48h), held, atRisk — plus a conservative estimatedCompletionAt. Message fields on the envelope return FIELD_NOT_ALLOWED_IN_BULK.
| Field | Type | Required | Description |
|---|---|---|---|
| messages | Message[] | yes | Each item has the fields of "Send one message" |
| batchName | string | Batch name | |
| eventId | string | Batch event | |
| fromPolicy | string | Policy for the whole batch |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/messages/bulk \
-H "x-api-key: sk_..." \
-H "Content-Type: application/json" \
-d '{"batchName":"reminders-sep","messages":[{"clientMessageId":"rem-0001","to":"9665XXXXXXXX","templateName":"order_ready","templateLang":"ar","templateParams":["Sara","#1"]}]}'{
"success": true,
"environment": "live",
"data": {
"batchId": "b1f…",
"batchName": "reminders-sep",
"eventId": null,
"total": 1,
"accepted": [
{
"clientMessageId": "rem-0001"
}
],
"held": [],
"atRisk": [],
"rejected": [],
"duplicates": [],
"estimatedCompletionAt": "2026-09-26T10:05:00.000Z"
}
}/api/v1/messages/richx-api-keyHeader image + text + up to 3 reply buttons (or one URL button). Inside the conversation window, or with an approved template outside it. Call buttons and copyCode are not available (400 unsupported).
| Field | Type | Required | Description |
|---|---|---|---|
| clientMessageId | string | yes | Your message ID (8–128 chars) — used for matching and 48h de-duplication |
| to | string | yes | Recipient number in international digits (8–20) |
| message | string ≤ 1024 | yes | Body text |
| buttons | {id,text}[] | [{text,url}] | yes | Button text ≤ 20 chars |
| headerImage | base64 | data: | Header image | |
| title · footer | string ≤ 60 | Title and footer |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/messages/rich \
-H "x-api-key: sk_..." \
-H "Content-Type: application/json" \
-d '{"clientMessageId":"card-2201","to":"9665XXXXXXXX","message":"تذكرتك جاهزة","buttons":[{"id":"confirm","text":"تأكيد"},{"id":"decline","text":"اعتذار"}]}'/api/v1/messages/uploadx-api-keyUpload once and reuse the mediaId across many messages (content is de-duplicated).
| Field | Type | Required | Description |
|---|---|---|---|
| mediaBase64 | string | yes | jpeg/png/pdf up to 5MB |
| mimetype · filename | string | Optional |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/messages/upload \ -H "x-api-key: sk_..."
{
"success": true,
"environment": "live",
"data": {
"mediaId": "ast_…",
"mimetype": "image/png",
"bytes": 48211
}
}/api/v1/feedback/sendx-api-keyA feedback form you built in the portal, approved by us and published at Meta — the guest gets a button that opens it inside WhatsApp. **Once per number** (ALREADY_SENT) · **only the first answer counts** and reaches you as feedback.submitted with the question texts · sandbox accounts send a ready form in simulation.
| Field | Type | Required | Description |
|---|---|---|---|
| formId | string | yes | Form id (any version — the latest published one is sent) |
| to | string | yes | Guest number in international digits |
| clientMessageId | string | Your id — repeating it returns the original result (duplicate:true) |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/feedback/send \
-H "x-api-key: sk_..." \
-H "Content-Type: application/json" \
-d '{"formId":"6f1c…","to":"9665XXXXXXXX","clientMessageId":"visit-10045-rating"}'{
"success": true,
"environment": "live",
"data": {
"sendId": "a3b…",
"jobId": "9e2…",
"formId": "6f1c…",
"version": 1,
"to": "9665XXXXXXXX",
"status": "queued",
"duplicate": false
}
}/api/v1/messages/:cidx-api-keytimeline, invitation, attempts, error, meta — by your clientMessageId.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/messages/{cid} \
-H "x-api-key: sk_..."/api/v1/linksx-api-keyA link on our domain that redirects to yours and counts opens — https and a public host.
| Field | Type | Required | Description |
|---|---|---|---|
| url | https URL | yes | Destination |
| eventId · clientMessageId · expiresAt | string · ISO-8601 | Optional |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/links \ -H "x-api-key: sk_..."
{
"success": true,
"environment": "live",
"data": {
"code": "Ab3kP9xQr2",
"shortUrl": "https://whatsapp.nawa-tech.com/l/Ab3kP9xQr2",
"url": "https://…",
"clicks": 0,
"active": true
}
}/api/v1/links/:codex-api-keyclicks, firstClickAt, lastClickAt, active.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/links/{code} \
-H "x-api-key: sk_..."/api/v1/batches/:batchId/cancelx-api-keyCancels whatever has not been sent (refunded) — messages mid-send are counted in inFlight.
curl -X POST https://whatsapp.nawa-tech.com/api/v1/batches/{batchId}/cancel \
-H "x-api-key: sk_..."/api/v1/eventsx-api-keyInvitation deadlines, reply texts and the callback for its events. 201 new · 200 duplicate · 409 EVENT_LOCKED when changing the date after an invitation went out (unless confirmLockedChange: true).
| Field | Type | Required | Description |
|---|---|---|---|
| eventId | string (4–64) | yes | Your event ID |
| eventDate | ISO-8601 | yes | Event date |
| callbackUrl | https URL | yes | Webhook URL for this event |
| texts.invalidReply · texts.nudge · texts.expired | string | yes | Texts sent to guests |
| defaults | object | Invitation deadlines and undeliveredTimeoutMinutes | |
| defaults.sentFrom | string | Default sending number for the event (one of yours) — each new guest starts on it, guests already messaged keep their number · null ⇒ automatic · editable after lock |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/events \ -H "x-api-key: sk_..."
/api/v1/events/:eventIdx-api-keyIts settings + sentInvitations.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/events/{eventId} \
-H "x-api-key: sk_..."/api/v1/numbers/checkx-api-keyUp to 500 — true if we delivered to it before · null otherwise (Meta offers no pre-check — never false).
| Field | Type | Required | Description |
|---|---|---|---|
| numbers | string[] | yes | Numbers |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/numbers/check \ -H "x-api-key: sk_..."
/api/v1/optouts/checkx-api-keyWho asked to stop your messages.
| Field | Type | Required | Description |
|---|---|---|---|
| numbers | string[] | yes | Numbers |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/optouts/check \ -H "x-api-key: sk_..."
/api/v1/optouts/:phoneNumberx-api-keyOnly at the number owner's request.
curl -X DELETE https://whatsapp.nawa-tech.com/api/v1/optouts/{phoneNumber} \
-H "x-api-key: sk_..."We POST every event as JSON to your URL (or the event callbackUrl). Any 2xx within 10 seconds is success — otherwise we retry: immediately, then after 10s, 1m, 5m, 30m, 2h (six attempts), then it appears in GET /api/v1/webhooks/failed.
X-Timestamp: 1790000000 X-Signature: sha256=hex(HMAC_SHA256(secret, "<X-Timestamp>.<rawBody>")) X-Webhook-Id: whk_… X-Delivery-Attempt: 1
const crypto = require('crypto');
// rawBody: the exact request body string · ts: X-Timestamp · sig: X-Signature
function verify(rawBody, ts, sig, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(ts + '.' + rawBody).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
return fresh && sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}<?php
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_TIMESTAMP'];
$sig = $_SERVER['HTTP_X_SIGNATURE'];
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $raw, $secret);
$ok = abs(time() - (int)$ts) < 300 && hash_equals($expected, $sig);
| message.status | sent, delivered, read, failed (with code, explanation, action), expired (neverDelivered) |
| message.deferred | Deferred once per reason: send window, number limits, INVOICE_OVERDUE, SENDING_PAUSED … |
| message.received | raw accounts: every reply verbatim + repliedTo attribution (button_payload, quoted_message, inferred last_outbound, none) with clientMessageId and meta, lastOutbound, lastOutboundByKind, windowExpiresAt — interpretation is yours |
| guest.confirmed · guest.declined · guest.replied_invalid · guest.unresolved · guest.replied_late · guest.message | Guest replies to invitations (answer, matchConfidence, slotMatch) |
| guest.opted_out · guest.opted_in | Opt-out and opt-in |
| batch.progress · batch.completed | Batch progress (every 5% or 60s) and completion once |
| sender.health | Number status or quality change at Meta |
| feedback.submitted | A guest rating — first one only: answers with question texts (rating 1–5, single, multi, comment), formId, version, to, clientMessageId, meta |
/api/v1/webhooks/failedx-api-keyEvents whose six attempts were exhausted — with their payload.
| Field | Type | Required | Description |
|---|---|---|---|
| limit · since | number · ISO-8601 | Optional |
curl -X GET https://whatsapp.nawa-tech.com/api/v1/webhooks/failed \ -H "x-api-key: sk_..."
/api/v1/webhooks/failed/:webhookId/retryx-api-keyRe-queued with six new attempts and the same ID.
curl -X POST https://whatsapp.nawa-tech.com/api/v1/webhooks/failed/{webhookId}/retry \
-H "x-api-key: sk_..."/api/v1/media/inbound/:wamidNo keyDelivered ready-made as mediaUrl in guest.* events — signed, so it works without a key (even in <img>) · valid 7 days from receipt (Meta retention) then 410 · we do not store the file: download it within that window.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/media/inbound/{wamid}A unique message is the first one to a number in your subscription; a second one to the same number is a follow-up. Anything not delivered is refunded. An invoice is valid until its due time; after that new messages get 402 INVOICE_OVERDUE and the queue waits — everything resumes by itself once paid.
/api/v1/balancex-api-keyPackage, billing type (PACKAGE, MONTHLY, USAGE), quota (unique/follow-up), pay-as-you-go money, suspended and open invoices — or partner capacity.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/balance \ -H "x-api-key: sk_..."
{
"success": true,
"environment": "live",
"data": {
"billingMode": "ENFORCED",
"suspended": false,
"invoices": [],
"subscription": {
"package": "الأعمال",
"billingType": "PACKAGE",
"state": "ACTIVE",
"unique": {
"quota": 5000,
"used": 1200,
"remaining": 3800
},
"followup": {
"quota": 1000,
"used": 40,
"remaining": 960
}
}
}
}/api/v1/invoicesx-api-keyEach invoice is valid until dueAt; after that sending pauses (INVOICE_OVERDUE) and resumes by itself once paid in the portal.
| Field | Type | Required | Description |
|---|---|---|---|
| status | OPEN|OVERDUE|PAID|VOID | Optional |
curl -X GET https://whatsapp.nawa-tech.com/api/v1/invoices \ -H "x-api-key: sk_..."
{
"success": true,
"environment": "live",
"data": [
{
"number": "INV-2026-000001",
"kind": "USAGE",
"status": "OPEN",
"subtotal": "500.00",
"vatPct": 0,
"vatAmount": "0.00",
"total": "500.00",
"currency": "SAR",
"issuedAt": "2026-09-26T10:00:00.000Z",
"dueAt": "2026-09-27T10:00:00.000Z"
}
]
}/api/v1/invoices/:numberx-api-keyIts lines, amounts and status.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/invoices/{number} \
-H "x-api-key: sk_..."/api/v1/partner/accountx-api-key of a partner accountCapacity (total, used, remaining), balance owed, credit limit, available credit, maximum top-up now, expiry, pending transfers.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/partner/account \ -H "x-api-key: sk_..."
{
"success": true,
"environment": "live",
"data": {
"currency": "SAR",
"ratePerMessage": "0.3700",
"capacity": {
"total": 20000,
"used": 5120,
"remaining": 14880,
"pct": 26
},
"debt": "4000.00",
"creditLimit": "10000.00",
"creditAvailable": "6000.00",
"maxTopupMessages": 16216,
"expiresAt": null,
"expired": false,
"pendingTransfers": []
}
}/api/v1/partner/topupsx-api-key of a partner accountActivated instantly while the balance after it stays within the credit limit — otherwise 402 CREDIT_LIMIT_EXCEEDED with the numbers and the maximum you can request now.
| Field | Type | Required | Description |
|---|---|---|---|
| messages | integer | yes | Number of messages |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/partner/topups \
-H "x-api-key: sk_..." \
-H "Content-Type: application/json" \
-d '{"messages":20000}'/api/v1/partner/transfersx-api-key of a partner accountDoes not change your balance until we confirm receipt — the amount actually received is applied.
| Field | Type | Required | Description |
|---|---|---|---|
| amount | number | yes | Amount |
| reference | string | yes | Transfer reference — unique |
| transferDate · note | date · string | Optional |
curl -X POST https://whatsapp.nawa-tech.com/api/v1/partner/transfers \
-H "x-api-key: sk_..." \
-H "Content-Type: application/json" \
-d '{"amount":5000,"reference":"TRX-88213","transferDate":"2026-09-26"}'/api/v1/partner/ledgerx-api-key of a partner accountEvery entry with the balance after it: TOPUP, TRANSFER_REPORTED, TRANSFER_CONFIRMED, TRANSFER_REJECTED, ADJUSTMENT.
curl -X GET https://whatsapp.nawa-tech.com/api/v1/partner/ledger \ -H "x-api-key: sk_..."
Every error has the shape { success: false, error: { code, message, … } } — the message names the cause.
| Code | HTTP | Description |
|---|---|---|
| UNAUTHORIZED | 401 | x-api-key missing |
| INVALID_API_KEY | 401 | Unknown key |
| APP_INACTIVE | 403 | Account disabled |
| RATE_LIMIT_EXCEEDED | 429 | Per-minute request limit — see Retry-After (seconds) |
| VALIDATION_ERROR | 400 | Missing or invalid field — the message names it |
| INVALID_PHONE | 400 | Number is not 8–20 digits |
| FORM_NOT_FOUND | 404 | No feedback form with this id in your account |
| FORM_NOT_SENDABLE | 409 | The form is not published yet (sandbox: not approved yet) |
| ALREADY_SENT | 409 | This form was already sent to this number — rated once (previousSendAt) |
| DUPLICATE_CLIENT_MESSAGE_ID | 409 | clientMessageId already used for another message in this account |
| RECIPIENT_NOT_ALLOWLISTED | 403 | Recipient not on the allowed list |
| BATCH_TOO_LARGE | 400 | Batch above the limit |
| FIELD_NOT_ALLOWED_IN_BULK | 400 | Message field on the batch envelope |
| INVALID_FROM_POLICY | 400 | fromPolicy: prefer, strict, auto |
| SERVICE_WINDOW_CLOSED | 400 | Free text outside the conversation window — use a template |
| MEDIA_TOO_LARGE | 400 | Media above 5MB |
| INVALID_MEDIA | 400 | Not jpeg/png/pdf |
| MESSAGE_NOT_FOUND | 404 | No message with this ID |
| TEMPLATE_REQUIRED | 400 | templateName is required for invitations on your account — no default template |
| INVALID_OTP | 400 | Code must be 1–15 characters, no spaces or links |
| INVALID_URL | 400 | Invalid URL — https and a public host |
| BUTTON_URL_UNSUPPORTED | 400 | Template button is not on our domain — buttonUrl not accepted |
| INVALID_BUTTON_PAYLOAD | 400 | buttonPayloads: 1–10 strings, each 1–128 chars — never silently truncated |
| LINK_NOT_FOUND | 404 | No link with this code |
| INVALID_SENDER | 400 | Not one of your numbers |
| INVALID_SIGNATURE | 403 | Invalid link signature |
| MEDIA_EXPIRED | 410 | Media expired at Meta (7 days) |
| MEDIA_NOT_FOUND | 404 | No media for this message |
| META_ERROR | 502 | Download from Meta failed — its text is in the message |
| EVENT_NOT_FOUND | 404 | No event with this ID |
| EVENT_LOCKED | 409 | Date change after invitations went out — confirm with confirmLockedChange |
| NOT_FOUND | 404 | Not found |
| NO_ACTIVE_SUBSCRIPTION | 402 | No active subscription |
| SUBSCRIPTION_EXPIRED | 402 | Subscription expired |
| QUOTA_EXHAUSTED | 402 | Unique messages used up |
| FOLLOWUP_QUOTA_EXHAUSTED | 402 | A second message to the same number needs follow-up credit |
| INVOICE_OVERDUE | 402 | An invoice is overdue — sending is paused until paid (includes its number and amount) |
| USAGE_CREDIT_LIMIT | 402 | Pay as you go: balance reached its limit |
| PARTNER_CAPACITY_EXHAUSTED | 402 | Partner capacity used up — request a top-up |
| PARTNER_CAPACITY_EXPIRED | 402 | Partner agreement ended |
| CREDIT_LIMIT_EXCEEDED | 402 | Top-up exceeds the credit limit |
| INVALID_QUANTITY | 400 | Messages from 1 to 10,000,000 |
| REFERENCE_REQUIRED | 400 | Transfer reference required |
| DUPLICATE_REFERENCE | 409 | Reference already reported |
| INVALID_AMOUNT | 400 | Invalid amount |
| NOT_A_PARTNER | 403 | Partner accounts only |
SendTo — a Nawa Tech service