# Send a message > POST /v1/messages — queue a template or free-text WhatsApp message for delivery. > Source: https://onpad.in/docs/api/send-message Queues a WhatsApp message for delivery and returns immediately with an id to track it. ```text POST https://app.onpad.in/api/v1/messages ``` ## Headers | 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](https://onpad.in/docs/api/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 ```bash 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"] } }' ``` > `919876543210` throughout 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: ```json { "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](https://onpad.in/docs/api/get-message) | | `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](https://onpad.in/docs/api/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.