Send a WhatsApp message

View as Markdown
Sends a WhatsApp message to an existing contact, in one of two forms you choose: - **Freeform:** send `message`. Only allowed while the contact is inside WhatsApp's 24-hour customer-service window (they wrote to your WhatsApp line in the last 24 hours). Outside it, the call fails with `422 OUTSIDE_24H_WINDOW` **before** the cap is touched or the telephony carrier is called. - **Template:** send `templateId` and optional `templateVariables`. Works whether or not the window is open, but the template must be `approved`. When both are sent, the template wins. If you cannot know the window state, as with an AI agent, use `POST /api/whatsapp/reach`, which picks the form for you. The message goes out from the workspace's `ONLINE` WhatsApp sender, preferring the line the contact last wrote to so the reply stays in the same thread. `status` is the carrier's initial status (usually `queued`). Delivery updates arrive later through the carrier's status callbacks. The message is **not** added to a conversation thread. To have it recorded, use `POST /api/conversations` or `POST /api/conversations/{conversationId}/messages`. **Checks, in order:** contact exists in the workspace with a `whatsapp_number` (`NO_WHATSAPP_NUMBER` otherwise, even for an unknown `contactId`), compliance rules (`COMPLIANCE_BLOCKED`), opt-out (`CONTACT_OPTED_OUT`), template or window, daily send cap (`DAILY_CAP_REACHED`), then sender resolution (`SENDER_NOT_REGISTERED`). **Side effects.** Sends a real WhatsApp message that WhatsApp bills to the workspace's WhatsApp account, since a template opens a business-initiated conversation. Consumes one unit of the daily WhatsApp send cap: 1,000 per UTC day by default, 25,000 on plans with no monthly limit. The unit is refunded if the telephony carrier rejects the send. Does not draw on the AI-message allowance. Writes an audit entry. **Idempotency.** Not idempotent. There is no dedup, so a retry after a timeout can deliver the message twice. Treat a timeout as "possibly sent". **Webhook events.** `audit.log_recorded` for subscribers that selected it explicitly. See [Webhooks](/webhooks). **Access** - **Required scope:** `write`. - **Rate limit:** Outbound — 20 requests/min per workspace, fail-closed, counted on every `/api/whatsapp` request **and** the configuration-mutations limiter — 10 requests/min per workspace, shared with other configuration mutations. See [Rate limits](/rate-limits). - **Plan:** Available on every plan. Requires a WhatsApp sender in `ONLINE` status.

Authentication

AuthorizationBearer
Workspace API key: `jl_` followed by 64 lowercase hex characters, created by the workspace owner in the dashboard (**Settings → API Keys**) and sent as `Authorization: Bearer jl_...`. The plaintext is shown once, at creation; Jelliu stores only a SHA-256 hash. A workspace can hold up to 25 active keys. | Scope | GET / HEAD | POST / PUT / PATCH / DELETE | Admin-only routes | | --- | --- | --- | --- | | `read` | Yes | No | No | | `write` | Yes | Yes | No | | `full` | Yes | Yes | Yes | Operations restricted to admins or owners reject keys without the `full` scope with `403`, and say so in their description. No key, whatever its scope, can mint or revoke API keys or rotate a webhook secret — that requires a signed-in owner session. A revoked key stops authenticating within about 10 seconds. See [Authentication](/authentication).

Request

At least one of message or templateId is required.

contactIdstringRequiredformat: "uuid"

Contact to message. Must belong to the workspace and have a whatsapp_number.

messagestringOptional1-4096 characters

Freeform text, trimmed before sending. Only accepted inside the 24-hour window. Ignored when templateId is sent.

templateIdstringOptionalformat: "uuid"
Id of an approved WhatsApp template of the workspace. Sendable at any time.
templateVariableslist of strings or map from strings to stringsOptional

Values for the template placeholders. An ordered list fills {{1}}, {{2}}, … in order. An object is used as-is, keyed by placeholder number. Only used with templateId. Empty lists or objects are treated as absent.

Response

The telephony carrier accepted the message.
dataobject

Errors

400
Bad Request Error
401
Unauthorized Error
403
Forbidden Error
404
Not Found Error
409
Conflict Error
422
Unprocessable Entity Error
423
Locked Error
429
Too Many Requests Error
502
Bad Gateway Error