Send a WhatsApp message
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 with422 OUTSIDE_24H_WINDOWbefore the cap is touched or the telephony carrier is called. - Template: send
templateIdand optionaltemplateVariables. Works whether or not the window is open, but the template must beapproved. 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 day by default, 25,000 on plans with no monthly limit, counted in your workspace’s timezone since 2026-09-15 (it was the UTC day before), so the allowance resets at local midnight. 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.
Access
- Required scope:
write. - Rate limit: Outbound — 20 requests/min per workspace, fail-closed, counted on every
/api/whatsapprequest and the configuration-mutations limiter — 10 requests/min per workspace, shared with other configuration mutations. See Rate limits. - Plan: Available on every plan. Requires a WhatsApp sender in
ONLINEstatus.
Authentication
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.
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.
Request
At least one of message or templateId is required.
Contact to message. Must belong to the workspace and have a whatsapp_number.
Freeform text, trimmed before sending. Only accepted inside the 24-hour window. Ignored when templateId is sent.
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.

