WhatsApp Messages — Reference

Every message type with its description, request payload, webhook response, limits, and a field-by-field required / optional breakdown. Companion to the Postman collection.

Media: always link (URL), never id
01
Webhooks
Set up & secure your callback, then the inbound envelopes (messages, status, template status).
Inbound webhooks arrive as a POST to your configured callback URL. Everything is wrapped in the same outer envelope — objectentry[]changes[]value. Inside value you get either a messages[] array (a user message) or a statuses[] array (a delivery receipt). Per-message webhook responses shown elsewhere are the object found inside value.messages[].

Webhook verification (GET)

GET · hub.challenge Meta docs ↗

When you register a callback URL, Meta sends a one-time GET to prove you own the endpoint. Echo back hub.challenge as plain text only if hub.verify_token matches the token you configured.

GET {{callback_url}}
  ?hub.mode=subscribe
  &hub.verify_token=YOUR_VERIFY_TOKEN
  &hub.challenge=1158201444
app.get('/webhooks/whatsapp', (req, res) => {
  const mode      = req.query['hub.mode'];
  const token     = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];

  if (mode === 'subscribe' && token === process.env.WHATSAPP_VERIFY_TOKEN) {
    return res.status(200).send(challenge);  // echo RAW challenge, text/plain
  }
  return res.sendStatus(403);                // token mismatch
});
200 OK
Content-Type: text/plain

1158201444

Fields

FieldTypeRequired?Notes
hub.modestringAlwaysAlways "subscribe".
hub.verify_tokenstringAlwaysThe token YOU set when configuring the webhook — compare it.
hub.challengestringAlwaysRandom value you must echo back verbatim to pass verification.

Limits

ConstraintValue
Respond withthe raw hub.challenge value, status 200
On mismatchreturn 403 — do not echo the challenge
Timeoutrespond within ~5 seconds
FrequencyGET fires only at setup / URL change

Webhook security & subscription

POST · X-Hub-Signature-256 Meta docs ↗

Every event POST is signed. Verify the X-Hub-Signature-256 header — an HMAC-SHA256 of the raw request body keyed with your App Secret — before trusting any payload. Then subscribe your app (callback + verify token) and subscribe the WABA so events start flowing.

POST {{callback_url}}
Content-Type: application/json
X-Hub-Signature-256: sha256=3f1c0a...   (HMAC-SHA256 of the raw body, keyed with APP SECRET)

{ ...event payload (messages / statuses / template status)... }
const crypto = require('crypto');

// IMPORTANT: capture the RAW body, e.g. express.json({ verify:(req,_,buf)=>{ req.rawBody = buf; } })
function verifySignature(req) {
  const received = req.get('X-Hub-Signature-256') || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.META_APP_SECRET)
    .update(req.rawBody)            // raw bytes, NOT the parsed JSON
    .digest('hex');
  return received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
}
# App-level: point the app at your callback + verify token
curl -X POST 'https://graph.facebook.com/v23.0/{{app_id}}/subscriptions' \
  -d 'object=whatsapp_business_account' \
  -d 'callback_url={{callback_url}}' \
  -d 'verify_token={{verify_token}}' \
  -d 'fields=messages,message_template_status_update,message_template_quality_update' \
  -d 'access_token={{app_id}}|{{app_secret}}'
# Subscribe your app to THIS WhatsApp Business Account's events
curl -X POST 'https://graph.facebook.com/v23.0/{{waba_id}}/subscribed_apps' \
  -H 'Authorization: Bearer {{access_token}}'

Fields

FieldTypeRequired?Notes
X-Hub-Signature-256headerAlwaysFormat "sha256=<hex>"; HMAC-SHA256(raw body, app secret).
objectstringRequiredFor App subscription: "whatsapp_business_account".
callback_urlstringRequiredYour HTTPS endpoint that handles GET + POST.
verify_tokenstringRequiredArbitrary secret you choose; echoed in the GET handshake.
fieldscsvRequiredWebhook fields to receive, e.g. messages, message_template_status_update.
/subscribed_appsendpointRequiredPOST per WABA to actually start delivery (Bearer token).

Limits

ConstraintValue
HMAC inputthe RAW request body bytes — never the re-serialized JSON
Compareuse a timing-safe comparison
Rejectreturn 401/403 if the signature does not match
Two stepsApp /subscriptions sets URL; WABA /subscribed_apps starts events

Common message webhook

value.messages[] Meta docs ↗

The full envelope every inbound user message arrives in. The messages[] object carries a type field that tells you which sub-object to read (text shown here). A context object is present when the user replied to one of your messages.

{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              {
                "profile": { "name": "John Doe" },
                "wa_id": "{{sender_wa_id}}"
              }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "timestamp": "1718533200",
                "type": "text",
                "text": { "body": "Hello" }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeAlways?Notes
objectstringAlways"whatsapp_business_account".
entry[].idstringAlwaysWABA id.
changes[].fieldstringAlways"messages".
value.metadataobjectAlwaysdisplay_phone_number, phone_number_id.
value.contacts[]arrayWith messagesprofile.name, wa_id of the sender.
value.messages[]arrayConditionalPresent for user messages (not for statuses).
messages[].typestringAlwaystext / image / interactive / order / location / button …
messages[].contextobjectOptionalPresent when the user replied to a message: from, id.

Notes

ItemValue
Routingread messages[] for user input, statuses[] for receipts
type fielddecides which sub-object is present
timestampUnix seconds (string)
ackrespond 200 quickly; process async

Message status webhook

value.statuses[] Meta docs ↗

Delivery receipts for messages you sent: sentdeliveredread, or failed. Same outer envelope as a message webhook, but value carries statuses[] instead of messages[].

{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "statuses": [
              {
                "id": "{{wamid}}",
                "status": "delivered",
                "timestamp": "1718533210",
                "recipient_id": "{{recipient_wa_id}}",
                "conversation": {
                  "id": "{{conversation_id}}",
                  "origin": { "type": "utility" }
                },
                "pricing": {
                  "billable": true,
                  "pricing_model": "CBP",
                  "category": "utility"
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeAlways?Notes
statuses[].idstring (wamid)Alwayswamid of the message you sent.
statuses[].statusstringAlwayssent / delivered / read / failed.
statuses[].recipient_idstringAlwayswa_id of the recipient.
statuses[].conversationobjectOptionalid + origin.type (marketing / utility / authentication / service).
statuses[].pricingobjectOptionalbillable, pricing_model, category.
statuses[].errors[]arrayOn failedcode, title, error_data.details.

Notes

ItemValue
Ordersent → delivered → read (read only if recipient has receipts on)
failedincludes errors[] with code & title
origin.typedrives the conversation pricing category

Template status webhook

value · message_template_status_update Meta docs ↗

Fired whenever a template's review status changes — approval, rejection, pausing, etc. Arrives under the message_template_status_update field (not messages). Use it to flag templates as sendable or to alert your team on a rejection.

{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "time": 1718533200,
      "changes": [
        {
          "field": "message_template_status_update",
          "value": {
            "event": "APPROVED",
            "message_template_id": 1234567890,
            "message_template_name": "order_confirmation",
            "message_template_language": "en_US",
            "reason": "NONE"
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
changes[].fieldstringAlways"message_template_status_update".
value.eventstringAlwaysAPPROVED / REJECTED / PENDING / FLAGGED / PAUSED / DISABLED / PENDING_DELETION.
value.message_template_idnumberAlwaysNumeric template id.
value.message_template_namestringAlwaysTemplate name.
value.message_template_languagestringAlwaysLanguage & locale, e.g. en_US.
value.reasonstringOptionalRejection reason, or NONE.

Limits

ConstraintValue
Subscribe fieldmessage_template_status_update
Related fieldsmessage_template_quality_update, template_category_update
eventdrives whether the template is sendable
02
Message Utility
Actions on an incoming message: read receipts, typing, contextual reply.
Message Utility — these act on an incoming message, so you need its wamid from the inbound webhook. Read receipts and typing indicators use a lightweight status payload (no recipient_type / to / type envelope). Contextual reply is a normal outbound message with an added context object.

Read receipts

status: read Meta docs ↗

Marks an incoming message as read — the two blue ticks appear on the user's side. There is no to; the recipient is inferred from the message_id.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "status": "read",
  "message_id": "{{wamid_incoming}}"
}'
{
  "messaging_product": "whatsapp",
  "status": "read",
  "message_id": "{{wamid_incoming}}"
}

Fields

FieldTypeRequired?Notes
messaging_productstringRequiredAlways "whatsapp".
statusstringRequiredMust be "read".
message_idstring (wamid)Requiredwamid of the inbound message to mark read.

Limits

ConstraintValue
Applies toincoming (user → business) messages only
message_ida recent received wamid from the webhook
to fieldnot used — omit it

Typing indicator

status: read · typing_indicator Meta docs ↗

Marks the message read and shows the "typing…" bubble on the user's side while you prepare a reply. Same envelope as a read receipt, plus a typing_indicator object.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "status": "read",
  "message_id": "{{wamid_incoming}}",
  "typing_indicator": {
    "type": "text"
  }
}'
{
  "messaging_product": "whatsapp",
  "status": "read",
  "message_id": "{{wamid_incoming}}",
  "typing_indicator": {
    "type": "text"
  }
}

Fields

FieldTypeRequired?Notes
statusstringRequiredMust be "read" — also marks the message read.
message_idstring (wamid)Requiredwamid of the inbound message.
typing_indicatorobjectRequiredEnables the typing bubble.
typing_indicator.typestringRequiredCurrently "text".

Limits

ConstraintValue
Durationup to 25 seconds, or until you send a message
Dismissed bysending the next outbound message
Best practiceonly show if you are actually going to reply

Contextual reply

context Meta docs ↗

Quotes a previous message above your reply. Add a context object to any outbound message — the rest of the payload is a normal send of that type (text shown here).

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "context": {
    "message_id": "{{wamid_to_reply_to}}"
  },
  "type": "text",
  "text": {
    "body": "Replying to your earlier message"
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "context": {
    "message_id": "{{wamid_to_reply_to}}"
  },
  "type": "text",
  "text": {
    "body": "Replying to your earlier message"
  }
}

Fields

FieldTypeRequired?Notes
contextobjectRequiredMarks this message as a reply.
context.message_idstring (wamid)Requiredwamid of the message being quoted.
type + its objectvariesRequiredStandard send body for the chosen type.

Limits

ConstraintValue
Works withmost message types (text, media, interactive …)
message_idvalid wamid in the same conversation
Quoted messagemust still exist (not deleted)
03
Template messages — Creation
Build & submit templates for approval via the Message Templates API.

Variables — named & numbered

creation

At creation a template may define its variables in either style — both numbered and named variables are accepted by Meta — but a single template must use one style throughout, and every variable needs an example value so the template can be reviewed.

Numbered variables

Placeholders are {{1}}, {{2}}, {{3}} … numbered sequentially from 1. Examples are a positional array — one value per number, in order.

"body": {
  "text": "Hi {{1}}, your order {{2}} is confirmed.",
  "example": { "body_text": [["John", "#A1234"]] }
}
Named variables

Placeholders are words like {{customer_name}}, {{order_id}} (lowercase letters, digits and underscores). Each example is given by name, so order does not matter.

"body": {
  "text": "Hi {{customer_name}}, your order {{order_id}} is confirmed.",
  "example": {
    "body_text_named_params": [
      { "param_name": "customer_name", "example": "John" },
      { "param_name": "order_id",      "example": "#A1234" }
    ]
  }
}
Variable rules
These rules apply to both numbered and named variables:
  • A variable can never be the first or the last character of the body — there must be text before it and after it ({{1}} hello and hello {{order_id}} are both rejected).
  • Two variables may not be adjacent — put fixed text between them ({{1}} {{2}} is rejected).
  • Every variable must be wrapped in double curly braces {{ }}{{1}} or {{order_id}}, never single braces or a bare number/word.
  • A text header allows at most one variable; a footer allows none.

Create a template

POST · message_templates Meta docs ↗

Submit a template for approval. Body = name, language, category, and a components[] array (header, body, footer, buttons). Variables are numbered: {{1}}, {{2}}, … — each one needs an example so reviewers can preview it.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{waba_id}}/message_templates?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "order_confirmation",
  "language": "en_US",
  "category": "utility",
  "components": [
    {
      "type": "header",
      "format": "text",
      "text": "Order {{1}}",
      "example": { "header_text": ["#A1234"] }
    },
    {
      "type": "body",
      "text": "Hi {{1}}, your order {{2}} is confirmed.",
      "example": { "body_text": [["John", "#A1234"]] }
    },
    {
      "type": "footer",
      "text": "Atmik Bharat"
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "quick_reply", "text": "Track order" }
      ]
    }
  ]
}'
{
  "name": "order_confirmation",
  "language": "en_US",
  "category": "utility",
  "components": [
    {
      "type": "header",
      "format": "text",
      "text": "Order {{1}}",
      "example": { "header_text": ["#A1234"] }
    },
    {
      "type": "body",
      "text": "Hi {{1}}, your order {{2}} is confirmed.",
      "example": { "body_text": [["John", "#A1234"]] }
    },
    {
      "type": "footer",
      "text": "Atmik Bharat"
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "quick_reply", "text": "Track order" }
      ]
    }
  ]
}
{
  "id": "1234567890",
  "status": "PENDING",
  "category": "UTILITY"
}

Fields

FieldTypeRequired?Notes
namestringRequiredLowercase a–z, 0–9, underscores only.
languagestringRequiredLanguage & locale code, e.g. en_US.
categorystringRequiredutility / marketing / authentication.
components[]arrayRequiredOptional header + required body + optional footer + optional buttons.
component.exampleobjectIf variablesheader_text / body_text — numbered example values.

Limits

ConstraintValue
Template name≤ 512 chars; lowercase a–z, 0–9, _ only
Header — text≤ 60 chars; max 1 variable
Header — mediaimage / video / document via handle (no id/URL)
Body≤ 1024 chars; can’t start/end with a variable; no two adjacent variables
Footer≤ 60 chars; plain text, no variables
Button title≤ 25 chars (every button type)
URL buttonURL ≤ 2000 chars; HTTPS only; max 2
Phone buttonmax 1 per template
Copy-code value≤ 15 chars; A–Z and 0–9 only
Quick-replytitle ≤ 25; no id (tap returns the title as payload)
Buttons totalmax 10; same-type buttons must be consecutive
Variablesnumbered {{1}}, {{2}}…; each needs an example
Endpoint for this whole section: POST {{base_url}}/{{waba_id}}/message_templates (Bearer {{access_token}}). Variables are numbered only{{1}}, {{2}}, … (named parameters are not used). Component type, header format, and button type are case-insensitive; we use lowercase to match Meta's current docs. Media headers use a handle from the Vertex Suite media wrapper (see Media upload) — never Graph, and never a media id at creation. Approval lands on the Template status webhook.

Header component (formats)

components · header Meta docs ↗

A template may have one optional header. It can be a single line of text (with at most one variable), a piece of media (image / video / document), a location, or a product (single-product templates). Media is referenced by a handle from the Vertex Suite media wrapper — see Media upload. No media id here.

// TEXT — one short line, max 1 variable
{
  "type": "header",
  "format": "text",
  "text": "Receipt for {{1}}",
  "example": { "header_text": ["Order #A1234"] }
}

// MEDIA — image / video / document (use a handle from the media wrapper)
{
  "type": "header",
  "format": "image",
  "example": { "header_handle": ["4:aW1hZ2U...:e:1700000000:..."] }
}

// LOCATION — pin supplied at send time
{ "type": "header", "format": "location" }

// PRODUCT — single-product templates pull the image from the catalog
{ "type": "header", "format": "product" }

Fields

FieldTypeRequired?Notes
formatstringRequiredtext / image / video / document / location / product.
textstringformat=textHeader line; max 1 variable.
example.header_textarrayIf text variableOne example value for the {{1}} in the header.
example.header_handlearrayIf mediaHandle string from the Vertex Suite media wrapper.

Limits

ConstraintValue
Headers per templatemax 1
Text header≤ 60 chars; exactly 0–1 variable
Media headersupply a handle (not an id, not a URL)
Allowed mediaimage, video, document (no GIF)
location / productno example needed at creation

Media upload (wrapper API)

wrapper · handle + link Meta media types ↗

Media is handled entirely by the wrapper Media API — no direct Graph calls. Upload a file once; the response gives you a handle (for template creation headers) and a public link (for sending media messages). Accepted types and size caps are below.

# One multipart upload — the wrapper runs Meta's Resumable Upload + hosting for you.
curl -X POST '{{media_api}}/v1/media/upload' \
  -H 'Authorization: Bearer {{access_token}}' \
  -F 'file=@/path/to/header.jpg'
{
  "handle": "4:aW1hZ2U...:e:1700000000:...",
  "link": "https://cdn.vertexsuite.in/media/abc123.jpg",
  "mime_type": "image/jpeg"
}
{
  "type": "header",
  "format": "image",
  "example": { "header_handle": ["4:aW1hZ2U...:e:1700000000:..."] }
}
{
  "type": "image",
  "image": { "link": "https://cdn.vertexsuite.in/media/abc123.jpg" }
}

Fields

FieldTypeRequired?Notes
filemultipartRequiredThe raw media file (one of the types below).
handlestringResponseUse in a template header’s example.header_handle.
linkstringResponsePublic URL to send in a media message (never an id).
mime_typestringResponseDetected MIME type of the file.

Accepted media & size limits

MediaAccepted formatsMax size
Imageimage/jpeg, image/png (8-bit, RGB/RGBA)5 MB
Videovideo/mp4, video/3gp (H.264 + AAC; 1 or 0 audio streams)16 MB
Audioaudio/aac, audio/mp4 (m4a), audio/mpeg (mp3), audio/amr, audio/ogg (OPUS only)16 MB
Documentpdf, doc(x), ppt(x), xls(x), txt100 MB
Stickerimage/webp, 512×512 pxstatic 100 KB · animated 500 KB

Limits

ConstraintValue
Graph APInot used — the wrapper handles upload + hosting
At creationuse the handle in header.example.header_handle
At senduse the public link (a media id is never used)
Handle reuseone handle per template submission
Video codecH.264 video + AAC audio only
No Graph API for media. You never call Meta’s Resumable Upload or /media endpoints directly — Atmik Bharat’s wrapper Media API does that for you. Upload once and reuse the response: the handle goes into a template header at creation, and the public link is what you send in media messages. A media id is never used.

Body & footer components

components · body / footer Meta docs ↗

Body is the only required component — plain text plus numbered variables ({{1}}, {{2}}…). Footer is an optional single line of plain text with no variables — good for opt-out or brand attribution.

// BODY — numbered variables, one example row per variable
{
  "type": "body",
  "text": "Hi {{1}}, your order {{2}} ships today.",
  "example": { "body_text": [["John", "#A1234"]] }
}

// FOOTER — plain text only, no variables
{
  "type": "footer",
  "text": "Reply STOP to opt out"
}

Fields

FieldTypeRequired?Notes
body.textstringRequiredMain content; numbered variables only.
example.body_textarrayIf variablesArray of example rows; one value per {{n}}.
footer.textstringOptionalSingle line, plain text, no variables.

Limits

ConstraintValue
Body≤ 1024 chars (the {{n}} tag counts as 1 char)
Body variablesnumbered only; can’t be first or last; no two adjacent
Nonew lines/tabs or 4+ consecutive spaces
Footer≤ 60 chars; text only, no variables, no emoji

Buttons — all types

components · buttons Meta docs ↗

Buttons are optional and sit at the bottom. Below is every button type in one array for reference; in practice you mix only what you need (see the combination rules under the table). The table lists each type, its key fields with lengths, and which webhook a tap produces.

{
  "type": "buttons",
  "buttons": [
    { "type": "quick_reply", "text": "Stop promotions" },
    { "type": "url", "text": "Track order",
      "url": "https://ex.com/o/{{1}}", "example": ["A1234"] },
    { "type": "url", "text": "Visit site", "url": "https://ex.com" },
    { "type": "phone_number", "text": "Call us", "phone_number": "+919999999999" },
    { "type": "copy_code", "example": "SAVE20" },
    { "type": "flow", "text": "Book now", "flow_id": "1234567890",
      "navigate_screen": "WELCOME", "flow_action": "navigate" },
    { "type": "catalog", "text": "View catalog" },
    { "type": "mpm", "text": "View items" },
    { "type": "spm", "text": "View" },
    { "type": "voice_call", "text": "Call on WhatsApp" }
  ]
}

Fields

Button typeWhat it doesKey fields (lengths)Webhook on tap
quick_replySends a short text back to you (e.g. opt-out).text ≤ 25; no idbutton
urlOpens a URL; dynamic suffix via {{1}} at send.text ≤ 25; url ≤ 2000 (HTTPS); max 2none
phone_numberDials a number. Max 1 per template.text ≤ 25; phone ≤ 20 digitsnone
copy_codeCopies a coupon code to the clipboard.code ≤ 15; A–Z 0–9 onlynone
flowLaunches a WhatsApp Flow form.text ≤ 25; flow_id; navigate_screennfm_reply
catalogOpens your full catalog.text ≤ 25order
mpmOpens a curated multi-product list.text ≤ 25order
spmOpens a single product card.text ≤ 25 (header.format=product)order
voice_callStarts a WhatsApp call to the business.text ≤ 25none
otpAuth only — autofill / copy / zero-tap code.otp_type; package_name; signature_hashbutton

Limits

ConstraintValue
Total buttonsmax 10
Same typemust be grouped consecutively
URL / phonemax 2 URL, max 1 phone
Button title≤ 25 chars (all types)
Copy-code≤ 15 chars, alphanumeric
Quick-reply idnone — the tap webhook echoes the title (≤ 25)
How buttons combine. Up to 10 buttons total. You can mix quick reply and call-to-action (url / phone) buttons, but all buttons of the same type must be consecutive — e.g. group your quick replies together and your CTAs together. URL max 2, phone max 1. If a template has more than 3 buttons, WhatsApp shows the first few and tucks the rest under a “See all options” menu. Commerce (catalog / mpm / spm), OTP, and flow buttons are normally the only button on their template — that single button is the action.

Common button limits & rules

RuleValue
Total buttonsUp to 10 buttons per template
Quick Reply button textUp to 25 characters
Call-To-Action button textUp to 25 characters
Phone number CTA buttonsMaximum 1
Website URL CTA buttonsMaximum 2
Quick Reply buttonsUp to 10
Copy Code buttonAuthentication templates only · up to 15 characters
OTP buttonAuthentication templates only

Button combination rules

CombinationSupported
Quick Reply only
URL CTA only
Phone CTA only
URL + Phone CTA
Multiple Quick Replies
Quick Reply + CTA buttons
Multiple Phone CTA buttons
More than 2 URL CTA buttons

Create authentication template

category · authentication Meta docs ↗

Auth templates have fixed body/footer — you don’t write copy, you toggle options. The OTP button comes in three flavours: copy-code (user taps to copy), one-tap (autofill, needs your app package + signature hash), and zero-tap (app reads the code automatically). All three full payloads below.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{waba_id}}/message_templates?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "auth_copy_code",
  "language": "en_US",
  "category": "authentication",
  "message_send_ttl_seconds": 600,
  "components": [
    {
      "type": "body",
      "add_security_recommendation": true
    },
    {
      "type": "footer",
      "code_expiration_minutes": 10
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "otp", "otp_type": "copy_code", "text": "Copy code" }
      ]
    }
  ]
}'
{
  "name": "auth_copy_code",
  "language": "en_US",
  "category": "authentication",
  "message_send_ttl_seconds": 600,
  "components": [
    {
      "type": "body",
      "add_security_recommendation": true
    },
    {
      "type": "footer",
      "code_expiration_minutes": 10
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "otp", "otp_type": "copy_code", "text": "Copy code" }
      ]
    }
  ]
}
{
  "name": "auth_one_tap",
  "language": "en_US",
  "category": "authentication",
  "message_send_ttl_seconds": 600,
  "components": [
    {
      "type": "body",
      "add_security_recommendation": true
    },
    {
      "type": "footer",
      "code_expiration_minutes": 10
    },
    {
      "type": "buttons",
      "buttons": [
        {
          "type": "otp",
          "otp_type": "one_tap",
          "text": "Autofill",
          "autofill_text": "Autofill",
          "package_name": "com.atmikbharat.app",
          "signature_hash": "K0xxxxxxxxx"
        }
      ]
    }
  ]
}
{
  "name": "auth_zero_tap",
  "language": "en_US",
  "category": "authentication",
  "message_send_ttl_seconds": 600,
  "components": [
    {
      "type": "body",
      "add_security_recommendation": true
    },
    {
      "type": "footer",
      "code_expiration_minutes": 10
    },
    {
      "type": "buttons",
      "buttons": [
        {
          "type": "otp",
          "otp_type": "zero_tap",
          "text": "Autofill",
          "autofill_text": "Autofill",
          "zero_tap_terms_accepted": true,
          "package_name": "com.atmikbharat.app",
          "signature_hash": "K0xxxxxxxxx"
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
categorystringRequiredMust be "authentication".
body.add_security_recommendationbooleanOptionalAppends Meta’s standard security line.
footer.code_expiration_minutesnumberOptionalShows an expiry note; 1–90.
button.otp_typestringRequiredcopy_code / one_tap / zero_tap.
package_name + signature_hashstringone_tap / zero_tapAndroid app identity for autofill.
zero_tap_terms_acceptedbooleanzero_tapMust be true to use zero-tap.
message_send_ttl_secondsnumberOptionalHow long Cloud API keeps trying to deliver.

Limits

ConstraintValue
Body / footerconfig flags, not free text
OTP codepassed at send into body + button
one_tap / zero_tapAndroid only; need package + signature hash
zero_tapalso requires accepted terms + a one-tap fallback
Button title≤ 25 chars

Create limited-time offer template

components · limited_time_offer Meta docs ↗

Adds a live countdown driven by an expiry you pass at send. Pair it with a copy-code coupon button.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{waba_id}}/message_templates?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "lto_offer",
  "language": "en_US",
  "category": "marketing",
  "components": [
    {
      "type": "header",
      "format": "image",
      "example": { "header_handle": ["4:aW1hZ2U...:e:..."] }
    },
    {
      "type": "body",
      "text": "Hi {{1}}, grab this before it ends!",
      "example": { "body_text": [["John"]] }
    },
    {
      "type": "limited_time_offer",
      "limited_time_offer": { "text": "Expiring soon!", "has_expiration": true }
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "copy_code", "example": "SAVE20" },
        { "type": "url", "text": "Shop now", "url": "https://ex.com" }
      ]
    }
  ]
}'
{
  "name": "lto_offer",
  "language": "en_US",
  "category": "marketing",
  "components": [
    {
      "type": "header",
      "format": "image",
      "example": { "header_handle": ["4:aW1hZ2U...:e:..."] }
    },
    {
      "type": "body",
      "text": "Hi {{1}}, grab this before it ends!",
      "example": { "body_text": [["John"]] }
    },
    {
      "type": "limited_time_offer",
      "limited_time_offer": { "text": "Expiring soon!", "has_expiration": true }
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "copy_code", "example": "SAVE20" },
        { "type": "url", "text": "Shop now", "url": "https://ex.com" }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
limited_time_offer.textstringRequiredOffer caption shown by the timer.
limited_time_offer.has_expirationbooleanOptionalShow the live countdown.
buttonsarrayRequiredTypically copy_code + url.

Limits

ConstraintValue
Categorymarketing
Expiryexpiration_time_ms passed at send
Buttonscopy_code carries the coupon

Create catalog template

buttons · catalog Meta docs ↗

A body + a single catalog button that opens your whole catalog. Requires a catalog connected to the WABA.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{waba_id}}/message_templates?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "catalog_promo",
  "language": "en_US",
  "category": "marketing",
  "components": [
    {
      "type": "body",
      "text": "Browse our catalog, {{1}}!",
      "example": { "body_text": [["John"]] }
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "catalog", "text": "View catalog" }
      ]
    }
  ]
}'
{
  "name": "catalog_promo",
  "language": "en_US",
  "category": "marketing",
  "components": [
    {
      "type": "body",
      "text": "Browse our catalog, {{1}}!",
      "example": { "body_text": [["John"]] }
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "catalog", "text": "View catalog" }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
button.typestringRequired"catalog".
body.textstringRequiredSupports numbered variables.

Limits

ConstraintValue
Prerequisitecatalog connected to the WABA
Thumbnailset at send via thumbnail_product_retailer_id
Tapopens catalog; cart submit → order webhook

Create single-product (SPM) template

buttons · spm Meta docs ↗

A marketing template that shows one product (image, title, price from the catalog) with a non-customizable spm View button. Header must be format: "product"; the product itself is chosen at send time. Body uses numbered variables.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{waba_id}}/message_templates?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "spm_offer",
  "language": "en_US",
  "category": "marketing",
  "components": [
    {
      "type": "header",
      "format": "product"
    },
    {
      "type": "body",
      "text": "Use code {{1}} to get {{2}} off our newest succulent!",
      "example": { "body_text": [["15OFF", "15%"]] }
    },
    {
      "type": "footer",
      "text": "Offer ends soon"
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "spm", "text": "View" }
      ]
    }
  ]
}'
{
  "name": "spm_offer",
  "language": "en_US",
  "category": "marketing",
  "components": [
    {
      "type": "header",
      "format": "product"
    },
    {
      "type": "body",
      "text": "Use code {{1}} to get {{2}} off our newest succulent!",
      "example": { "body_text": [["15OFF", "15%"]] }
    },
    {
      "type": "footer",
      "text": "Offer ends soon"
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "spm", "text": "View" }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
header.formatstringRequiredMust be "product".
button.typestringRequired"spm"; text is the View label (≤ 25).
body.textstringRequired≤ 160 chars; numbered variables.
footer.textstringOptional≤ 60 chars.

Limits

ConstraintValue
Categorymarketing
Body≤ 160 chars
Productproduct_retailer_id + catalog_id supplied at send
ClientWhatsApp v2.22.24+; forwarding disabled

Create multi-product (MPM) template

buttons · mpm Meta docs ↗

A text header + body + a single mpm button. Sections and products are supplied at send time.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{waba_id}}/message_templates?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "mpm_bestsellers",
  "language": "en_US",
  "category": "marketing",
  "components": [
    {
      "type": "header",
      "format": "text",
      "text": "Bestsellers"
    },
    {
      "type": "body",
      "text": "Hi {{1}}, here are our picks.",
      "example": { "body_text": [["John"]] }
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "mpm", "text": "View items" }
      ]
    }
  ]
}'
{
  "name": "mpm_bestsellers",
  "language": "en_US",
  "category": "marketing",
  "components": [
    {
      "type": "header",
      "format": "text",
      "text": "Bestsellers"
    },
    {
      "type": "body",
      "text": "Hi {{1}}, here are our picks.",
      "example": { "body_text": [["John"]] }
    },
    {
      "type": "buttons",
      "buttons": [
        { "type": "mpm", "text": "View items" }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
header.formatstringRequired"text" header.
button.typestringRequired"mpm".

Limits

ConstraintValue
Sectionsup to 10, 30 products total (at send)
Prerequisitecatalog connected to the WABA
Tapopens product list; cart submit → order webhook

Create flow template

buttons · flow Meta docs ↗

Body + a flow button that launches a published Flow. Reference an existing flow_id (or inline flow_json) and the first screen to open.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{waba_id}}/message_templates?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "appointment_flow",
  "language": "en_US",
  "category": "marketing",
  "components": [
    {
      "type": "body",
      "text": "Hi {{1}}, book your slot.",
      "example": { "body_text": [["John"]] }
    },
    {
      "type": "buttons",
      "buttons": [
        {
          "type": "flow",
          "text": "Book now",
          "flow_id": "1234567890",
          "navigate_screen": "APPOINTMENT",
          "flow_action": "navigate"
        }
      ]
    }
  ]
}'
{
  "name": "appointment_flow",
  "language": "en_US",
  "category": "marketing",
  "components": [
    {
      "type": "body",
      "text": "Hi {{1}}, book your slot.",
      "example": { "body_text": [["John"]] }
    },
    {
      "type": "buttons",
      "buttons": [
        {
          "type": "flow",
          "text": "Book now",
          "flow_id": "1234567890",
          "navigate_screen": "APPOINTMENT",
          "flow_action": "navigate"
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
button.typestringRequired"flow".
button.flow_idstringIf publishedId of an existing Flow (or use flow_json).
button.navigate_screenstringRequiredFirst screen id to open.
button.flow_actionstringOptionalnavigate (default) or data_exchange.

Limits

ConstraintValue
Referenceflow_id (published) or inline flow_json
Tapopens the Flow; completion → nfm_reply webhook
Windowsend via template anytime; data in response_json

Create checkout button template

buttons · order_detailsMeta docs ↗

Body + an order_details checkout button labelled Review and pay. Tapping it opens the native order summary where the customer reviews items and completes payment. The itemized order, shipping and amounts are attached at send time.

Same template for both payment methods. An order_details template only defines the body (+ optional footer) and the Review-and-pay button. Whether the order settles via a payment_gateway or a payment_link, and whether items are catalog (retailer_id) or custom (name), is all decided at send time — you do not need a separate template for each.
# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{waba_id}}/message_templates?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "order_checkout",
  "language": "en_US",
  "category": "utility",
  "components": [
    {
      "type": "body",
      "text": "Hi {{1}}, your order {{2}} is ready. Tap below to review the items and pay securely.",
      "example": {
        "body_text": [
          [
            "John",
            "#A1234"
          ]
        ]
      }
    },
    {
      "type": "buttons",
      "buttons": [
        {
          "type": "order_details",
          "text": "Review and pay"
        }
      ]
    }
  ]
}'
{
  "name": "order_checkout",
  "language": "en_US",
  "category": "utility",
  "components": [
    {
      "type": "body",
      "text": "Hi {{1}}, your order {{2}} is ready. Tap below to review the items and pay securely.",
      "example": {
        "body_text": [
          [
            "John",
            "#A1234"
          ]
        ]
      }
    },
    {
      "type": "buttons",
      "buttons": [
        {
          "type": "order_details",
          "text": "Review and pay"
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
components[].buttons[].typestringRequired"order_details" — the Review and pay checkout button.
button.textstringRequiredButton label, ≤ 25 chars.
body.textstringRequiredMessage body; numbered or named variables allowed.
example.body_textarrayConditionalOne example value per body variable.

Limits

ConstraintValue
Categoryutility or marketing
Checkout buttonone order_details button per template
Order dataitems, shipping, totals & currency are supplied at send, not creation
Prerequisitepayments configured & enabled for the WABA
Tapopens the native Review and pay sheet; result via webhook

Create order details template

commerce · order_detailsMeta docs ↗

A template that pairs with a full itemized order — line items, taxes, shipping, discount and total. It carries a body, an optional footer (e.g. an expiry note) and the order_details button. The order breakdown is sent with the order details send.

Same template for both payment methods. An order_details template only defines the body (+ optional footer) and the Review-and-pay button. Whether the order settles via a payment_gateway or a payment_link, and whether items are catalog (retailer_id) or custom (name), is all decided at send time — you do not need a separate template for each.
# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{waba_id}}/message_templates?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "order_details",
  "language": "en_US",
  "category": "utility",
  "components": [
    {
      "type": "body",
      "text": "Hi {{1}}, here is the breakdown for order {{2}}. Review the items, taxes and total, then pay.",
      "example": {
        "body_text": [
          [
            "John",
            "#A1234"
          ]
        ]
      }
    },
    {
      "type": "footer",
      "text": "Complete payment before the order expires"
    },
    {
      "type": "buttons",
      "buttons": [
        {
          "type": "order_details",
          "text": "Review and pay"
        }
      ]
    }
  ]
}'
{
  "name": "order_details",
  "language": "en_US",
  "category": "utility",
  "components": [
    {
      "type": "body",
      "text": "Hi {{1}}, here is the breakdown for order {{2}}. Review the items, taxes and total, then pay.",
      "example": {
        "body_text": [
          [
            "John",
            "#A1234"
          ]
        ]
      }
    },
    {
      "type": "footer",
      "text": "Complete payment before the order expires"
    },
    {
      "type": "buttons",
      "buttons": [
        {
          "type": "order_details",
          "text": "Review and pay"
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
body.textstringRequiredIntro line above the order card; variables allowed.
footer.textstringOptionalSingle line, ≤ 60 chars, no variables.
components[].buttons[].typestringRequired"order_details".
button.textstringRequiredButton label, ≤ 25 chars.

Limits

ConstraintValue
Categoryutility or marketing
Itemized orderline items, subtotal, tax, shipping, discount supplied at send
Footeroptional; good for an expiry / payment-window note
Buttonsone order_details button
Amountssent in minor units at send (value + offset)

Create order status template

commerce · order_statusMeta docs ↗

A transactional template for order status updates — confirmed, processing, shipped, completed or canceled. Body variables carry the order reference, the new status and an optional tracking line. Pairs with the order status send.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{waba_id}}/message_templates?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "order_status_update",
  "language": "en_US",
  "category": "utility",
  "components": [
    {
      "type": "body",
      "text": "Order {{1}} is now {{2}}. {{3}}",
      "example": {
        "body_text": [
          [
            "#A1234",
            "shipped",
            "Track: https://track.example.com/A1234"
          ]
        ]
      }
    }
  ]
}'
{
  "name": "order_status_update",
  "language": "en_US",
  "category": "utility",
  "components": [
    {
      "type": "body",
      "text": "Order {{1}} is now {{2}}. {{3}}",
      "example": {
        "body_text": [
          [
            "#A1234",
            "shipped",
            "Track: https://track.example.com/A1234"
          ]
        ]
      }
    }
  ]
}

Fields

FieldTypeRequired?Notes
body.textstringRequiredStatus copy with variables (order id, status, tracking).
example.body_textarrayRequiredOne example value per variable.
footer.textstringOptionalOptional supporting line, ≤ 60 chars.

Limits

ConstraintValue
Categoryutility (transactional)
Status textdriven by body variables
Pairs withthe order_status send, which carries the machine-readable status
Windowsendable anytime as an approved template
04
Template messages — Sending
Send an approved template by filling its header / body / button variables.
Common template envelope — every template send is type: "template" with a template object holding name, language.code and a components[] array. Components fill the variables of an already-approved template (header, body, buttons) positionally — they do not define new content. Templates are required for business-initiated messages outside the 24-hour service window.

Variables — numbered or named

sending

Fill the variables in the order/shape the template was approved with. A template is either numbered or named, never both.

Numbered: placeholders are {{1}}, {{2}} … parameters are positional (order matters, no name).

"components": [
  { "type": "body", "parameters": [
      { "type": "text", "text": "John" },
      { "type": "text", "text": "#A1234" }
  ] }
]

Named: placeholders are {{order_id}}, {{customer_name}} … each parameter carries a parameter_name matching the placeholder (order no longer matters).

"components": [
  { "type": "body", "parameters": [
      { "type": "text", "parameter_name": "customer_name", "text": "John" },
      { "type": "text", "parameter_name": "order_id",      "text": "#A1234" }
  ] }
]

Header — types

sending

The header component takes one parameter whose type matches the approved header format. Media headers always use a public link, never a media id.

// text header (named: add "parameter_name")
{ "type": "header", "parameters": [
    { "type": "text", "text": "Diwali Sale" } ] }

// image / video / document header — always a public link, never an id
{ "type": "header", "parameters": [
    { "type": "image",    "image":    { "link": "{{media_image_url}}" } } ] }
{ "type": "header", "parameters": [
    { "type": "video",    "video":    { "link": "{{media_video_url}}" } } ] }
{ "type": "header", "parameters": [
    { "type": "document", "document": { "link": "{{media_doc_url}}", "filename": "invoice.pdf" } } ] }

// location header
{ "type": "header", "parameters": [
    { "type": "location", "location": {
        "latitude": "21.25", "longitude": "81.63",
        "name": "Atmik Bharat", "address": "Raipur, CG" } } ] }

Body — types

sending

The body component fills each placeholder with a parameter. The common parameter types:

// text
{ "type": "text", "text": "20%" }

// currency
{ "type": "currency", "currency": {
    "fallback_value": "Rs 499.00", "code": "INR", "amount_1000": 499000 } }

// date_time
{ "type": "date_time", "date_time": { "fallback_value": "12 Aug, 3 PM" } }

// named template — every object also carries "parameter_name"
{ "type": "text", "parameter_name": "discount", "text": "20%" }

Buttons — types & indexes

sending

A button component is type:"button" with a sub_type and a zero-based index — the button’s position in the approved template (first = "0", second = "1"…). The index must match where that button sits; same-type buttons stay consecutive.

// index is the button's ZERO-BASED position in the approved
// template: first button = "0", second = "1", and so on.

// quick_reply
{ "type":"button","sub_type":"quick_reply","index":"0",
  "parameters":[ { "type":"payload","payload":"STOP_PROMOS" } ] }

// url  (value is appended to the approved URL)
{ "type":"button","sub_type":"url","index":"1",
  "parameters":[ { "type":"text","text":"track/123" } ] }

// copy_code  (coupon)
{ "type":"button","sub_type":"copy_code","index":"2",
  "parameters":[ { "type":"coupon_code","coupon_code":"SAVE20" } ] }

// flow
{ "type":"button","sub_type":"flow","index":"0",
  "parameters":[ { "type":"action","action":{ "flow_token":"abc123" } } ] }

// otp (authentication) — sub_type url or copy_code
{ "type":"button","sub_type":"url","index":"0",
  "parameters":[ { "type":"text","text":"123456" } ] }

Utility template

template · utility Meta docs ↗

Utility templates are used for transactional and service-related updates such as order confirmations, payment updates, appointment reminders, account alerts, delivery notifications, location-based updates, and call permission requests. Parameters map positionally to the template's {{1}}, {{2}} variables.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "order_confirmation",
    "language": { "code": "en" },
    "components": [
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "John" },
          { "type": "text", "text": "#A1234" }
        ]
      }
    ]
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "order_confirmation",
    "language": { "code": "en" },
    "components": [
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "John" },
          { "type": "text", "text": "#A1234" }
        ]
      }
    ]
  }
}
Utility, not marketing. Utility templates should be transactional or service-related. Promotional offers, discounts, upsell, cross-sell, or marketing-style content should not be sent as utility templates.

Fields

FieldTypeRequired?Notes
template.namestringRequiredExact approved template name.
template.language.codestringRequirede.g. en, en_US, hi.
components[]arrayConditionalRequired only if the template has variables.
components[].typestringRequiredheader / body / button.
parameters[].typestringRequiredtext / currency / date_time / image …

Limits

ConstraintValue
CategoryUTILITY (approved at creation)
Parametersmust match the template's variable count & order
Windowsendable anytime (no 24h limit)
Supported components1 optional header, 1 required body, 1 optional footer, and up to 10 buttons

Marketing template

template · marketing Meta docs ↗

Marketing templates are used for promotional or awareness-based messages such as offers, discounts, product launches, announcements, event invites, and re-engagement campaigns.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "summer_sale",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "header",
        "parameters": [
          { "type": "image", "image": { "link": "{{media_image_url}}" } }
        ]
      },
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "20%" }
        ]
      },
      {
        "type": "button",
        "sub_type": "quick_reply",
        "index": "0",
        "parameters": [
          { "type": "payload", "payload": "STOP_PROMOS" }
        ]
      }
    ]
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "summer_sale",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "header",
        "parameters": [
          { "type": "image", "image": { "link": "{{media_image_url}}" } }
        ]
      },
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "20%" }
        ]
      },
      {
        "type": "button",
        "sub_type": "quick_reply",
        "index": "0",
        "parameters": [
          { "type": "payload", "payload": "STOP_PROMOS" }
        ]
      }
    ]
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "button",
                "button": {
                  "payload": "STOP_PROMOS",
                  "text": "Stop promotions"
                },
                "context": {
                  "from": "{{business_phone}}",
                  "id": "{{wamid_of_template}}"
                }
              }
            ]
          }
        }
      ]
    }
  ]
}
TTL & pacing. Use a shorter TTL for time-sensitive campaigns such as flash sales, limited-time offers, event reminders, or same-day promotions. Marketing messages should not be repeatedly sent to users who are unlikely to engage.

Fields

FieldTypeRequired?Notes
header parameters[].image.linkstring (URL)If media headerPublic URL; matches the header format.
button.sub_typestringRequiredquick_reply / url / phone_number / copy_code …
button.indexstringRequiredZero-based button position.
button parameters[].payloadstringquick_replyReturned to you on tap.
messages[].button.payloadstringWebhookThe payload you set, echoed on tap.
template.namestringRequiredExact approved and active marketing template name.
template.language.codestringRequiredApproved template language code, e.g. en, en_US, hi.
components[]arrayConditionalRequired when the template has dynamic variables, media header, or button parameters.
body.parameters[]arrayConditionalRequired when the body contains variables like {{1}}, {{2}}.
body.parameters[].textstringIf text varText value used to fill the body variable.

Limits

ConstraintValue
CategoryMARKETING
Quick-reply taparrives as type:"button" (not interactive)
Per-user pacingsubject to marketing template limits
Supported components1 optional header, 1 required body, 1 optional footer, and up to 10 buttons
ParametersMust match the template's variable count and order
Message validity / TTLMarketing messages may support a configurable validity period; if not delivered within this period it can expire instead of being delivered late
Marketing deliverySubject to WhatsApp quality, engagement, pacing, and delivery limits. Some messages may be delayed or held for quality checks

Authentication template

template · authentication Meta docs ↗

Authentication templates are used to send one-time passwords or verification codes for login, account verification, recovery, or security checks. The OTP value is passed in the body and button parameter, and both values must match.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "otp_code",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "123456" }
        ]
      },
      {
        "type": "button",
        "sub_type": "url",
        "index": "0",
        "parameters": [
          { "type": "text", "text": "123456" }
        ]
      }
    ]
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "otp_code",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "123456" }
        ]
      },
      {
        "type": "button",
        "sub_type": "url",
        "index": "0",
        "parameters": [
          { "type": "text", "text": "123456" }
        ]
      }
    ]
  }
}
For most basic OTP flows, copy code is the easiest option. One-tap autofill and zero-tap require additional mobile app integration and eligibility checks. If those eligibility checks fail, WhatsApp may display a copy code button instead.

Fields

FieldTypeRequired?Notes
body parameters[0].textstringRequiredThe OTP code.
button.sub_typestringRequiredurl (one-tap) or copy_code.
button parameters[0].textstringRequiredMust equal the OTP in the body.
template.namestringRequiredExact approved and active authentication template name.
template.language.codestringRequiredApproved template language code, e.g. en, en_US, hi.
components[]arrayRequiredUsed to pass the OTP value into the approved authentication template.
button.indexstringRequiredZero-based button position, usually 0.

Limits

ConstraintValue
CategoryAUTHENTICATION (fixed body text)
Codebody and button code must match
No webhookcopy/autofill does not send an inbound message
Supported useOTP, login verification, account verification, account recovery, and security checks
Supported contentFixed authentication text with optional security disclaimer and optional code expiration warning
Code valueBody OTP and button OTP must match
ButtonsCopy code or one-tap autofill button, based on the approved template
Media / URL / emojiNot supported in authentication template content
TTLConfigurable validity period from 30 seconds to 15 minutes. Keep the TTL equal to or shorter than the actual OTP expiry time.

Authentication button types

Button typeWhat it doesImportant notes
Copy codeCopies the OTP/code to the user's clipboard. User manually pastes it into the app or website.Simplest option. Does not require a handshake or app signing hash setup.
One-tap autofillOpens the app and passes the OTP/code automatically when the user taps the button.Requires app-side setup. If eligibility checks fail, WhatsApp may fall back to copy code.
Zero-tapAttempts to deliver the OTP/code automatically without requiring the user to tap a button.Android only. Requires handshake and app signing key hash setup. If unsupported, can fall back to one-tap autofill or copy code.

Limited-time offer template

template · limited_time_offer Meta docs ↗

Limited-time offer templates are marketing templates that display a time-sensitive offer with an expiration countdown. They help create urgency by showing users when an offer, discount, coupon, or promotion will expire.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "lto_offer",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "header",
        "parameters": [
          { "type": "image", "image": { "link": "{{media_image_url}}" } }
        ]
      },
      {
        "type": "body",
        "parameters": [ { "type": "text", "text": "John" } ]
      },
      {
        "type": "limited_time_offer",
        "parameters": [
          {
            "type": "limited_time_offer",
            "limited_time_offer": { "expiration_time_ms": 1718620000000 }
          }
        ]
      },
      {
        "type": "button",
        "sub_type": "copy_code",
        "index": "0",
        "parameters": [ { "type": "coupon_code", "coupon_code": "SAVE20" } ]
      }
    ]
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "lto_offer",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "header",
        "parameters": [
          { "type": "image", "image": { "link": "{{media_image_url}}" } }
        ]
      },
      {
        "type": "body",
        "parameters": [ { "type": "text", "text": "John" } ]
      },
      {
        "type": "limited_time_offer",
        "parameters": [
          {
            "type": "limited_time_offer",
            "limited_time_offer": { "expiration_time_ms": 1718620000000 }
          }
        ]
      },
      {
        "type": "button",
        "sub_type": "copy_code",
        "index": "0",
        "parameters": [ { "type": "coupon_code", "coupon_code": "SAVE20" } ]
      }
    ]
  }
}

Fields

FieldTypeRequired?Notes
limited_time_offer.expiration_time_msintegerRequiredUnix epoch in milliseconds.
button.sub_typestringRequiredcopy_code.
button parameters[].coupon_codestringRequiredCode copied on tap.
template.namestringRequiredExact approved limited-time offer template name.
template.language.codestringRequiredApproved template language code.
components[]stringRequiredIncludes header, body, limited_time_offer, and button components as per approved template.
body.parameters[]arrayConditionalRequired when body contains variables.
button.indexstringRequiredZero-based button index.

Limits

ConstraintValue
CategoryMARKETING
Timerexpiration_time_ms must be in the future
copy_codeno inbound webhook on tap
Offer expirationRequired for every limited-time offer message
Coupon codeThe coupon code can contain up to 15 characters.
ParametersMust match the approved template variable count and order
ButtonsMust match the approved template structure
DeliverySubject to marketing template quality, pacing, and messaging limits
Countdown displayWhatsApp may display a countdown or expiration indicator based on client support

Catalog template

template · catalog Meta docs ↗

Catalog templates allow businesses to share an entire WhatsApp catalog within a template message. Users can browse available products directly inside WhatsApp and continue shopping without leaving the conversation.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "catalog_promo",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "body",
        "parameters": [ { "type": "text", "text": "John" } ]
      },
      {
        "type": "button",
        "sub_type": "catalog",
        "index": "0",
        "parameters": [
          {
            "type": "action",
            "action": { "thumbnail_product_retailer_id": "{{product_retailer_id}}" }
          }
        ]
      }
    ]
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "catalog_promo",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "body",
        "parameters": [ { "type": "text", "text": "John" } ]
      },
      {
        "type": "button",
        "sub_type": "catalog",
        "index": "0",
        "parameters": [
          {
            "type": "action",
            "action": { "thumbnail_product_retailer_id": "{{product_retailer_id}}" }
          }
        ]
      }
    ]
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "order",
                "order": {
                  "catalog_id": "{{catalog_id}}",
                  "text": "Optional note from customer",
                  "product_items": [
                    {
                      "product_retailer_id": "SKU-1",
                      "quantity": "2",
                      "item_price": "499.00",
                      "currency": "INR"
                    }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  ]
}
Catalog templates do not send product information directly in the message payload. Products are retrieved dynamically from the connected WhatsApp catalog when the user opens the catalog.

Fields

FieldTypeRequired?Notes
button.sub_typestringRequiredcatalog.
action.thumbnail_product_retailer_idstringOptionalCover product for the button.
messages[].order.product_items[]arrayWebhookproduct_retailer_id, quantity, item_price, currency.

Limits

ConstraintValue
Prerequisiteconnected Commerce catalog
Orderarrives as type:"order" in messages[]
CategoryMARKETING
Catalog buttonOne catalog button per template
Thumbnail productMust exist in the connected catalog
ProductsProducts are loaded dynamically from the connected catalog
ParametersMust match the approved template variable count and order

Single-product (SPM) template

template · spm Meta docs ↗

Single Product Message (SPM) templates promote a specific catalog product within a template message. Product information such as image, title, price, and availability is automatically loaded from the connected catalog using the product parameter, while body variables are populated dynamically at send time.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "spm_offer",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "header",
        "parameters": [
          {
            "type": "product",
            "product": {
              "product_retailer_id": "SKU-1",
              "catalog_id": "{{catalog_id}}"
            }
          }
        ]
      },
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "25OFF" },
          { "type": "text", "text": "25%" }
        ]
      }
    ]
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "spm_offer",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "header",
        "parameters": [
          {
            "type": "product",
            "product": {
              "product_retailer_id": "SKU-1",
              "catalog_id": "{{catalog_id}}"
            }
          }
        ]
      },
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "25OFF" },
          { "type": "text", "text": "25%" }
        ]
      }
    ]
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "order",
                "order": {
                  "catalog_id": "{{catalog_id}}",
                  "product_items": [
                    { "product_retailer_id": "SKU-1", "quantity": "1", "item_price": "499.00", "currency": "INR" }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
header parameters[].typestringRequired"product".
product.product_retailer_idstringRequiredSKU shown in the card.
product.catalog_idstringRequiredConnected catalog id.
body parameters[]arrayIf body varsNumbered, in order — no parameter_name.
order.product_items[]arrayWebhookReturned on cart submit.

Limits

ConstraintValue
Headermust be the product parameter
Bodynumbered parameters, in order
Webhooktype:"order"
CategoryMARKETING
Product sourceProduct must exist in the connected catalog
Product countExactly one catalog product per message
Catalog ownershipCatalog must belong to or be shared with the sending WhatsApp Business Account

Multi-product template

template · mpm Meta docs ↗

Multi-Product Message (MPM) templates allow businesses to showcase multiple catalog products grouped into one or more sections. Users can browse products, add items to their cart, and submit an order directly within WhatsApp.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "mpm_template",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "header",
        "parameters": [ { "type": "text", "text": "Bestsellers" } ]
      },
      {
        "type": "body",
        "parameters": [ { "type": "text", "text": "John" } ]
      },
      {
        "type": "button",
        "sub_type": "mpm",
        "index": "0",
        "parameters": [
          {
            "type": "action",
            "action": {
              "thumbnail_product_retailer_id": "SKU-1",
              "sections": [
                {
                  "title": "Featured",
                  "product_items": [
                    { "product_retailer_id": "SKU-1" },
                    { "product_retailer_id": "SKU-2" }
                  ]
                }
              ]
            }
          }
        ]
      }
    ]
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "mpm_template",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "header",
        "parameters": [ { "type": "text", "text": "Bestsellers" } ]
      },
      {
        "type": "body",
        "parameters": [ { "type": "text", "text": "John" } ]
      },
      {
        "type": "button",
        "sub_type": "mpm",
        "index": "0",
        "parameters": [
          {
            "type": "action",
            "action": {
              "thumbnail_product_retailer_id": "SKU-1",
              "sections": [
                {
                  "title": "Featured",
                  "product_items": [
                    { "product_retailer_id": "SKU-1" },
                    { "product_retailer_id": "SKU-2" }
                  ]
                }
              ]
            }
          }
        ]
      }
    ]
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "order",
                "order": {
                  "catalog_id": "{{catalog_id}}",
                  "product_items": [
                    { "product_retailer_id": "SKU-1", "quantity": "1", "item_price": "499.00", "currency": "INR" },
                    { "product_retailer_id": "SKU-2", "quantity": "3", "item_price": "199.00", "currency": "INR" }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  ]
}
An MPM displays a selected set of catalog products organized into sections — up to 10 sections and up to 30 products in total. In contrast, a Catalog Template opens the fully connected WhatsApp catalog through a catalog button, letting users browse all available products rather than a predefined selection.

Fields

FieldTypeRequired?Notes
button.sub_typestringRequiredmpm.
action.sections[]arrayRequiredtitle + product_items[].
product_items[].product_retailer_idstringRequiredSKU from your catalog.
thumbnail_product_retailer_idstringRequiredProduct used as the message thumbnail.
sections[].titlestringRequiredSection title displayed to the user.

Limits

ConstraintValue
Sectionsup to 10, up to 30 products total
Orderarrives as type:"order"
CategoryMARKETING
ProductsUp to 30 products across all sections
Thumbnail productMust exist in the connected catalog
Catalog ownershipCatalog must belong to or be shared with the sending WhatsApp Business Account
Product sourceAll products must exist in the connected catalog
Order webhookCart submissions return an order webhook event

Flow template

template · flow Meta docs ↗

Flow templates launch a WhatsApp Flow from a template button. Flows are used for multi-step forms and structured journeys such as appointment booking, lead capture, registration, surveys, feedback, and service requests. When the user completes the Flow, the submitted answers are received through an nfm_reply webhook in response_json.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "appointment_flow",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "body",
        "parameters": [ { "type": "text", "text": "John" } ]
      },
      {
        "type": "button",
        "sub_type": "flow",
        "index": "0",
        "parameters": [
          {
            "type": "action",
            "action": {
              "flow_token": "{{flow_token}}",
              "flow_action_data": {}
            }
          }
        ]
      }
    ]
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "appointment_flow",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "body",
        "parameters": [ { "type": "text", "text": "John" } ]
      },
      {
        "type": "button",
        "sub_type": "flow",
        "index": "0",
        "parameters": [
          {
            "type": "action",
            "action": {
              "flow_token": "{{flow_token}}",
              "flow_action_data": {}
            }
          }
        ]
      }
    ]
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "interactive",
                "context": { "from": "{{business_phone}}", "id": "{{wamid_of_template}}" },
                "interactive": {
                  "type": "nfm_reply",
                  "nfm_reply": {
                    "name": "flow",
                    "body": "Sent",
                    "response_json": "{\"flow_token\":\"{{flow_token}}\",\"appointment_date\":\"2026-07-01\",\"slot\":\"10:00\"}"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
button.sub_typestringRequiredflow.
action.flow_tokenstringRequiredYour correlation token, echoed back.
nfm_reply.response_jsonstring (JSON)WebhookJSON-encoded string; parse server-side. Keys depend on the flow.

Limits

ConstraintValue
Flow statusFlow must be created, configured, and available for use before sending.
Button typeTemplate button must use Flow action/subtype.
Flow tokenUse flow_token to connect the submitted response with the correct session or request.
Response formatresponse_json is returned as a JSON-encoded string and should be parsed safely server-side.
Response bodyWebhook body text may show a generic value such as Sent; actual user answers are inside response_json.
Unsupported clientsFlow messages may not work on unsupported WhatsApp versions.

Checkout button (Review and pay)

template · order_detailsMeta docs ↗

Sends the approved checkout template with the full order_details action — header asset, body variables, shipping info and the itemized order. The customer taps Review and pay; the result returns on the payment webhook. (shipping_info is required for physical-goods; omit it for digital-goods.)

Two payment methods — set payment_settings[].type to payment_gateway (native in-WhatsApp checkout via a gateway configured in WhatsApp Manager) or payment_link (the customer taps through to a hosted payment URL you supply). The rest of the payload is identical — same cURL, just swap the payment_settings block (see the gateway vs payment-link tabs). Exact provider fields follow Meta’s payments spec for your region.
Catalog or custom products — each order.items[] entry can reference a product from your connected catalog via retailer_id (name & image come from the catalog) or be a custom, ad-hoc product defined inline with name + amount. You can mix both in one order; every item still needs amount and quantity.
# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "order_checkout",
    "language": {
      "policy": "deterministic",
      "code": "en_US"
    },
    "components": [
      {
        "type": "header",
        "parameters": [
          {
            "type": "image",
            "image": {
              "id": "{{header_image_id}}"
            }
          }
        ]
      },
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "John"
          },
          {
            "type": "text",
            "text": "#A1234"
          }
        ]
      },
      {
        "type": "button",
        "sub_type": "order_details",
        "index": 0,
        "parameters": [
          {
            "type": "action",
            "action": {
              "order_details": {
                "reference_id": "{{order_reference_id}}",
                "currency": "INR",
                "type": "physical-goods",
                "payment_settings": [
                  {
                    "type": "payment_gateway",
                    "payment_gateway": {
                      "type": "razorpay",
                      "configuration_name": "{{payment_config}}"
                    }
                  }
                ],
                "shipping_info": {
                  "country": "IN",
                  "addresses": [
                    {
                      "name": "John Doe",
                      "phone_number": "{{recipient_phone}}",
                      "address": "12 MG Road",
                      "city": "Raipur",
                      "state": "Chhattisgarh",
                      "in_pin_code": "492001",
                      "house_number": "12",
                      "building_name": "Vertex Tower"
                    }
                  ]
                },
                "order": {
                  "items": [
                    {
                      "retailer_id": "SKU-EARBUDS",
                      "name": "Wireless Earbuds",
                      "amount": {
                        "offset": 100,
                        "value": 49900
                      },
                      "sale_amount": {
                        "offset": 100,
                        "value": 44900
                      },
                      "quantity": 1,
                      "country_of_origin": "IN"
                    }
                  ],
                  "subtotal": {
                    "offset": 100,
                    "value": 44900
                  },
                  "shipping": {
                    "offset": 100,
                    "value": 4900
                  },
                  "tax": {
                    "offset": 100,
                    "value": 8964,
                    "description": "GST 18%"
                  },
                  "discount": {
                    "offset": 100,
                    "value": 5000,
                    "description": "FIRST10"
                  },
                  "status": "pending",
                  "expiration": {
                    "timestamp": "1735660800"
                  }
                },
                "total_amount": {
                  "offset": 100,
                  "value": 53764
                }
              }
            }
          }
        ]
      }
    ]
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "order_checkout",
    "language": {
      "policy": "deterministic",
      "code": "en_US"
    },
    "components": [
      {
        "type": "header",
        "parameters": [
          {
            "type": "image",
            "image": {
              "id": "{{header_image_id}}"
            }
          }
        ]
      },
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "John"
          },
          {
            "type": "text",
            "text": "#A1234"
          }
        ]
      },
      {
        "type": "button",
        "sub_type": "order_details",
        "index": 0,
        "parameters": [
          {
            "type": "action",
            "action": {
              "order_details": {
                "reference_id": "{{order_reference_id}}",
                "currency": "INR",
                "type": "physical-goods",
                "payment_settings": [
                  {
                    "type": "payment_gateway",
                    "payment_gateway": {
                      "type": "razorpay",
                      "configuration_name": "{{payment_config}}"
                    }
                  }
                ],
                "shipping_info": {
                  "country": "IN",
                  "addresses": [
                    {
                      "name": "John Doe",
                      "phone_number": "{{recipient_phone}}",
                      "address": "12 MG Road",
                      "city": "Raipur",
                      "state": "Chhattisgarh",
                      "in_pin_code": "492001",
                      "house_number": "12",
                      "building_name": "Vertex Tower"
                    }
                  ]
                },
                "order": {
                  "items": [
                    {
                      "retailer_id": "SKU-EARBUDS",
                      "name": "Wireless Earbuds",
                      "amount": {
                        "offset": 100,
                        "value": 49900
                      },
                      "sale_amount": {
                        "offset": 100,
                        "value": 44900
                      },
                      "quantity": 1,
                      "country_of_origin": "IN"
                    }
                  ],
                  "subtotal": {
                    "offset": 100,
                    "value": 44900
                  },
                  "shipping": {
                    "offset": 100,
                    "value": 4900
                  },
                  "tax": {
                    "offset": 100,
                    "value": 8964,
                    "description": "GST 18%"
                  },
                  "discount": {
                    "offset": 100,
                    "value": 5000,
                    "description": "FIRST10"
                  },
                  "status": "pending",
                  "expiration": {
                    "timestamp": "1735660800"
                  }
                },
                "total_amount": {
                  "offset": 100,
                  "value": 53764
                }
              }
            }
          }
        ]
      }
    ]
  }
}
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "order_checkout",
    "language": {
      "policy": "deterministic",
      "code": "en_US"
    },
    "components": [
      {
        "type": "header",
        "parameters": [
          {
            "type": "image",
            "image": {
              "id": "{{header_image_id}}"
            }
          }
        ]
      },
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "John"
          },
          {
            "type": "text",
            "text": "#A1234"
          }
        ]
      },
      {
        "type": "button",
        "sub_type": "order_details",
        "index": 0,
        "parameters": [
          {
            "type": "action",
            "action": {
              "order_details": {
                "reference_id": "{{order_reference_id}}",
                "currency": "INR",
                "type": "physical-goods",
                "payment_settings": [
                  {
                    "type": "payment_link",
                    "payment_link": {
                      "uri": "{{payment_link_url}}"
                    }
                  }
                ],
                "shipping_info": {
                  "country": "IN",
                  "addresses": [
                    {
                      "name": "John Doe",
                      "phone_number": "{{recipient_phone}}",
                      "address": "12 MG Road",
                      "city": "Raipur",
                      "state": "Chhattisgarh",
                      "in_pin_code": "492001",
                      "house_number": "12",
                      "building_name": "Vertex Tower"
                    }
                  ]
                },
                "order": {
                  "items": [
                    {
                      "retailer_id": "SKU-EARBUDS",
                      "name": "Wireless Earbuds",
                      "amount": {
                        "offset": 100,
                        "value": 49900
                      },
                      "sale_amount": {
                        "offset": 100,
                        "value": 44900
                      },
                      "quantity": 1,
                      "country_of_origin": "IN"
                    }
                  ],
                  "subtotal": {
                    "offset": 100,
                    "value": 44900
                  },
                  "shipping": {
                    "offset": 100,
                    "value": 4900
                  },
                  "tax": {
                    "offset": 100,
                    "value": 8964,
                    "description": "GST 18%"
                  },
                  "discount": {
                    "offset": 100,
                    "value": 5000,
                    "description": "FIRST10"
                  },
                  "status": "pending",
                  "expiration": {
                    "timestamp": "1735660800"
                  }
                },
                "total_amount": {
                  "offset": 100,
                  "value": 53764
                }
              }
            }
          }
        ]
      }
    ]
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "statuses": [
              {
                "id": "{{wamid}}",
                "recipient_id": "{{recipient_phone}}",
                "status": "sent",
                "type": "payment",
                "payment": {
                  "reference_id": "{{order_reference_id}}",
                  "currency": "INR",
                  "amount": {
                    "offset": 100,
                    "value": 53764
                  },
                  "transaction": {
                    "id": "txn_abc123",
                    "type": "razorpay",
                    "status": "captured"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
button.sub_typestringRequired"order_details".
payment_settings[].typestringRequired"payment_gateway" or "payment_link".
payment_gatewayobjectConditional{ type, configuration_name } — when type = payment_gateway.
payment_link.uristringConditionalHosted payment URL — when type = payment_link.
order.items[].retailer_idstringConditionalLinks a line to a catalog product. Omit for custom items.
order.items[].namestringConditionalRequired for custom items; optional for catalog items.
order_details.typestringRequired"physical-goods" or "digital-goods".
shipping_infoobjectConditionalRequired for physical-goods; addresses[] may be empty if unknown.
total_amountobjectRequired{ offset, value } in minor units (offset 100 = paise/cents).
statuses[].paymentobjectWebhookPayment result with transaction id & status.

Limits

ConstraintValue
Payment methodspayment_gateway (native checkout) or payment_link (hosted URL)
Productscatalog (retailer_id) or custom (name + amount), or a mix
Amountsminor units — value with offset (100 = two decimals)
Total ruletotal_amount = subtotal + shipping + tax − discount
reference_idunique per order; matches the webhook
shipping_inforequired when type = physical-goods
Resultcaptured/failed payment arrives on the payment webhook

Order details (catalog vs custom)

template · order_detailsMeta docs ↗

Sends the itemized order with the Review and pay button. The two request tabs below are identical except for order.items[] — one uses catalog products (retailer_id), the other custom products (name). The Payment link tab shows the same call with payment_settings swapped.

Two payment methods — set payment_settings[].type to payment_gateway (native in-WhatsApp checkout via a gateway configured in WhatsApp Manager) or payment_link (the customer taps through to a hosted payment URL you supply). Only the payment_settings block changes — see the Payment link tab.
Catalog vs custom products — the two examples below are identical except for order.items[]. A catalog item uses retailer_id (name & image come from your connected catalog); a custom item is defined inline with name. Both still send amount + quantity, and you may mix the two in one order.
# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "order_details",
    "language": {
      "policy": "deterministic",
      "code": "en_US"
    },
    "components": [
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "John"
          },
          {
            "type": "text",
            "text": "#A1234"
          }
        ]
      },
      {
        "type": "button",
        "sub_type": "order_details",
        "index": 0,
        "parameters": [
          {
            "type": "action",
            "action": {
              "order_details": {
                "reference_id": "{{order_reference_id}}",
                "currency": "INR",
                "type": "digital-goods",
                "payment_settings": [
                  {
                    "type": "payment_gateway",
                    "payment_gateway": {
                      "type": "razorpay",
                      "configuration_name": "{{payment_config}}"
                    }
                  }
                ],
                "order": {
                  "status": "pending",
                  "items": [
                    {
                      "retailer_id": "SKU-EARBUDS",
                      "amount": {
                        "offset": 100,
                        "value": 49900
                      },
                      "quantity": 1
                    },
                    {
                      "retailer_id": "SKU-CASE",
                      "amount": {
                        "offset": 100,
                        "value": 19900
                      },
                      "quantity": 1
                    }
                  ],
                  "subtotal": {
                    "offset": 100,
                    "value": 69800
                  }
                },
                "total_amount": {
                  "offset": 100,
                  "value": 69800
                }
              }
            }
          }
        ]
      }
    ]
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "order_details",
    "language": {
      "policy": "deterministic",
      "code": "en_US"
    },
    "components": [
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "John"
          },
          {
            "type": "text",
            "text": "#A1234"
          }
        ]
      },
      {
        "type": "button",
        "sub_type": "order_details",
        "index": 0,
        "parameters": [
          {
            "type": "action",
            "action": {
              "order_details": {
                "reference_id": "{{order_reference_id}}",
                "currency": "INR",
                "type": "digital-goods",
                "payment_settings": [
                  {
                    "type": "payment_gateway",
                    "payment_gateway": {
                      "type": "razorpay",
                      "configuration_name": "{{payment_config}}"
                    }
                  }
                ],
                "order": {
                  "status": "pending",
                  "items": [
                    {
                      "retailer_id": "SKU-EARBUDS",
                      "amount": {
                        "offset": 100,
                        "value": 49900
                      },
                      "quantity": 1
                    },
                    {
                      "retailer_id": "SKU-CASE",
                      "amount": {
                        "offset": 100,
                        "value": 19900
                      },
                      "quantity": 1
                    }
                  ],
                  "subtotal": {
                    "offset": 100,
                    "value": 69800
                  }
                },
                "total_amount": {
                  "offset": 100,
                  "value": 69800
                }
              }
            }
          }
        ]
      }
    ]
  }
}
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "order_details",
    "language": {
      "policy": "deterministic",
      "code": "en_US"
    },
    "components": [
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "John"
          },
          {
            "type": "text",
            "text": "#A1234"
          }
        ]
      },
      {
        "type": "button",
        "sub_type": "order_details",
        "index": 0,
        "parameters": [
          {
            "type": "action",
            "action": {
              "order_details": {
                "reference_id": "{{order_reference_id}}",
                "currency": "INR",
                "type": "digital-goods",
                "payment_settings": [
                  {
                    "type": "payment_gateway",
                    "payment_gateway": {
                      "type": "razorpay",
                      "configuration_name": "{{payment_config}}"
                    }
                  }
                ],
                "order": {
                  "status": "pending",
                  "items": [
                    {
                      "name": "Wireless Earbuds",
                      "amount": {
                        "offset": 100,
                        "value": 49900
                      },
                      "quantity": 1
                    },
                    {
                      "name": "Charging Case",
                      "amount": {
                        "offset": 100,
                        "value": 19900
                      },
                      "quantity": 1
                    }
                  ],
                  "subtotal": {
                    "offset": 100,
                    "value": 69800
                  }
                },
                "total_amount": {
                  "offset": 100,
                  "value": 69800
                }
              }
            }
          }
        ]
      }
    ]
  }
}
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "order_details",
    "language": {
      "policy": "deterministic",
      "code": "en_US"
    },
    "components": [
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "John"
          },
          {
            "type": "text",
            "text": "#A1234"
          }
        ]
      },
      {
        "type": "button",
        "sub_type": "order_details",
        "index": 0,
        "parameters": [
          {
            "type": "action",
            "action": {
              "order_details": {
                "reference_id": "{{order_reference_id}}",
                "currency": "INR",
                "type": "digital-goods",
                "payment_settings": [
                  {
                    "type": "payment_link",
                    "payment_link": {
                      "uri": "{{payment_link_url}}"
                    }
                  }
                ],
                "order": {
                  "status": "pending",
                  "items": [
                    {
                      "retailer_id": "SKU-EARBUDS",
                      "amount": {
                        "offset": 100,
                        "value": 49900
                      },
                      "quantity": 1
                    },
                    {
                      "retailer_id": "SKU-CASE",
                      "amount": {
                        "offset": 100,
                        "value": 19900
                      },
                      "quantity": 1
                    }
                  ],
                  "subtotal": {
                    "offset": 100,
                    "value": 69800
                  }
                },
                "total_amount": {
                  "offset": 100,
                  "value": 69800
                }
              }
            }
          }
        ]
      }
    ]
  }
}

Fields

FieldTypeRequired?Notes
order.items[].retailer_idstringConditionalCatalog product reference — name & image come from the catalog. Omit for custom items.
order.items[].namestringConditionalRequired for custom items; optional override for catalog items.
order.items[].amountobjectRequired{ offset, value } per line, minor units (offset 100 = paise/cents).
order.items[].quantitynumberRequiredUnits for this line.
payment_settings[].typestringRequired"payment_gateway" or "payment_link".
payment_link.uristringConditionalHosted payment URL — when type = payment_link.
order.subtotalobjectRequiredSum of item amounts, in minor units.
total_amountobjectRequiredGrand total = subtotal + tax + shipping − discount.

Limits

ConstraintValue
Productscatalog (retailer_id) or custom (name + amount), or a mix
Payment methodspayment_gateway (native checkout) or payment_link (hosted URL)
Catalog requirementretailer_id must exist in the catalog connected to the WABA
Amountsall in minor units (value + offset)
Goods typedigital-goods omits shipping_info & shipping

Order status update

template · order_statusMeta docs ↗

Pushes a status update for a previously sent order, referenced by reference_id. The body shows the human-readable message while the order_status action updates the machine-readable state on the existing order card.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "order_status_update",
    "language": {
      "policy": "deterministic",
      "code": "en_US"
    },
    "components": [
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "#A1234"
          },
          {
            "type": "text",
            "text": "shipped"
          },
          {
            "type": "text",
            "text": "Track: https://track.example.com/A1234"
          }
        ]
      },
      {
        "type": "button",
        "sub_type": "order_status",
        "index": 0,
        "parameters": [
          {
            "type": "action",
            "action": {
              "order_status": {
                "reference_id": "{{order_reference_id}}",
                "order": {
                  "status": "shipped",
                  "description": "Out for delivery, arriving today"
                }
              }
            }
          }
        ]
      }
    ]
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "template",
  "template": {
    "name": "order_status_update",
    "language": {
      "policy": "deterministic",
      "code": "en_US"
    },
    "components": [
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "#A1234"
          },
          {
            "type": "text",
            "text": "shipped"
          },
          {
            "type": "text",
            "text": "Track: https://track.example.com/A1234"
          }
        ]
      },
      {
        "type": "button",
        "sub_type": "order_status",
        "index": 0,
        "parameters": [
          {
            "type": "action",
            "action": {
              "order_status": {
                "reference_id": "{{order_reference_id}}",
                "order": {
                  "status": "shipped",
                  "description": "Out for delivery, arriving today"
                }
              }
            }
          }
        ]
      }
    ]
  }
}

Fields

FieldTypeRequired?Notes
button.sub_typestringRequired"order_status".
order_status.reference_idstringRequiredMust match the order sent earlier.
order.statusstringRequiredpending / processing / partially_shipped / shipped / completed / canceled.
order.descriptionstringOptionalShort human-readable status note.
body.parameters[]arrayConditionalFill the template body variables.

Limits

ConstraintValue
reference_idmust match a previously sent order
Status valuespending → processing → partially_shipped → shipped → completed (or canceled)
Effectupdates the status shown on the existing order card
Windowsendable anytime as an approved template
05
Service Messages
Free-form replies inside the 24-hour window: standard, interactive & commerce.
Service messages are free-form messages sent within an active customer service conversation. Outside the customer service window, businesses must use approved template messages.
Common envelope — present in every service-message payload below; the per-type tables only list the type-specific object.
FieldTypeRequired?Notes
messaging_productstringRequiredAlways "whatsapp".
recipient_typestringOptional"individual" (default).
tostringRequiredRecipient phone (E.164, with country code).
typestringRequiredMessage type, e.g. text / image / interactive …
RequiredOptionalConditionalWebhook — Conditional = required only in certain cases (noted).
Standard

Text

text Meta docs ↗

Text Messages are free-form service messages used to send plain text content within an active customer service conversation. Link previews can be enabled for supported URLs using the preview_url parameter.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "text",
  "text": {
    "preview_url": true,
    "body": "As requested, here is the link to our latest product: https://www.example.com/product"
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "text",
  "text": {
    "preview_url": true,
    "body": "As requested, here is the link to our latest product: https://www.example.com/product"
  }
}

Fields

FieldTypeRequired?Notes
text.bodystringRequiredThe message text. Max 4096 chars.
text.preview_urlbooleanOptionaltrue = render a preview of the first URL in body.

Limits

ConstraintValue
bodymax 4096 characters
preview_urlpreviews first URL only, when true
URL schememust start with http:// or https://

Audio

audio Meta docs ↗

Audio messages send an audio file within an active customer service conversation. Audio can be sent using a public media URL. Captions are not supported for audio messages.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "audio",
  "audio": {
    "link": "{{media_audio_url}}"
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "audio",
  "audio": {
    "link": "{{media_audio_url}}"
  }
}

Fields

FieldTypeRequired?Notes
audio.linkstring (URL)RequiredPublic URL of the audio file. Never a media id.

Limits

ConstraintValue
Max file size16 MB
FormatsAAC, AMR, MP3, M4A, OGG (OPUS only, mono)
CaptionNot supported

Image

image Meta docs ↗

Image messages send images within an active customer service conversation. Images can be sent using a public media URL and may include an optional caption.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "image",
  "image": {
    "link": "{{media_image_url}}",
    "caption": "Optional caption text"
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "image",
  "image": {
    "link": "{{media_image_url}}",
    "caption": "Optional caption text"
  }
}

Fields

FieldTypeRequired?Notes
image.linkstring (URL)RequiredPublic URL of the image. Never a media id.
image.captionstringOptionalCaption shown under the image. Max 1024 chars.

Limits

ConstraintValue
Max file size5 MB
FormatsJPEG, PNG
captionmax 1024 characters

Video

video Meta docs ↗

Video messages send a video file within an active customer service conversation. Videos are delivered from a public URL and may include an optional caption.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "video",
  "video": {
    "link": "{{media_video_url}}",
    "caption": "Optional caption text"
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "video",
  "video": {
    "link": "{{media_video_url}}",
    "caption": "Optional caption text"
  }
}

Fields

FieldTypeRequired?Notes
video.linkstring (URL)RequiredPublic URL of the video. Never a media id.
video.captionstringOptionalCaption shown under the video. Max 1024 chars.

Limits

ConstraintValue
Max file size16 MB
FormatsMP4, 3GPP — H.264 video + AAC audio only
captionmax 1024 characters

Document

document Meta docs ↗

Document messages send files such as PDFs, Office documents, text files, or other supported documents within an active customer service conversation. The message displays a document icon and filename that the WhatsApp user can tap to download. Documents are delivered from a public URL and can include an optional caption.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "document",
  "document": {
    "link": "{{media_document_url}}",
    "caption": "Optional caption text",
    "filename": "document.pdf"
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "document",
  "document": {
    "link": "{{media_document_url}}",
    "caption": "Optional caption text",
    "filename": "document.pdf"
  }
}

Fields

FieldTypeRequired?Notes
document.linkstring (URL)RequiredPublic URL of the document. Never a media id.
document.captionstringOptionalCaption shown with the document. Max 1024 chars.
document.filenamestringOptionalDisplay filename; extension drives the file-type icon.

Limits

ConstraintValue
Max file size100 MB
captionmax 1024 characters
filenamedisplay name; extension drives the file-type icon

Sticker

sticker Meta docs ↗

Sticker messages send static or animated WebP stickers within an active customer service conversation. Stickers are delivered from a public URL and do not support captions.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "sticker",
  "sticker": {
    "link": "{{media_sticker_url}}"
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "sticker",
  "sticker": {
    "link": "{{media_sticker_url}}"
  }
}

Fields

FieldTypeRequired?Notes
sticker.linkstring (URL)RequiredPublic URL of the .webp sticker. Never a media id.

Limits

ConstraintValue
FormatWebP only
Staticmax 100 KB
Animatedmax 500 KB
CaptionNot supported

Contacts

contacts Meta docs ↗

Contact messages share one or more contact cards within a WhatsApp conversation. Each contact can include names, phone numbers, email addresses, company information, websites, addresses, and other supported contact details.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "contacts",
  "contacts": [
    {
      "name": {
        "formatted_name": "John Doe",
        "first_name": "John",
        "last_name": "Doe"
      },
      "phones": [
        { "phone": "+16505551234", "type": "WORK", "wa_id": "16505551234" }
      ],
      "emails": [
        { "email": "john@example.com", "type": "WORK" }
      ],
      "org": { "company": "Example Inc", "department": "Sales", "title": "Manager" },
      "urls": [
        { "url": "https://www.example.com", "type": "WORK" }
      ],
      "addresses": [
        {
          "street": "1 Hacker Way",
          "city": "Menlo Park",
          "state": "CA",
          "zip": "94025",
          "country": "United States",
          "country_code": "us",
          "type": "WORK"
        }
      ],
      "birthday": "1990-01-15"
    }
  ]
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "contacts",
  "contacts": [
    {
      "name": {
        "formatted_name": "John Doe",
        "first_name": "John",
        "last_name": "Doe"
      },
      "phones": [
        { "phone": "+16505551234", "type": "WORK", "wa_id": "16505551234" }
      ],
      "emails": [
        { "email": "john@example.com", "type": "WORK" }
      ],
      "org": { "company": "Example Inc", "department": "Sales", "title": "Manager" },
      "urls": [
        { "url": "https://www.example.com", "type": "WORK" }
      ],
      "addresses": [
        {
          "street": "1 Hacker Way",
          "city": "Menlo Park",
          "state": "CA",
          "zip": "94025",
          "country": "United States",
          "country_code": "us",
          "type": "WORK"
        }
      ],
      "birthday": "1990-01-15"
    }
  ]
}

Fields

FieldTypeRequired?Notes
contactsarrayRequiredArray of contact objects.
contacts[].nameobjectRequiredName object.
name.formatted_namestringRequiredFull display name.
name.first_name / last_namestringConditionalAt least one required besides formatted_name.
contacts[].phonesarrayOptionalphone, type (HOME/WORK), wa_id.
contacts[].emailsarrayOptionalemail, type.
contacts[].orgobjectOptionalcompany, department, title.
contacts[].urlsarrayOptionalurl, type.
contacts[].addressesarrayOptionalstreet, city, state, zip, country, country_code, type.
contacts[].birthdaystringOptionalYYYY-MM-DD.

Limits

ConstraintValue
nameformatted_name + at least 1 other name field
type fieldsHOME or WORK
birthdayYYYY-MM-DD
wa_idmakes the phone tappable to open a chat

Location

location Meta docs ↗

Location messages send a map pin to the user using latitude and longitude. An optional name and address can be included to label the pin and make the location easier to understand.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "location",
  "location": {
    "latitude": "21.1938",
    "longitude": "81.3509",
    "name": "Atmik Bharat Industries",
    "address": "Bhilai, Chhattisgarh, India"
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "location",
  "location": {
    "latitude": "21.1938",
    "longitude": "81.3509",
    "name": "Atmik Bharat Industries",
    "address": "Bhilai, Chhattisgarh, India"
  }
}

Fields

FieldTypeRequired?Notes
location.latitudestring/numberRequiredLatitude.
location.longitudestring/numberRequiredLongitude.
location.namestringOptionalLabel for the pin.
location.addressstringOptionalAddress under the name.

Limits

ConstraintValue
latitude / longituderequired
name + addressoptional — set together so the pin shows a label

Reaction

reaction Meta docs ↗

Reaction messages let you react to a previously sent WhatsApp message using a single emoji.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "reaction",
  "reaction": {
    "message_id": "{{wamid_to_react_to}}",
    "emoji": "\ud83d\udc4d"
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "reaction",
  "reaction": {
    "message_id": "{{wamid_to_react_to}}",
    "emoji": "\ud83d\udc4d"
  }
}

Fields

FieldTypeRequired?Notes
reaction.message_idstring (wamid)Requiredwamid being reacted to.
reaction.emojistringRequiredSingle emoji, or "" to remove.

Limits

ConstraintValue
message_idvalid wamid, 30 days old max, not deleted
emojisingle emoji; "" removes the reaction
Interactive

Reply buttons

interactive · button Meta docs ↗

Reply Button Messages are an interactive WhatsApp message type, not a template message. They show up to 3 quick-reply buttons below a message body, with optional header and footer. When the user taps a button, a button_reply webhook is returned.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "button",
    "header": {
      "type": "image",
      "image": { "link": "{{media_image_url}}" }
    },
    "body": { "text": "Pick an option below." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "buttons": [
        { "type": "reply", "reply": { "id": "btn-1", "title": "Yes" } },
        { "type": "reply", "reply": { "id": "btn-2", "title": "No" } },
        { "type": "reply", "reply": { "id": "btn-3", "title": "Maybe" } }
      ]
    }
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "button",
    "header": {
      "type": "image",
      "image": { "link": "{{media_image_url}}" }
    },
    "body": { "text": "Pick an option below." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "buttons": [
        { "type": "reply", "reply": { "id": "btn-1", "title": "Yes" } },
        { "type": "reply", "reply": { "id": "btn-2", "title": "No" } },
        { "type": "reply", "reply": { "id": "btn-3", "title": "Maybe" } }
      ]
    }
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "interactive",
                "context": { "from": "{{business_phone}}", "id": "{{wamid_of_message}}" },
                "interactive": {
                  "type": "button_reply",
                  "button_reply": { "id": "btn-1", "title": "Yes" }
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
interactive.typestringRequiredMust be "button".
interactive.body.textstringRequiredBody text. Max 1024.
action.buttonsarrayRequired1–3 buttons.
buttons[].reply.idstringRequiredUnique payload id. Max 256.
buttons[].reply.titlestringRequiredLabel. Max 20.
interactive.button_reply.idstringWebhookThe id of the tapped button.
interactive.headerobjectOptionalOptional header above the body. Can be text or supported media type.
interactive.footer.textstringOptionalFooter text shown below the body. Maximum 60 characters.

Limits

ConstraintValue
Buttons1 to 3
Button titlemax 20 characters
Button idmax 256 characters, unique per message
body.textmax 1024 characters
footer.textmax 60 characters
Webhookinteractive.type = button_reply
Message typeinteractive
Interactive typebutton

List

interactive · list Meta docs ↗

List Messages are an interactive WhatsApp message type that displays a button, not a template message. They present multiple options in a structured menu where the user taps a button to open the list and selects one option. Useful for service selection, support menus, appointment types, locations, product categories, and guided workflows. Selecting a row returns a list_reply webhook.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "list",
    "header": { "type": "text", "text": "Our Menu" },
    "body": { "text": "Choose an item from the list." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "button": "View options",
      "sections": [
        {
          "title": "Channels",
          "rows": [
            { "id": "row-wa", "title": "WhatsApp", "description": "WABA messaging & calling" },
            { "id": "row-ig", "title": "Instagram", "description": "DM & comment management" }
          ]
        },
        {
          "title": "Tools",
          "rows": [
            { "id": "row-flow", "title": "Workflows", "description": "No-code automation" }
          ]
        }
      ]
    }
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "list",
    "header": { "type": "text", "text": "Our Menu" },
    "body": { "text": "Choose an item from the list." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "button": "View options",
      "sections": [
        {
          "title": "Channels",
          "rows": [
            { "id": "row-wa", "title": "WhatsApp", "description": "WABA messaging & calling" },
            { "id": "row-ig", "title": "Instagram", "description": "DM & comment management" }
          ]
        },
        {
          "title": "Tools",
          "rows": [
            { "id": "row-flow", "title": "Workflows", "description": "No-code automation" }
          ]
        }
      ]
    }
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "interactive",
                "context": { "from": "{{business_phone}}", "id": "{{wamid_of_message}}" },
                "interactive": {
                  "type": "list_reply",
                  "list_reply": {
                    "id": "row-wa",
                    "title": "WhatsApp",
                    "description": "WABA messaging & calling"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  ]
}
List Messages allow only a single selection. If users need to choose multiple options, consider a WhatsApp Flow instead.
Example: ✅ 2 sections × 5 rows = valid · ✅ 1 section × 10 rows = valid · ❌ 3 sections × 5 rows = 15 rows = invalid.

Fields

FieldTypeRequired?Notes
interactive.typestringRequiredMust be "list".
interactive.body.textstringRequiredBody text. Max 4096.
action.buttonstringRequiredOpener label. Max 20.
action.sectionsarrayRequired1–10 sections.
sections[].titlestringConditionalRequired if >1 section. Max 24.
rows[].idstringRequiredUnique. Max 200.
interactive.list_reply.idstringWebhookThe id of the selected row.
headerobjectOptionalHeader displayed above the message body.
footer.textstringOptionalAdditional supporting text displayed below the list.
action.sections[]arrayRequiredOne or more sections containing list options.
rows[].titlestringRequiredOption title displayed to the user.
rows[].descriptionstringOptionalAdditional information shown below the row title.

Limits

ConstraintValue
Total rowsmax 10 across all sections
Sectionsmax 10
Row titlemax 24
Row descriptionmax 72
Button labelmax 20
Webhookinteractive.type = list_reply
Message typeInteractive, List
Section titleUp to 24 characters
Button textUp to 20 characters
SelectionUser can select only one row at a time
ResponseSelected row ID is returned through the webhook

URL button (CTA URL)

interactive · cta_url Meta docs ↗

CTA URL Messages are an interactive WhatsApp message type that display a single tappable button. When tapped, the button opens the configured URL in the user's browser. Opening the URL does not generate an inbound webhook.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "cta_url",
    "header": { "type": "text", "text": "Special offer" },
    "body": { "text": "Tap below to view the offer on our website." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "name": "cta_url",
      "parameters": {
        "display_text": "Visit website",
        "url": "https://www.example.com/offer"
      }
    }
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "cta_url",
    "header": { "type": "text", "text": "Special offer" },
    "body": { "text": "Tap below to view the offer on our website." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "name": "cta_url",
      "parameters": {
        "display_text": "Visit website",
        "url": "https://www.example.com/offer"
      }
    }
  }
}

Fields

FieldTypeRequired?Notes
interactive.typestringRequiredMust be "cta_url".
interactive.body.textstringRequiredMax 1024.
action.namestringRequiredMust be "cta_url".
action.parameters.display_textstringRequiredButton label.
action.parameters.urlstring (URL)RequiredDestination URL.
interactive.headerobjectOptionalOptional header above the body.
interactive.footer.textstringOptionalFooter text. Maximum 60 characters.

Limits

ConstraintValue
display_textaround 20 characters
body.textmax 1024
Webhooknone — opening a URL is not reported
Message typeinteractive
Footer textMaximum 60 characters
URLMust be a valid HTTP/HTTPS URL

Flow

interactive · flow Meta docs ↗

Launches a multi-screen Flow form via a CTA button inside the 24-hour window. On completion you receive an nfm_reply webhook with answers in response_json.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "flow",
    "header": { "type": "text", "text": "Book appointment" },
    "body": { "text": "Tap below to pick a slot." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "name": "flow",
      "parameters": {
        "flow_message_version": "3",
        "flow_token": "{{flow_token}}",
        "flow_id": "{{flow_id}}",
        "flow_cta": "Book now",
        "flow_action": "navigate",
        "flow_action_payload": {
          "screen": "APPOINTMENT",
          "data": {}
        }
      }
    }
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "flow",
    "header": { "type": "text", "text": "Book appointment" },
    "body": { "text": "Tap below to pick a slot." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "name": "flow",
      "parameters": {
        "flow_message_version": "3",
        "flow_token": "{{flow_token}}",
        "flow_id": "{{flow_id}}",
        "flow_cta": "Book now",
        "flow_action": "navigate",
        "flow_action_payload": {
          "screen": "APPOINTMENT",
          "data": {}
        }
      }
    }
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "interactive",
                "context": { "from": "{{business_phone}}", "id": "{{wamid_of_message}}" },
                "interactive": {
                  "type": "nfm_reply",
                  "nfm_reply": {
                    "name": "flow",
                    "body": "Sent",
                    "response_json": "{\"flow_token\":\"{{flow_token}}\",\"appointment_date\":\"2026-07-01\",\"slot\":\"10:00\"}"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
interactive.typestringRequiredMust be "flow".
parameters.flow_tokenstringRequiredYour correlation token, echoed back.
parameters.flow_idstringRequiredThe published Flow id.
parameters.flow_ctastringRequiredButton label. Max 20.
parameters.flow_actionstringOptionalnavigate (default) or data_exchange.
nfm_reply.response_jsonstring (JSON)WebhookJSON-encoded string; parse server-side.

Limits

ConstraintValue
Windowsend within the 24-hour service window (or use a Flow template)
response_jsona string — JSON.parse() it in a try/catch
flow_ctamax 20 chars, no emoji

Location request

interactive · location_request_message Meta docs ↗

Prompts the user with a Send location button; the shared pin arrives as a location webhook.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "location_request_message",
    "body": { "text": "Please share your location so we can find the nearest store." },
    "action": { "name": "send_location" }
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "location_request_message",
    "body": { "text": "Please share your location so we can find the nearest store." },
    "action": { "name": "send_location" }
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "location",
                "context": { "from": "{{business_phone}}", "id": "{{wamid_of_message}}" },
                "location": {
                  "latitude": 21.1938,
                  "longitude": 81.3509,
                  "name": "Home",
                  "address": "Civic Centre Road, Bhilai"
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
interactive.typestringRequiredMust be "location_request_message".
interactive.body.textstringRequiredPrompt. Max 1024.
action.namestringRequiredMust be "send_location".
messages[].locationobjectWebhooklatitude, longitude, optional name/address.

Limits

ConstraintValue
body.textmax 1024 characters
action.namemust be send_location
Webhooktype:"location" in messages[]

Address

interactive · address_message Meta docs ↗

Native address form. Availability: India (IN) and Singapore (SG) only. The submitted form arrives as an nfm_reply webhook (name "address_message"), with the fields inside response_json.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "address_message",
    "body": { "text": "Please provide your delivery address." },
    "action": {
      "name": "address_message",
      "parameters": {
        "country": "IN",
        "values": {
          "name": "John Doe",
          "phone_number": "+919999999999",
          "in_pin_code": "490001",
          "house_number": "12",
          "floor_number": "2",
          "tower_number": "A",
          "building_name": "Sunrise Apartments",
          "address": "Civic Centre Road",
          "landmark_area": "Near City Mall",
          "city": "Bhilai",
          "state": "Chhattisgarh"
        }
      }
    }
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "address_message",
    "body": { "text": "Please provide your delivery address." },
    "action": {
      "name": "address_message",
      "parameters": {
        "country": "IN",
        "values": {
          "name": "John Doe",
          "phone_number": "+919999999999",
          "in_pin_code": "490001",
          "house_number": "12",
          "floor_number": "2",
          "tower_number": "A",
          "building_name": "Sunrise Apartments",
          "address": "Civic Centre Road",
          "landmark_area": "Near City Mall",
          "city": "Bhilai",
          "state": "Chhattisgarh"
        }
      }
    }
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "interactive",
                "context": { "from": "{{business_phone}}", "id": "{{wamid_of_message}}" },
                "interactive": {
                  "type": "nfm_reply",
                  "nfm_reply": {
                    "name": "address_message",
                    "body": "address_message",
                    "response_json": "{\"name\":\"John Doe\",\"phone_number\":\"+919999999999\",\"in_pin_code\":\"490001\",\"house_number\":\"12\",\"address\":\"Civic Centre Road\",\"city\":\"Bhilai\",\"state\":\"Chhattisgarh\"}"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
interactive.typestringRequiredMust be "address_message".
action.parameters.countrystringRequired"IN" or "SG".
action.parameters.valuesobjectOptionalPre-fill (country-specific).
action.parameters.saved_addressesarrayOptionalSaved addresses to pick from.
nfm_reply.response_jsonstring (JSON)WebhookSubmitted address fields, JSON-encoded.

Limits

ConstraintValue
AvailabilityIndia (IN) & Singapore (SG) only
IN valuesin_pin_code, house_number, floor_number, building_name, landmark_area, city, state…
SG valuessg_post_code, unit_number, floor_number…
Webhooknfm_reply.name = "address_message"
Commerce
Commerce messages require a connected Commerce Manager catalog. When a customer builds a cart from any of them, the submission arrives as a single type: "order" message in the messages[] webhook — shown on the Webhook response tab.

Catalog message

interactive · catalog_message Meta docs ↗

Shows a button that opens your entire catalog, with one product as the cover thumbnail. Cart submissions return an order webhook.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "catalog_message",
    "body": { "text": "Browse our full catalog and add items to your cart." },
    "action": {
      "name": "catalog_message",
      "parameters": {
        "thumbnail_product_retailer_id": "SKU-1"
      }
    },
    "footer": { "text": "Powered by Atmik Bharat" }
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "catalog_message",
    "body": { "text": "Browse our full catalog and add items to your cart." },
    "action": {
      "name": "catalog_message",
      "parameters": {
        "thumbnail_product_retailer_id": "SKU-1"
      }
    },
    "footer": { "text": "Powered by Atmik Bharat" }
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "order",
                "context": { "from": "{{business_phone}}", "id": "{{wamid_of_message}}" },
                "order": {
                  "catalog_id": "{{catalog_id}}",
                  "text": "Optional note from customer",
                  "product_items": [
                    { "product_retailer_id": "SKU-1", "quantity": "2", "item_price": "499.00", "currency": "INR" }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
interactive.typestringRequiredMust be "catalog_message".
interactive.body.textstringRequiredBody text. Max 1024.
action.namestringRequiredMust be "catalog_message".
parameters.thumbnail_product_retailer_idstringOptionalCover product SKU; defaults to first catalog item.
order.product_items[]arrayWebhookproduct_retailer_id, quantity, item_price, currency.

Limits

ConstraintValue
Prerequisiteconnected Commerce Manager catalog
footer.textmax 60 characters
Webhooktype:"order" — item_price/currency are catalog values

Single product (SPM)

interactive · product Meta docs ↗

Highlights one catalog product with a View / Add-to-cart action. Cart submissions return an order webhook.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "product",
    "body": { "text": "Check out this product." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "catalog_id": "{{catalog_id}}",
      "product_retailer_id": "SKU-1"
    }
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "product",
    "body": { "text": "Check out this product." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "catalog_id": "{{catalog_id}}",
      "product_retailer_id": "SKU-1"
    }
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "order",
                "context": { "from": "{{business_phone}}", "id": "{{wamid_of_message}}" },
                "order": {
                  "catalog_id": "{{catalog_id}}",
                  "product_items": [
                    { "product_retailer_id": "SKU-1", "quantity": "1", "item_price": "499.00", "currency": "INR" }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
interactive.typestringRequiredMust be "product".
action.catalog_idstringRequiredCommerce Manager catalog id.
action.product_retailer_idstringRequiredSKU (content id) of the product.
interactive.body / footerobjectOptionalOptional text around the card.
order.product_items[]arrayWebhookReturned on cart submit.

Limits

ConstraintValue
Prerequisitecatalog + product must be in stock / approved
Webhooktype:"order"

Multi product (MPM)

interactive · product_list Meta docs ↗

Shows a curated set of catalog products grouped into sections, as a single browsable list. Cart submissions return an order webhook.

# waba_id is required as a query parameter on every call
curl -X POST '{{base_url}}/{{phone_number_id}}/messages?waba_id={{waba_id}}' \
  -H 'Authorization: Bearer {{access_token}}' \
  -H 'Content-Type: application/json' \
  -d '{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "product_list",
    "header": { "type": "text", "text": "Our bestsellers" },
    "body": { "text": "Pick items to add to your cart." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "catalog_id": "{{catalog_id}}",
      "sections": [
        {
          "title": "Featured",
          "product_items": [
            { "product_retailer_id": "SKU-1" },
            { "product_retailer_id": "SKU-2" }
          ]
        },
        {
          "title": "New arrivals",
          "product_items": [
            { "product_retailer_id": "SKU-3" }
          ]
        }
      ]
    }
  }
}'
{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "{{recipient_phone}}",
  "type": "interactive",
  "interactive": {
    "type": "product_list",
    "header": { "type": "text", "text": "Our bestsellers" },
    "body": { "text": "Pick items to add to your cart." },
    "footer": { "text": "Powered by Atmik Bharat" },
    "action": {
      "catalog_id": "{{catalog_id}}",
      "sections": [
        {
          "title": "Featured",
          "product_items": [
            { "product_retailer_id": "SKU-1" },
            { "product_retailer_id": "SKU-2" }
          ]
        },
        {
          "title": "New arrivals",
          "product_items": [
            { "product_retailer_id": "SKU-3" }
          ]
        }
      ]
    }
  }
}
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "{{waba_id}}",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "{{business_phone}}",
              "phone_number_id": "{{phone_number_id}}"
            },
            "contacts": [
              { "profile": { "name": "John Doe" }, "wa_id": "{{sender_wa_id}}" }
            ],
            "messages": [
              {
                "from": "{{sender_wa_id}}",
                "id": "{{wamid}}",
                "type": "order",
                "context": { "from": "{{business_phone}}", "id": "{{wamid_of_message}}" },
                "order": {
                  "catalog_id": "{{catalog_id}}",
                  "product_items": [
                    { "product_retailer_id": "SKU-1", "quantity": "1", "item_price": "499.00", "currency": "INR" },
                    { "product_retailer_id": "SKU-3", "quantity": "2", "item_price": "199.00", "currency": "INR" }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

Fields

FieldTypeRequired?Notes
interactive.typestringRequiredMust be "product_list".
interactive.headerobjectRequiredtype:"text" header.
action.catalog_idstringRequiredCommerce Manager catalog id.
action.sections[]arrayRequiredtitle + product_items[].
product_items[].product_retailer_idstringRequiredSKU per product.
order.product_items[]arrayWebhookOnly the items the customer actually added.

Limits

ConstraintValue
Sectionsup to 10
Productsup to 30 total across sections
headertext header required
Webhooktype:"order"
On preview images & media: all media is sent by public link (never an uploaded media id) — the URL must be reachable by Meta's servers at send time. For webhooks, always read messages[].type first and branch: interactive replies live under interactive (button_reply / list_reply / nfm_reply), carts under order, shared pins under location. response_json is a JSON stringJSON.parse() it inside a try/catch.