Guides

Templates

Submit templates for Meta review, track their status, sync them, and use them in your sends.

Updated November 12, 2026View as Markdown

A template is a message model pre-approved by Meta. It is the only way to message someone who hasn't written to you in the last 24 hours. Templates belong to a WhatsApp Business account (WABA): all the numbers in that account share the same templates.

The lifecycle#

text
You submit ──► pending ──► approved   (usable)
                      └──► rejected   (reason in rejectionReason)
                           paused / disabled  (insufficient quality: decided by Meta)

The status is Meta's, in lowercase. Only approved can be sent (usable: true).

Create a template#

bash
curl -X POST "$INBOX_API/waba-connections/WABA_CONNECTION_ID/templates" \
  -H "Authorization: Bearer $INBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "appointment_reminder",
    "language": "en",
    "category": "UTILITY",
    "components": [
      {
        "type": "BODY",
        "text": "Hello {{1}}, your appointment is confirmed for {{2}}.",
        "example": {
          "body_text": [
            [
              "Awa",
              "November 14 at 10 am"
            ]
          ]
        }
      }
    ]
  }'
Response · 201 Created
{
  "id": "ff532b8f-2956-4f04-851f-db09d5eefbd4",
  "name": "appointment_reminder",
  "language": "en",
  "category": "UTILITY",
  "status": "pending",
  "usable": false,
  "rejectionReason": null,
  "components": [
    {
      "type": "BODY",
      "text": "Hello {{1}}, your appointment is confirmed for {{2}}.",
      "example": {
        "body_text": [
          [
            "Awa",
            "November 14 at 10 am"
          ]
        ]
      }
    }
  ],
  "updatedAt": "2026-11-12T08:33:45.595Z"
}
RuleDetail
nameLowercase letters, digits, and underscores: appointment_reminder.
languagefr, en, en_US… The same name can exist in several languages.
categoryMARKETING, UTILITY, or AUTHENTICATION. Meta decides which category applies, and the pricing that follows from it.
componentsIn Meta's format. The BODY component is required; HEADER, FOOTER, and BUTTONS are optional. Variables are written with numbered double curly braces, and the example field provides a sample value for each variable.

The API validates the structure of the draft (name, language, category, components, buttons) before calling Meta, and responds with 400 validation_error and details if something is wrong. Meta then applies its own content rules: a rejection arrives later, as a rejected status.

Components#

Each component is an object in components. The limits below are the ones Meta announces; Meta has the final say.

ComponentContentLimits
HEADERformat: TEXT, IMAGE, VIDEO, DOCUMENT, or LOCATION.Text: 60 characters, at most one variable (example.header_text). Media: example.header_handle with a sample file.
BODYtext with variables {{1}}, {{2}}…1,024 characters. Consecutive numbers starting at 1, and one example per variable (example.body_text).
FOOTERtext.60 characters, no variables.
BUTTONSA buttons list.At most 10 buttons. Text of 25 characters.

Button types:

typeFieldsRules
QUICK_REPLYtextQuick replies form a single block, at the start or at the end of the list.
URLtext, url, exampleAt most 2. https:// is required. One {{1}} variable is allowed, at the end of the URL, with a full-URL example.
PHONE_NUMBERtext, phone_numberAt most 1, with a country code (+237…).
COPY_CODEexampleAt most 1. Example of 1 to 15 letters or digits.
A template with an image header, footer, and buttons
{
  "name": "christmas_promo",
  "language": "en",
  "category": "MARKETING",
  "components": [
    { "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["https://example.com/poster.jpg"] } },
    { "type": "BODY", "text": "Hello {{1}}, enjoy {{2}} storewide!", "example": { "body_text": [["Awa", "20% off"]] } },
    { "type": "FOOTER", "text": "Offer valid until 12/31" },
    { "type": "BUTTONS", "buttons": [
      { "type": "QUICK_REPLY", "text": "Interested" },
      { "type": "URL", "text": "See the offer", "url": "https://example.com/offer/{{1}}", "example": ["https://example.com/offer/42"] },
      { "type": "COPY_CODE", "example": "XMAS25" }
    ] }
  ]
}

Authentication templates#

For AUTHENTICATION, Meta provides the message text itself: no free text, no header. The BODY only carries add_security_recommendation, the FOOTER only code_expiration_minutes (1 to 90), and an OTP button is required:

json
[
  { "type": "BODY", "add_security_recommendation": true },
  { "type": "FOOTER", "code_expiration_minutes": 10 },
  { "type": "BUTTONS", "buttons": [{ "type": "OTP", "otp_type": "COPY_CODE", "text": "Copy code" }] }
]

Track the review#

You have two options:

  • Webhooks: the template.status_updated event notifies you as soon as Meta rules on a template.
  • Sync, a repair path rather than a polling loop:
bash
curl -X POST "$INBOX_API/waba-connections/WABA_CONNECTION_ID/templates/sync" \
  -H "Authorization: Bearer $INBOX_KEY"
Response · 200 OK
[
  {
    "id": "ff532b8f-2956-4f04-851f-db09d5eefbd4",
    "name": "appointment_reminder",
    "language": "en",
    "category": "UTILITY",
    "status": "approved",
    "usable": true,
    "rejectionReason": null,
    "components": [
      {
        "text": "Hello {{1}}, your appointment is confirmed for {{2}}.",
        "type": "BODY"
      }
    ],
    "updatedAt": "2026-11-12T08:33:45.640Z"
  }
]

Sync aligns the database with Meta: statuses are updated, and templates created elsewhere (directly with Meta, for example) are added. Status changes also trigger template.status_updated.

bash
curl "$INBOX_API/waba-connections/WABA_CONNECTION_ID/templates" \
  -H "Authorization: Bearer $INBOX_KEY"
Response · 200 OK
[
  {
    "id": "ff532b8f-2956-4f04-851f-db09d5eefbd4",
    "name": "appointment_reminder",
    "language": "en",
    "category": "UTILITY",
    "status": "approved",
    "usable": true,
    "rejectionReason": null,
    "components": [
      {
        "text": "Hello {{1}}, your appointment is confirmed for {{2}}.",
        "type": "BODY"
      }
    ],
    "updatedAt": "2026-11-12T08:33:45.640Z"
  }
]

Send a template#

Reference it by name and language. Variables are passed in components, in the order of their numbers:

POST /messages body
{
  "phoneNumberId": "…",
  "to": "+237600000000",
  "type": "template",
  "template": {
    "name": "appointment_reminder",
    "language": "en",
    "components": [
      { "type": "body", "parameters": [
        { "type": "text", "text": "Awa" },
        { "type": "text", "text": "November 14 at 10 am" }
      ] }
    ]
  }
}

If the template exists in Bref Point Inbox but is not approved, the send is rejected immediately, with the status and reason:

422 template_not_approved
{ "error": { "code": "template_not_approved",
  "message": "Template “appointment_reminder” (en) is “rejected”: INVALID_FORMAT. Only an approved template can be sent" } }

A template unknown to Bref Point Inbox (created directly with Meta and not yet synced) is not blocked upfront: Meta decides, and the message goes to failed if the template doesn't exist.

Delete#

bash
curl -X DELETE "$INBOX_API/waba-connections/WABA_CONNECTION_ID/templates/NAME" \
  -H "Authorization: Bearer $INBOX_KEY"
Response · 204 No Content
(no content)

Deletion removes the template in all its languages, first with Meta, then in Bref Point Inbox.