WhatsApp

Message templates

Create WhatsApp templates, follow Meta's review, fill variables, media headers and buttons, and send a template to start a conversation.

A template is a pre-approved message. It is the only thing you can send to someone who has not messaged you in the last 24 hours, so every outbound campaign, reminder and one-time code on WhatsApp starts with one. You create the template, Meta reviews it, and once it is APPROVED you can send it as often as you like with different values filled in.

#Create a template

Send POST /v1/whatsapp/templates with the account, a name, a category, a language and the components that make up the message.

curl -X POST https://api.pozy.ai/v1/whatsapp/templates \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "6712a9d4c0e3b58f1a7d2e64",
    "name": "pickup_ready",
    "category": "UTILITY",
    "language": "en",
    "components": [
      {
        "type": "body",
        "text": "Hi {{1}}, your order {{2}} is ready for pickup until {{3}}.",
        "example": { "body_text": [["Marta", "#4821", "6pm"]] }
      },
      { "type": "footer", "text": "Reply STOP to opt out" }
    ]
  }'
Response
{
  "success": true,
  "template": {
    "id": "781203456789012",
    "name": "pickup_ready",
    "status": "PENDING",
    "category": "UTILITY",
    "language": "en"
  }
}

Rules for the request:

  • name uses lowercase letters, digits and underscores and starts with a letter.
  • Meta stores one template per name and language. The same name in three languages is three templates, each with its own id.
  • Every variable needs an example value. Reviewers reject templates they cannot read with real values.

#Pick the category

The category decides what Meta charges per delivery and how long Meta keeps trying to deliver. Pick the one that matches the content honestly; Meta reclassifies templates that are filed under the wrong one.

category Use it for Delivery window you can set Meta's default
UTILITY Order updates, receipts, reminders, account notices 30 seconds to 12 hours 30 days
MARKETING Promotions, offers, newsletters, re-engagement 12 hours to 30 days 30 days
AUTHENTICATION One-time passcodes 30 seconds to 15 minutes 10 minutes

Set the window with message_send_ttl_seconds. A message not delivered inside it is dropped rather than delivered hours afterwards. That is right for a passcode and wrong for a promotion. On create you can pass -1 to keep the 30-day default for UTILITY and AUTHENTICATION.

When Meta changes a category it sends whatsapp.template.category_updated twice: once with changeType: "scheduled" as a 24-hour notice, and again with changeType: "applied" when it takes effect. Meta clears any custom delivery window at that point, so read it back afterwards.

#Build the components

A template is a list of components. Only body is required.

Component What it holds Notes
header format is text, image, video, gif, document or location A text header can carry one variable. A media header needs an example.header_handle for review and real media at send time.
body The main text with variables add_security_recommendation adds Meta's standard warning on authentication templates.
footer Short plain text code_expiration_minutes on authentication templates.
buttons Quick replies, links, phone, code and flow buttons Types: quick_reply, url, phone_number, otp, copy_code, flow, mpm, catalog.
carousel Up to 10 cards, each with its own header, body and buttons Every card must use the same structure.
limited_time_offer Offer text (16 characters) and an optional expiry Marketing templates only.

#Variables

Variables are positional by default: {{1}}, {{2}} and so on. To use names, set parameter_format: "NAMED" and write {{customer_name}} in the text, with the examples in body_text_named_params.

Broadcasts only fill positional variables. Use positional ones if you plan to broadcast the template.

#Buttons

Buttons component
{
  "type": "buttons",
  "buttons": [
    { "type": "quick_reply", "text": "Change pickup time" },
    { "type": "url", "text": "View order", "url": "https://example.com/orders/{{1}}", "example": ["4821"] },
    { "type": "phone_number", "text": "Call the shop", "phone_number": "+351210000123" }
  ]
}

A url button can end in one variable, filled at send time. A flow button opens a WhatsApp Flow by flow_id or flow_name.

#Wait for approval

Review can take up to 24 hours. Subscribe to whatsapp.template.status_updated instead of polling. It fires whenever Meta finishes a review or re-review and carries the new status and Meta's reason ("NONE" on approval).

status Meaning Can you send it?
PENDING In review No
APPROVED Live Yes
REJECTED Declined. Fix the content and create it again, or appeal in WhatsApp Manager. No
IN_APPEAL An appeal is being reviewed No
PAUSED Meta paused it after poor recipient feedback No
DISABLED Switched off by Meta No
PENDING_DELETION Delete requested; removed after 24 hours No

To check by hand, list templates with GET /v1/whatsapp/templates and filter by status, name or language. The list is read live from Meta.

Skip the review with Meta's library

Meta keeps a library of pre-approved utility templates. Look one up with GET /v1/whatsapp/template-library, then create it with library_template_name instead of components. It comes back APPROVED immediately. If the library template has URL or phone buttons, send one library_template_button_inputs entry per button, in order, or Meta rejects it.

#Send a template

To open a conversation, call POST /v1/inbox/conversations. The recipient goes in participantId as digits with the country code and no +. templateParams is one flat list in the order the variables appear: header variable first, then body variables.

curl -X POST https://api.pozy.ai/v1/inbox/conversations \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "6712a9d4c0e3b58f1a7d2e64",
    "participantId": "351912345678",
    "templateName": "pickup_ready",
    "templateLanguage": "en",
    "templateParams": ["Marta", "#4821", "6pm"]
  }'

Other parts of the template are filled with their own fields on the same call:

Field Fills
headerMedia The image, video or document of a media header (type plus a public link or an uploaded media id)
headerLocation A location header (latitude, longitude, optional name and address)
templateButtonParams Button variables by index, with subType url, copy_code or flow
templateCards Per-card values for a carousel (cardIndex, params, headerMedia, buttons)

templateLanguage must match exactly. en and en_US are two different templates.

Inside an existing conversation, send a template with POST /v1/inbox/conversations/{conversationId}/messages. To send the same template to many people with per-person values, use a broadcast.

#Edit or delete a template

Update by name with PATCH /v1/whatsapp/templates/{templateName} or by Meta id with PATCH /v1/whatsapp/templates/id/{templateId}. Put language in the body to say which variant you mean. A name that exists in several languages without language returns 409 with code ambiguous_template.

  • Changing components sends the template back to review and returns status: "PENDING".
  • Changing only message_send_ttl_seconds keeps an approved template approved. -1 is not allowed here.
  • Meta allows one edit per 24 hours and 10 per 30 days, and only from APPROVED, REJECTED or PAUSED.

Deleting moves a template to PENDING_DELETION for 24 hours before Meta removes it.

#Common errors

Error Cause Fix
400 on create Name format, missing example or a category outside the three values Check the rules above and resend
400 with param: message_send_ttl_seconds Window outside the range for the category Use a value from the category table
Meta code 132001, template not found Name or language code does not match Use the exact name and language the template was created with
Meta code 132000, parameter count Fewer or more templateParams than variables Send one value per variable, in order
REJECTED status Review failed; not an API error Read reason on whatsapp.template.status_updated, fix the text, create it again

#Endpoints

#Webhook events

Event When it fires
whatsapp.template.status_updated WhatsApp template status updated.
whatsapp.template.category_updated WhatsApp template category updated.