Skip to main content

Send WhatsApp

Send a one-off transactional WhatsApp message from an approved template — an OTP, an order update, a receipt.

Endpoint

POST /whatsapp/send

Returns: 202 Accepted

Prerequisites

Templates are mandatory

WhatsApp does not allow business-initiated free-text messages. You must create a template, wait for Meta to approve it, then send it by name. Create and track templates in the Arsel Dashboard under WhatsApp > Templates.

Your organization also needs a connected WhatsApp Business account and available WhatsApp balance. Both are set up in the dashboard under WhatsApp > Settings.

Headers

HeaderValueRequired
AuthorizationBearer <your-api-key>Yes
Content-Typeapplication/jsonYes
Idempotency-KeyAny unique string, max 256 charsNo — see Idempotency

Body Parameters

ParameterTypeRequiredDescription
tostringYesRecipient in E.164 format, including the country code.
templateNamestringYesName of an approved template in your account. Max 512 characters.
languagestringNoTemplate language code, e.g. en or ar. Omit when exactly one approved template carries this name.
parametersobjectNoValues for the template placeholders, keyed by placeholder name.

Filling in parameters

What goes in parameters depends on the template's category:

CategoryWhat to pass
AUTHENTICATIONThe one-time code as code. It is repeated automatically into the copy-code button when the template has one.
UTILITYOne entry per {{placeholder}} in the template body, keyed by placeholder name.
MARKETINGSame as UTILITY. Marketing templates are subject to the recipient's marketing opt-out.
When language is required

If the same template name is approved in more than one language, omitting language returns 409 Conflict. Send the language code explicitly.


Response

{
"id": "01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"
}
FieldTypeDescription
idstringUnique message ID. Use it to track delivery via GET /whatsapp/:id.
tip

202 Accepted means the provider accepted the message, not that it was delivered. Final delivery state arrives asynchronously — poll GET /whatsapp/:id.

Billing

Each message is charged at the rate for the destination country and category. The charge is held when the message is accepted and settled from the delivery receipt. A message the provider rejects is not charged.


Idempotency

Sending the same WhatsApp message twice — for example because a network timeout made you retry — could send the message twice — and bill you for both. To make a retry safe, send an Idempotency-Key header whose value is unique to that WhatsApp message (for example otp/123):

  • The first request with a given key is processed normally and its response is stored.
  • Any later request with the same key (within 24 hours) is not reprocessed — it returns the original response unchanged, plus an Idempotent-Replayed: true response header. The side effect happens only once.
  • After 24 hours the key expires and may be reused.

The key can be any unique string up to 256 characters. A UUID works, but a value derived from the entity the request is about — like otp/123 — is easier to regenerate identically on a retry. Keys are scoped per endpoint, so the same value can be reused independently across the send-email, send-SMS, and send-event endpoints without colliding.

Reusing a key with a different payload

Reuse a key with a different request body and the request is rejected with 409 invalid_idempotent_request, which protects you from receiving the wrong cached response. Use a fresh key for a genuinely different request.

Idempotency error responses

StatusnameWhen
400invalid_idempotency_keyThe key is empty or longer than 256 characters.
409invalid_idempotent_requestThe key was already used with a different request body.
409concurrent_idempotent_requestsAn earlier request with the same key is still being processed — retry shortly.
Especially important for OTPs

A retry that sends a second one-time code invalidates the first, and the user is left staring at a code that no longer works. Always send an Idempotency-Key with authentication templates.


Examples

Authentication template (OTP)

curl -X POST "https://api.arsel.sa/v1/whatsapp/send" \
-H "Authorization: Bearer be_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: otp/123" \
-d '{
"to": "+966512345678",
"templateName": "verification_code",
"language": "en",
"parameters": {
"code": "483920"
}
}'

Utility template with placeholders

curl -X POST "https://api.arsel.sa/v1/whatsapp/send" \
-H "Authorization: Bearer be_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"to": "+966512345678",
"templateName": "order_shipped",
"language": "ar",
"parameters": {
"firstName": "سارة",
"orderId": "A-1029",
"carrier": "SMSA"
}
}'

Error Responses

StatusMeaningRetry?
400The number, the template, or your WhatsApp setup was rejected. Deterministic.No — the same request fails the same way
401Invalid or missing API keyNo
403Recipient opted out, monthly limit reached, or insufficient WhatsApp balanceNo
404Template not foundNo
409Idempotency-Key reused with a different payload, a request with that key is still processing, or the template name exists in more than one language and language was omittedNo
422Validation errorNo
429Rate limit exceededYes — after retry-after
503The provider is unreachable or is rate limiting the account. The message was not sent.Yes
{
"status_code": 403,
"name": "forbidden",
"message": "Insufficient WhatsApp balance"
}