Skip to main content

Body

string
required
The customer’s number. E.164, with or without the +; spaces are fine.Normalised the same way the contact importer does, so +91 98765 43210 and 919876543210 are the same customer rather than two.
string
required
The name of an approved template on your account.
string[]
Values for {{1}}, {{2}} … in the body, in order.
string[]
The value for a link button’s blank. Separate on purpose — Meta wants it in a different component, and sending it in variables breaks both the words and the link.
string
The picture for a template whose header is an image, video or document.Required on every send for those templates. WhatsApp does not reuse the file the template was approved with — that one exists for the review — so a media template sent without a picture is refused with 409.Leave it out and the template’s own stored picture is used. Send one and it wins, which is usually what you want here: the photograph on “your order has shipped” is that customer’s parcel, not a stock image kept on the template.WhatsApp fetches it itself, so it has to be a public address.
string
Which approved language version to send, exactly as approved — en_US, hi.Leave it out and you get the oldest approved version, which is the one that existed when you wrote the integration. That is deliberate: adding a language in the console must never change what a working integration sends.Ask for one that is not approved and the 404 names the ones that are, in an available array, so you can fix it from the response.
object
Pre-fills a form the template opens. Only keys the form declared as pre-fillable reach the screen; anything else is ignored rather than refused.
A template is identified by its name, not by an id — so the name is stable across edits, and names are per account, which is why another business’s template is invisible to you.

Response

messageRef is WhatsApp’s own receipt for the message. It is what delivery and read reports are reported against, and what to quote if you ever have to ask what happened to one. A repeat of an idempotency key that already succeeded answers identically, with one field added and a header alongside it:
Every refusal is { "error": "…" } with the status from Errors — and the sentence is meant to be read, not matched on, because it names the specific thing that was wrong.
ok: true means WhatsApp accepted it — not that it arrived. Delivery and read are reported afterwards and appear in the inbox.

Idempotency

Send an Idempotency-Key header. A repeat of the same key sends nothing.
A body field named idempotencyKey works too; the header wins if you send both.
Derive the key from the thing that happened, never from a random value. The point is that your retry, your queue’s redelivery and your colleague’s manual re-run all produce the same key. A UUID generated per attempt is a key that never repeats and therefore never protects anything.
The claim is written before the send, which protects against the case that actually happens: two retries arriving at once, rather than one after the other. The second of the two gets 409that key is already being processed — and not a second message. Retry after a moment and the answer will be waiting. A refusal is remembered as well, and replayed as itself. If the first attempt came back 409 template not approved, so does the retry, rather than sending once the template is approved an hour later under a key you thought was spent.
Keys are honoured for 24 hours. That is Stripe’s window and long enough for any retry worth making. After it, the same string is a new request and sends again — so a key you reuse on a schedule, like daily-digest-priya, will send each day rather than replay for ever. That is usually what you want; it is worth knowing either way.

Worked example

Asking for something back

A template can carry a button that opens a form. formData fills it in before the customer sees it:
An order number your system already knows should arrive typed in rather than asked for. Only fields marked pre-fillable when the form was built accept a value. Answers return into the conversation and are stored against it, so they are readable in the inbox and usable afterwards as structured data.