API documentation
Send WhatsApp messages and verify phone numbers from your app through the WhatsApp Cloud API.
Getting started
- 1
Add a device and connect your WhatsApp Business number with Embedded Signup.
- 2
Create a token on the device page, then copy it.
- 3
Call the API below with that token.
You can also read and reply to chats by hand in the Console tab of each device.
Authentication
Send the token in the header. Each token belongs to one device.
Authorization: Bearer <token>
| 401 | Invalid token |
| 403 | Token is not linked to a device |
| 503 | Device is not connected |
/api/status— check the device before sending{"id":8,"device":"main-store","phone":"628…","channel":"cloud","status":"connected","connected":true}
Send messages
order updates, reminders, repliesFor regular messages. You can add a random delay in seconds, prevent duplicates with ref, and check the status at any time.
/api/messages| to | Recipient number, e.g. 628123456789 (a leading 0 becomes 62) |
| text | Message text, up to 4,096 characters |
| delay_min | Optional. Minimum delay before sending (seconds, decimals allowed) |
| delay_max | Optional. Maximum delay. The delay is random between the two; use the same value for an exact delay. Up to 300 seconds |
| ref | Optional, recommended. Your unique key (up to 120 characters). The same ref = the same message, never sent twice |
curl -X POST https://wa.supatmini.com/api/messages \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"to":"628123456789","text":"We received the payment for order QL-123","delay_min":5,"delay_max":10,"ref":"order-123:paid"}'
# 202 — accepted, waiting for the delay
{"id":42,"status":"queued","code":"","to":"628123456789","send_after":"2026-…","sent_at":"","duplikat":false,…}
# 200 — this ref was used before: the original message is returned, not sent again
{"id":42,"status":"sent",…,"duplikat":true}
# 422 — invalid number, not recorded and never sent to WhatsApp
{"error":"…","code":"nomor_tidak_valid"}
/api/messages/42— status of one message| queued | Waiting for its delay |
| sent | Accepted by WhatsApp (sent_at is set) |
| failed | Not sent. code says why: gagal_kirim (WhatsApp rejected it), antrean_penuh (queue full), perangkat_terputus (device disconnected), kedaluwarsa (held for more than 30 minutes), terputus (the gateway stopped while sending — not retried, to avoid duplicates) |
Who decides what
| Your app | When and whether to send: the schedule ("remind 2 minutes after checkout"), re-checking the situation right before sending (paid? cancelled?), the recipient, the text, and one message per event (ref) |
| WA Gateway | How to send it: the delay in seconds, the send queue, delivery through the WhatsApp Cloud API, and the final status |
Verification codes
Sends a one-time code that the user types into your app. On WhatsApp Cloud API numbers the code is sent with your approved AUTHENTICATION template, which also works outside the 24-hour window. Meta charges for each authentication message, so this is off by default: turn it on in Overview → WABA credentials.
/api/send| to | Recipient number, e.g. 628123456789 |
| text | Message text containing the code |
| otp_code | The code itself — filled into the template |
curl -X POST https://wa.supatmini.com/api/send \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"to":"628123456789","text":"Your code: 4821","otp_code":"4821"}'
# 200
{"queued":true,"channel":"cloud","to":"628123456789"}
Prefer the next section when you can: the user messages you first, so no code and no template are needed.
Verification: the user messages first
User
opens wa_link, sends
WA Gateway
sender matches → verified
Your app
lets the user in
The message comes from the number itself — that is the proof. No code to type.
/api/verify/start| phone | The number to verify |
| success_text | Optional. Automatic reply when verification succeeds |
| prefill | Optional. Ready-to-send message text; may contain {token} |
curl -X POST https://wa.supatmini.com/api/verify/start \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"phone":"628123456789","prefill":"Verify my account: {token}"}'
# 200
{"token":"AB2CD","wa_number":"628999…","wa_link":"https://wa.me/628999…?text=…","expires_in":600}
/api/verify/status?token=AB2CD{"status":"pending"} # not yet
{"status":"verified"} # success
{"status":"expired"} # expired
{"status":"not_found"}
verified is confirmed by your own server.Webhooks
set on the device pageVerification succeeded
POST your-webhook-URL
{"event":"verify.verified","token":"AB2CD","phone":"628…","device_id":1,"at":"2026-…"}
Not signed. Treat it as a notification only: re-check with /api/verify/status before letting the user in.
Incoming message — only when a secret key is set
POST your-webhook-URL
X-Qwa-Signature: sha256=<HMAC-SHA256 of the body, using the secret key>
{"event":"message.in","device_id":1,"from":"628…","text":"Hi, has my order shipped?","at":"2026-…"}
Reject requests whose signature doesn't match. To reply, answer within 15 seconds:
{"reply":"Yes, the tracking number is JX123"}
# with follow-up messages (optional)
{"reply":"One moment","replies":[{"text":"The tracking number is JX123","delay_seconds":12}]}
# no reply
{"reply":""}
Up to 3 messages, at most 60 seconds apart, 4,000 characters each.
Laravel example
// .env → WA_GATEWAY_URL, WA_GATEWAY_TOKEN, WA_GATEWAY_SECRET
$wa = Http::withToken(env('WA_GATEWAY_TOKEN'))->baseUrl(env('WA_GATEWAY_URL'));
// Send an order update (best done in a queued job)
$wa->post('/api/messages', ['to' => $phone, 'text' => "Order $number has shipped", 'ref' => "order-$number:shipped"]);
// Verification: the user messages first
$v = $wa->post('/api/verify/start', ['phone' => $phone])->json();
// show $v['wa_link'], then check:
$ok = $wa->get('/api/verify/status', ['token' => $v['token']])->json('status') === 'verified';
// Receive incoming messages
Route::post('/webhook/wa', function (Request $r) {
$sig = 'sha256=' . hash_hmac('sha256', $r->getContent(), env('WA_GATEWAY_SECRET'));
abort_unless(hash_equals($sig, (string) $r->header('X-Qwa-Signature')), 401);
return ['reply' => 'Thank you, we have received your message.'];
});
WhatsApp rules
Messages go through the official WhatsApp Cloud API, so WhatsApp's rules apply:
- 24-hour window. Free-form messages are only delivered within 24 hours of the customer's last message to you. Outside that window WhatsApp requires an approved template.
- Delivery reports. When WhatsApp rejects a message after accepting it, the Console marks it as failed and shows Meta's reason.
- Opt-in. Only message people who gave you their number and agreed to hear from you.