Skip to content

Messages

Sends one message.

Scope: messages:send · Accepts Idempotency-Key

One endpoint handles every message type, discriminated by type, following the shape of Meta’s own Cloud API so a developer who has seen that already knows this one.

FieldTypeNotes
tostringPhone number in international format. A contact is created if none exists.
contact_idstringAddress an existing contact instead. One of to or contact_id.
fromstringWhich WhatsApp number to send from. Defaults to the template’s, then the contact’s, then the organization default.
typestringtext, template, image, video, audio or document.
textobject{ "body": "..." } when type is text.
templateobjectSee below.
image / video / audio / documentobjectSee below.
referencestringTemplate sends. Your own id for the message, up to 255 characters. Comes back as in_reply_to.reference when a button on it is tapped.
callback_urlstringTemplate sends. Receives the button.reply event for taps on this message, with no webhook to set up. Public http or https; loopback, private and link-local hosts are refused. Not signed.
bot_replybooleanTemplate sends. Defaults to true. false keeps replies to this message away from the chatbot and AI. They are still stored, shown in the inbox and delivered to webhooks.

Unknown fields are rejected with 400, so a typo tells you the field name is wrong rather than that a required one is missing.

{
"to": "+966500000000",
"type": "text",
"text": { "body": "Your order has shipped." }
}
{
"to": "+966500000000",
"type": "template",
"template": {
"name": "order_shipped",
"parameters": { "name": "Sara", "tracking": "SA123456789" }
}
}
FieldNotes
nameTemplate name. Or use id.
parametersBody variables, keyed by name, or by position ("1", "2") for positional templates.
header_parametersHeader variables. Meta indexes these separately from body variables, so header {{1}} and body {{1}} are different values.
button_parametersButton values keyed by button index: the variable of a dynamic URL button, the code of a copy-code button, or the payload of a quick-reply button. See below.
header_media{ "id": "..." } or { "link": "https://...", "filename": "..." } for templates whose header is an image, video or document.

Every variable a template declares must be supplied. Missing ones are refused with missing_template_parameter, naming what is missing and what the template expects. WhatsApp itself does not check this: it delivers the message with an empty gap in the sentence and reports success, so this is the only place it can be caught.

GET /v1/templates lists each template’s variables.

For authentication templates with a copy-code button, passing the code as parameter "1" fills the button automatically.

A template with quick-reply buttons is a question. To get the answer back as data rather than text, give each button a payload and the message a reference:

{
"to": "+966500000000",
"type": "template",
"reference": "order-1001",
"bot_reply": false,
"template": {
"name": "delivery_confirmation",
"parameters": { "name": "Sara", "order": "1001" },
"button_parameters": { "0": "confirm", "1": "reschedule" }
}
}

button_parameters sets the payload of each quick-reply button by its position on the template. The contact sees the button’s text; when they tap it, a button.reply event carries the payload as button.id and the reference as in_reply_to.reference, so your system learns which question was answered and how, without parsing anything. Without a payload, button.id is the button text.

bot_reply: false keeps the chatbot out of the exchange: the tap, and anything the contact types in reply to this message, reaches your webhooks and the inbox but is not answered by automation. Leave it at the default when the bot should carry the conversation on from the tap.

callback_url delivers the same event to one URL without a webhook subscription. It is not signed, so put a token in the URL if you need to authenticate it.

See button.reply for the payload and React to button taps for the full flow.

{
"to": "+966500000000",
"type": "document",
"document": {
"link": "https://example.com/invoices/1001.pdf",
"filename": "Invoice-1001.pdf",
"caption": "Your invoice"
}
}
FieldNotes
linkA public URL. We fetch it and upload it to WhatsApp. Maximum 15 MB.
idA media id already uploaded to Meta. Use this for anything larger, or to avoid re-uploading.
captionShown under the media. Not supported for audio.
filenameDocuments only. WhatsApp shows it to the recipient.
voiceAudio only. true sends it as a push-to-talk voice note.

Links must be public http or https. Private and internal addresses are refused, and refused again at connection time if DNS resolves to one.

201
{
"id": "6f1c2d3e-...",
"contact_id": "3f2a...",
"to": "+966500000000",
"from": "Main",
"type": "text",
"direction": "outgoing",
"status": "pending",
"content": "Your order has shipped.",
"has_media": false,
"created_at": "2026-08-20T09:15:00Z"
}

pending means accepted, not delivered. The message is stored and queued; WhatsApp has not confirmed anything. Subscribe to message.delivered, message.read and message.failed, or poll the endpoint below.

One message and its current status.

Scope: messages:read

200
{
"id": "6f1c2d3e-...",
"contact_id": "3f2a...",
"to": "+966500000000",
"from": "Main",
"type": "template",
"direction": "outgoing",
"status": "delivered",
"template_name": "order_shipped",
"wamid": "wamid.HBgMOTY2NTAwMDAwMDAwFQIAERgS...",
"has_media": false,
"created_at": "2026-08-20T09:15:00Z",
"updated_at": "2026-08-20T09:15:04Z"
}
StatusMeaning
pendingAccepted by us, not yet handed to WhatsApp
sentWhatsApp accepted it
deliveredIt reached the recipient’s device
readThe recipient opened it
failedWhatsApp refused it. error_message says why.

A message can go from pending straight to failed. Statuses never move backwards, so read will not revert to delivered.

Streams the bytes of a media message. Returns the file, not JSON, with the original content type and filename.

Scope: messages:read

Terminal window
curl https://k-message.kerneltics.com/v1/media/6f1c2d3e-... \
-H "Authorization: Bearer km_live_YOUR_KEY" \
-o invoice.pdf

404 if the message has no media attached, or is not yours.