Skip to navigation

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 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/whatsapp request 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 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.

ScopeGET / HEADPOST / PUT / PATCH / DELETEAdmin-only routes
readYesNoNo
writeYesYesNo
fullYesYesYes

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.

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