Use templates when you need to reach out first — order updates, OTP codes, appointment reminders — especially outside the 24-hour reply window. Each template is pre-approved by Meta. A template send needs more than name and language. Pass components that match how the template was built: body variables, optional header image or text, and button URL parameters when the template defines them.

Before you send

1

Create and approve the template

Create the template in the Wazapin app or sync from Meta on official channels. Status must be approved.GET /v1/templates lists templates for your workspace. See Channel support for official vs unofficial behavior.
2

Read the template structure

Open the template in the app and note:
  • Header — none, text ({{1}}), image, video, or document
  • Body — static text with {{1}}, {{2}}, … placeholders
  • Buttons — quick reply, URL (may include {{1}} in the link), copy code, etc.
Your components array must mirror these sections in order.
3

Build the components array

Each section becomes one object in content.template.components:
Component typeWhen required
headerTemplate has a dynamic header (text, image, video, document)
bodyTemplate body has {{n}} variables
buttonURL or other dynamic button parameters (sub_type, index)
Omit a section if the template has no variables in that part.

Request shape

All template sends use POST /v1/messages with type: "template" and a nested content.template object (Meta-compatible shape).
FieldRequiredDescription
channel_idYesConnected WhatsApp channel
toYesRecipient phone (international format)
typeYestemplate
content.template.nameYesApproved template name
content.template.language.codeYesLocale, e.g. en_US, id
content.template.componentsOftenParameters per header/body/button
Do not use a flat shape like content.template_name at the top level — the API requires content.template.name and content.template.language.code.

Example: body variables only

Template body: Hi {{1}}, your order {{2}} is on the way.
curl -X POST "https://api.wazapin.com/v1/messages" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "wzp_abc123",
    "to": "6281234567890",
    "type": "template",
    "content": {
      "template": {
        "name": "order_shipped",
        "language": { "code": "en_US" },
        "components": [
          {
            "type": "body",
            "parameters": [
              { "type": "text", "text": "John" },
              { "type": "text", "text": "ORD-42" }
            ]
          }
        ]
      }
    }
  }'

Example: header image + body

Template with an image header and one body variable.
{
  "channel_id": "wzp_abc123",
  "to": "6281234567890",
  "type": "template",
  "content": {
    "template": {
      "name": "promo_with_image",
      "language": { "code": "id" },
      "components": [
        {
          "type": "header",
          "parameters": [
            {
              "type": "image",
              "image": { "link": "https://cdn.example.com/promo.jpg" }
            }
          ]
        },
        {
          "type": "body",
          "parameters": [
            { "type": "text", "text": "Ramadan Sale" }
          ]
        }
      ]
    }
  }
}

Example: header text variable

Template header text: Hello {{1}} — the header component lives inside content.template.components.
{
  "channel_id": "wzp_abc123",
  "to": "6281234567890",
  "type": "template",
  "content": {
    "template": {
      "name": "greeting_header",
      "language": { "code": "en_US" },
      "components": [
        {
          "type": "header",
          "parameters": [
            { "type": "text", "text": "John" }
          ]
        },
        {
          "type": "body",
          "parameters": [
            { "type": "text", "text": "Your appointment is confirmed." }
          ]
        }
      ]
    }
  }
}

Example: dynamic URL button

Template with a URL button containing {{1}} in the path — button component nested in the same components array.
{
  "channel_id": "wzp_abc123",
  "to": "6281234567890",
  "type": "template",
  "content": {
    "template": {
      "name": "order_tracking",
      "language": { "code": "en_US" },
      "components": [
        {
          "type": "body",
          "parameters": [
            { "type": "text", "text": "John" },
            { "type": "text", "text": "ORD-42" }
          ]
        },
        {
          "type": "button",
          "sub_type": "url",
          "index": "0",
          "parameters": [
            { "type": "text", "text": "ORD-42" }
          ]
        }
      ]
    }
  }
}
index is the zero-based button index in the template definition. sub_type matches the button type Meta approved (url, quick_reply, copy_code, etc.). List and sync templates with wazapin.templates.list() and wazapin.templates.sync(). See SDK templates (advanced).

Common mistakes

MistakeFix
Template not approvedWait for Meta approval or pick another name
Wrong language.codeMust match the template locale exactly (en_US vs en)
Missing componentsAdd parameters for every {{n}} in header/body/buttons
Flat template_name in contentUse nested content.template.name
Header image URL not HTTPSUse a public HTTPS URL Meta can fetch

Endpoint

POST https://api.wazapin.com/v1/messages Authenticate with X-Api-Key. See Authentication.

Response

On success, the API returns 201 Created with a message record (status often starts as queued).
201 Created
{
  "data": {
    "id": "9f1fd66d-c37a-4b50-a8c2-b4dca523f9c8",
    "channel_id": "wzp_abc123",
    "to_phone": "6281234567890",
    "type": "text",
    "status": "queued",
    "created_at": "2026-03-04T06:20:10Z",
    "updated_at": "2026-03-04T06:20:10Z"
  }
}
Track delivery with Webhooks or GET /v1/messages/.