Send a message
POST /v1/messages — queue a template or free-text WhatsApp message for delivery.
Queues a WhatsApp message for delivery and returns immediately with an id to track it.
POST https://app.onpad.in/api/v1/messagesHeaders
| Header | Required | Value |
|---|---|---|
Authorization | Yes | Bearer wh_live_... |
Content-Type | Yes | application/json |
Idempotency-Key | Yes | A unique key for this send, 8–120 characters |
Idempotency-Key is required, not optional. Without it the request is rejected with 422. It is what stops a retried request from sending the same message twice — see Idempotency.
Allowed characters in the key: letters, digits, ., _, : and -.
Body
Common fields
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient in full international form, digits only, 8–15 digits. Separators are stripped, so +91 98765 43210 is accepted |
type | string | No | template (default) or text |
When type is template
| Field | Type | Required | Description |
|---|---|---|---|
template.name | string | Yes | The approved template's name. Lowercase letters, digits and underscores, up to 512 characters |
template.language | string | No | Language code, default en_US. Must match the approved template |
template.parameters | array | Yes, if the template has variables | Values for {{1}}, {{2}} … in order. Maximum 20, each 1–1024 characters, text or number |
The count must match the template exactly. A template with {{1}} and {{2}} needs two parameters — one or three is a 422.
Templates with an image, video, document or location header are not supported by this endpoint yet. Use a campaign for those.
When type is text
| Field | Type | Required | Description |
|---|---|---|---|
text.body | string | Yes | The message, 1–4096 characters. body at the top level also works |
Free text is only allowed inside the 24-hour customer service window — that is, when the recipient has messaged you in the last 24 hours. Outside it, the request is rejected with 422 and you must send a template instead.
Example request
curl -X POST https://app.onpad.in/api/v1/messages \
-H "Authorization: Bearer wh_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10482-shipped" \
-d '{
"to": "919876543210",
"type": "template",
"template": {
"name": "order_shipped",
"language": "en_US",
"parameters": ["Priya", "#10482", "Friday"]
}
}'
919876543210throughout these docs is a placeholder, not a test number. There is no reserved range in India — whoever owns a number receives whatever you send, and Meta charges you for it. Replace it with your own number before running anything.
Response
202 Accepted when the message is queued:
{
"ok": true,
"duplicate": false,
"message": {
"id": "6f1b9a2c-4d7e-4a31-9f08-2b5c7d1e3a40",
"to": "+919876543210",
"type": "template",
"status": "queued",
"meta_message_id": null,
"attempts": 0,
"error": null,
"created_at": "2026-10-04T09:21:33+05:30",
"sent_at": null,
"delivered_at": null,
"read_at": null,
"status_url": "https://app.onpad.in/api/v1/messages/6f1b9a2c-4d7e-4a31-9f08-2b5c7d1e3a40"
}
}200 OK with "duplicate": true when this Idempotency-Key was already used for an identical request. The original message is returned and nothing is sent again.
Fields in the response
| Field | Description |
|---|---|
id | The ONPAD message id. Use it with GET /messages |
to | The recipient, normalised with a leading + |
status | queued, processing, sent, delivered, read or failed |
meta_message_id | Meta's own id, available once sent |
attempts | How many delivery attempts have been made |
error | The failure reason, when status is failed |
created_at, sent_at, delivered_at, read_at | ISO 8601 timestamps, null until each happens |
202 means accepted, not delivered. Delivery happens in the background — poll for the outcome.
Status codes
| Code | Meaning |
|---|---|
202 | Queued |
200 | Duplicate of an earlier identical request |
400 | invalid_json — the body is not valid JSON |
401 | invalid_api_key — missing, malformed, revoked, or the plan does not include API access |
403 | workspace_read_only — the workspace is read-only, usually unpaid billing |
405 | method_not_allowed — only GET and POST are accepted here |
409 | This Idempotency-Key was used for a different request body |
422 | message_rejected — a validation problem; the message field says which |
500 | internal_error — our fault; the message was not queued |
Every 422 message is specific. See Errors for the full list and what each one means.
Notes
The recipient becomes a contact. Sending to a new number creates a contact in your workspace, so the conversation appears in the inbox and replies land there.
Sending does not check opt-out. If you maintain an opt-out list, check it before calling the API — ONPAD does not block the send for you.
One message per request. To message many people, loop. There is no bulk endpoint; use a campaign for large sends, which is faster and gives you per-recipient reporting.