# Templates

> Write, submit and send the pre-approved WhatsApp messages that reach customers outside the 24-hour window.

Source: https://docs.amomic.in/channels/whatsapp-templates

---

A template is a message written in advance, reviewed by Meta, and then sent with the changeable parts filled in. It is the only way to reach a customer outside [the 24-hour window](/channels/whatsapp-window) — which, for anything your own systems trigger, is nearly always.

Almost every template is one you write. The exception is described under [Amomic-AI templates](#amomic-ai-templates) below.

Meta requires approval because templates are how a business can message someone who is not currently talking to it. Review is the control on that. It is not a formality: a template that reads like unsolicited promotion in a category that claims otherwise gets rejected, and templates that provoke blocks pull down the number's quality rating.

**Who can do it.** Anyone who can open **Channels → WhatsApp** can write, submit and sync templates. Uploading header images and videos, and deleting templates, need the owner or admin role.

## Where templates live

Open **Channels → WhatsApp**, select a number, and open its **Templates** tab. Templates belong to the WhatsApp Business Account, so every number on the same account shows the same list.

![The Templates tab with counters for 9 approved, 2 pending and 1 rejected, Sync and New template buttons, and a table of templates including diwali_exchange_offer marked Needs a header image before it can be sent](https://docs.amomic.in/screens/whatsapp/templates.light.webp)

*Each row shows its variable count, and any problem that stops it being sent.*

| Part | What it shows |
|---|---|
| **Sync** | Pulls the current state of every template from Meta, including ones created in WhatsApp Manager. The console confirms how many it synced. |
| **New template** | Opens the composer. See [Writing one](#writing-one). |
| **Approved / Pending / Rejected** | How many templates are in each state. |
| The table | **Name**, **Language**, **Category** and **Status** for each template. Under the name: the number of variables, Meta's rejection reason, or *"Needs a header image before it can be sent"*. Click a row to open it; the bin icon deletes it. |
| **Amomic-AI templates** | Below your own templates. See [Amomic-AI templates](#amomic-ai-templates). |

With no templates yet, the tab says *"No templates yet"* with **New template** and **Open WhatsApp Manager**. If the number has no WhatsApp Business Account ID, the tab shows *"WhatsApp Business Account required"* instead: reconnect the number through Meta. See [Reconnect a sender](/channels/whatsapp#reconnect-a-sender).

## Anatomy

A template has one required part and three optional ones.

| Component | Required | Limit | Notes |
|---|---|---|---|
| **Header** | No | 60 characters for text | A short bold line, one image, or one video. |
| **Body** | **Yes** | 1024 characters | The message. Where variables normally live. |
| **Footer** | No | 60 characters | Small print. **No variables allowed here.** |
| **Buttons** | No | 10 | Quick replies, links, or a call button. |

Plus two identifiers that are not shown to customers: a **name** (lowercase letters, numbers and underscores, up to 128 characters) and a **language** code.

```mock
{"component": "WhatsAppMock", "props": {"header": "Your order is on the way", "body": "Hi Asha, your order AM-4821 has shipped and arrives by 6 PM today. Track it any time with the button below.", "footer": "Reply STOP to opt out", "buttons": ["Track order", "Talk to support"], "time": "18:04", "caption": "The template order_shipped with {{1}} filled in as \"Asha\" and {{2}} as \"AM-4821\"."}}
```

## Image headers

In the composer, set **Header** to **Image**. Drop a file on *"Drop an image here, or click to choose"*, or click to pick one.

- **JPEG or PNG only, under 5 MB.** WhatsApp doesn't accept WEBP, GIF or other formats in a header. Anything else is refused with *"Use a JPEG or PNG — WhatsApp won't accept other formats in a header."*
- **You choose the crop.** WhatsApp shows a header as a wide strip (1.91:1) and crops the middle to get it. The console opens **Crop the header image** first: drag to choose what stays, zoom with the slider or scroll, then **Use image**. The crop you confirm is what Meta reviews and what customers see.
- **Adjust crop**, **Replace** and the bin icon change or remove the image before you submit. If a crop comes out over 5 MB, zoom out or start from a smaller image.

### Video headers

Set **Header** to **Video** and drop a file on the video zone. The video must be **MP4 (H.264 video, AAC audio), under 16 MB**. There's no crop step: WhatsApp fits a header video inside the bubble rather than cropping it.

The console checks the file type and size. It can't check the codecs, so a video in another codec is refused by Meta when it's sent.

Switching the header between **Text**, **Image** and **Video** keeps what you'd entered in each, so you can look at another option and come back. Only the format selected when you submit is sent.

### Every send uses the attached file

An approved image or video template needs its file every time it's sent. Amomic-AI attaches the file stored with the template automatically, whether the send comes from the API, an agent, a campaign or the console. You never pass the file yourself.

### Change the picture on an approved template

Meta approves that a picture goes above the message, not which picture. So you can swap it without a new review.

Open the template from the list. The **Header image** (or **Header video**) section shows the current file: *"Swap it any time — Meta approved the layout, not the file, so a new image needs no review."* Choose **Replace** and pick a new file. The console confirms *"Image updated. It goes out with the next message — no new review needed."*

This is how this month's offer runs through a template approved last month.

![The detail dialog for monsoon_ac_service_offer, approved, showing the header image monsoon-ac-service.jpg with Replace, the Sending it name and language, one variable, identifiers, and a phone preview with the Monsoon AC service banner](https://docs.amomic.in/screens/whatsapp/template-detail.light.webp)

*The picture can be replaced at any time; everything else is fixed.*

### Templates created in WhatsApp Manager

A template with an image or video header that you created in WhatsApp Manager arrives through **Sync** without its file, because Meta doesn't hand the file back. It's approved, but it can't be sent until you add one here.

The list marks it *"Needs a header image before it can be sent"*. Open it, and the dialog says *"This template shows an image, but none is attached here"*. Choose **Upload image** (or **Upload video**). No new review is needed.

![The detail dialog for diwali_exchange_offer with a warning that the template shows an image but none is attached, an Upload image button, and a preview with an empty image placeholder](https://docs.amomic.in/screens/whatsapp/template-missing-media.light.webp)

*Synced from WhatsApp Manager: approved, and waiting for its picture.*

Until then a campaign using it is blocked with `template_media_missing`, an agent picks another template, and an API send fails with `412 failed_precondition`.

The console can't preview or compose templates with a **document** header. A synced one says so in its dialog.

## Categories

There are exactly three. Pick the one that describes what the message actually does — misclassifying is a common rejection reason, and marketing costs more than utility.

| Value | Label | Use it for |
|---|---|---|
| `UTILITY` | Utility | Follows up on something the customer did — an order, a booking, an account change. |
| `MARKETING` | Marketing | Promotions and announcements. Costs more and needs clearer opt-in. |
| `AUTHENTICATION` | Authentication | One-time passcodes only. |

::: warning Create AUTHENTICATION templates in WhatsApp Manager
The authentication category has rules of its own at Meta — fixed body structure, code-expiry handling, autofill behaviour — and the console composer does not implement them. If you need one-time-passcode templates, create them in WhatsApp Manager, then press **Sync** to pull them into the console so they can be sent. Everything on this page about sending applies to them normally; only authoring is elsewhere.
:::

## Status

Templates carry Meta's verdict as a raw lowercase string. Three values matter:

| Status | Meaning |
|---|---|
| `pending` | With Meta for review — usually minutes, sometimes a day. The status updates itself; you do not need to poll. |
| `approved` | Ready to send. |
| `rejected` | Meta won't deliver this. Meta's reason, such as `INCORRECT_CATEGORY`, shows under the name in the list and in full under **Why Meta rejected it** in the template's dialog. |

Template pickers elsewhere in the console show `pending` as **In review**, with a note that it can't be sent until Meta approves it.

**Only `approved` templates can be sent.** Naming a `pending` or `rejected` one returns `404 not_found` with `details.reason` of `"template_not_found"` — the name exists in your workspace, but not as something sendable.

::: note Statuses you did not expect
Meta occasionally reports states beyond these three, and they pass through lowercased rather than being forced into a bucket we would have to guess at. Treat anything that is not the literal string `approved` as not sendable.
:::

## Variables

Variables are numbered placeholders: `{{1}}`, `{{2}}`, `{{3}}`. You write them into the body when you author the template, and supply the values at send time.

```text title="Body"
Hi {{1}}, your order {{2}} has shipped and arrives by {{3}}.
```

Meta reviews the template with sample values filled in, so the composer asks for one sample per placeholder. Use realistic values — `Priya` and `AM-10428`, not `xxx` and `123`. A reviewer reading `Hi xxx, your order 123 has shipped` is being shown something that looks like spam.

Rules the composer enforces before it will submit, because Meta rejects on all of them:

- **A body cannot start or end on a placeholder.** *"The message can't start or end on a `{{n}}` — put words around it."*
- **Two placeholders cannot be adjacent.** *"Two `{{n}}` next to each other reads as blank to a reviewer — separate them with words."*
- **Numbering must be contiguous from 1.** *"Numbers must run 1, 2, 3… — `{{5}}` appears without `{{2}}`."*
- **Every placeholder needs a sample.**
- **Footers cannot contain placeholders.**

### Supplying values at send time

Values are matched to placeholders **by position**. Two shapes are accepted:

```json title="Positional array"
{ "variables": ["Asha", "AM-4821", "6 PM today"] }
```

```json title="Numbered object"
{ "variables": { "1": "Asha", "2": "AM-4821", "3": "6 PM today" } }
```

They mean the same thing. The array is less error-prone in code you will read again in six months; the object survives refactoring better when a template gains a placeholder.

Header and button variables are supplied separately, with `header_variables` and `button_variables`, using the same two shapes.

Passing the wrong number of values returns `400 invalid_argument` with `details.reason` of `"template_variables"`. Values are stripped of newlines and tabs and truncated at 1024 characters before they reach Meta — WhatsApp does not allow a placeholder to inject line breaks into a reviewed layout.

## Buttons

Three button types exist. Nothing else.

| Value | Console label | What it does |
|---|---|---|
| `QUICK_REPLY` | Quick reply | Sends its own label back to you as a message from the customer, which also opens the 24-hour window. |
| `URL` | Visit website | Opens a link. The link may end in a single `{{1}}` so you can point at a specific order or booking. |
| `PHONE_NUMBER` | Call phone number | Dials a number you specify, in full international form. |

Limits, all enforced before submission:

- **10 buttons maximum** on a template.
- **WhatsApp shows three.** With four or more, it shows the first two and hides the rest behind **See all options**. The composer's preview shows exactly that. Treat three as the practical design limit and 10 as the ceiling.
- **At most 2 `URL` buttons.**
- **At most 1 `PHONE_NUMBER` button.**
- **Labels up to 25 characters**, no duplicates, no placeholders in labels.
- **Quick replies must be grouped together** — Meta rejects them mixed in between link and call buttons.
- A `URL` button's link must start with `http://` or `https://`, may end in one `{{1}}` and nothing else, and needs an example of the full link with the value filled in.

## Languages

A template is identified by **name and language together**, not by an id. That pair is what Meta matches on when you send.

The console offers: `en`, `en_US`, `en_GB`, `hi`, `mr`, `gu`, `ta`, `te`, `bn`, `kn`, `ml`, `pa`.

At send time you may omit `language`, or name one that has no exact match. Resolution then tries, in order: an exact match, then the same base language (`en_US` falls back to `en`), then any `en*` variant, then the first approved template with that name. This means a send does not fail merely because you asked for `en_GB` and only `en` was approved.

To offer a template in a second language, duplicate it, keep the name, and change the language. Same name plus same language is rejected as a duplicate.

## Writing one

::: steps

::: step Open the composer

On the number's **Templates** tab, choose **New template**. The composer opens as **New message template**, with the form on the left and a live preview on the right.

:::

::: step Describe the message, or write it yourself

Type what the message is for under **Describe the message** and choose **Generate**, or click a starter: **Order shipped**, **Appointment reminder**, **Payment receipt** or **Feedback request**. The draft follows Meta's rules: placeholder placement, sample values, category and buttons.

The draft is written from the knowledge base of the agent assigned to **this number**. The console says which happened: *"Written from this agent's knowledge base — edit anything before submitting."*, or *"Written without a knowledge base, so it's generic."* A note under **Category** explains the category it chose, because category decides what Meta charges. Notes under **Message** say what it drew on.

Whatever it produces is a starting point. **Regenerate** writes another. Read every line before submitting; you're accountable for what Meta reviews.

:::

::: step Name it for the system that will send it

`service_visit_confirmed`, `amc_renewal_reminder`, `payment_receipt`. **Name** is lowercased, and spaces become underscores as you type. Customers never see it; your code does, and so does whoever debugs it.

:::

::: step Fill in the rest

Pick the **Language** and **Category**, an optional **Header**, the **Message**, a **Sample values** entry for each `{{n}}`, an optional **Footer** and up to 10 **Buttons**. Counters show how close you are to each limit.

:::

::: step Check the preview

The preview has two views. **Samples** shows what the customer receives. **As submitted** shows what Meta's reviewer reads, with the placeholders visible. Read the reviewer view; most rejections are obvious from it.

:::

::: step Submit for review

Choose **Submit for review**. If anything breaks Meta's rules, the composer names it and doesn't submit. Otherwise it confirms *"Submitted to Meta. Review usually takes a few minutes to a day."* and the template appears as `pending`.

If it says *"Submitted to Meta. It isn't in the list yet — press Sync in a moment to pull it in."*, Meta has it, and **Sync** brings it into the list.

:::

:::

![The New message template composer with a generated draft named repair_completed, Utility selected with a note explaining why, a text header, a three-variable message with sample values, and a phone preview with three buttons](https://docs.amomic.in/screens/whatsapp/template-composer.light.webp)

*A generated draft, grounded in the agent's warranty policy, ready to check and submit.*

## Reading a template

Click any row to open the template. Nothing in the dialog edits it, except the header file.

| Section | What it shows |
|---|---|
| Status line | The status, category and language, and what the status means. |
| **Why Meta rejected it** | On a rejected template, Meta's reason in full. |
| **Header image** / **Header video** | The attached file, with **Replace**. See [Image headers](#image-headers). |
| **Sending it** | The **Name** and **Language** to send it by, each with a copy button, and **Variables (n)** with the sample Meta reviewed for each. |
| **Identifiers** | **Meta id**, which WhatsApp Manager uses, and the console's own id. Send by name and language, not by either id. |
| **Created**, **Last updated** | When it was submitted or synced, and last changed. |

The footer has **Open WhatsApp Manager**, **Delete** and **Duplicate as new**.

## Templates cannot be edited

There is deliberately no edit control. An approved template is a thing Meta reviewed; changing its text would mean sending customers something that was never reviewed.

To change one, open it and choose **Duplicate as new**:

- **Change the name** to create a separate template. Both exist; you switch your code over when the new one is approved, then delete the old one.
- **Keep the name and change the language** to add a language variant.

This is also why deploying a template change is a two-step release: get the new template approved first, then point your code at it. Doing it in the other order means an outage between the code deploy and the approval.

::: danger Deleting removes every language variant
Meta deletes templates by name, not by id. Deleting `order_shipped` removes the English, Hindi and Marathi versions together, and anything still sending that name starts failing with `template_not_found` immediately. There is no undo and no grace period.
:::

## Sending one

::: codegroup

```bash cURL
curl -X POST https://services.amomic.in/api/v1/messages \
  -H "Authorization: Bearer $AMOMIC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4821-shipped" \
  -d '{
    "to": "+919876543210",
    "template": {
      "name": "order_shipped",
      "language": "en",
      "variables": ["Asha", "AM-4821", "6 PM today"],
      "button_variables": ["AM-4821"]
    }
  }'
```

```javascript Node.js
const response = await fetch("https://services.amomic.in/api/v1/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AMOMIC_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": `order-${order.id}-shipped`,
  },
  body: JSON.stringify({
    to: order.customerPhone,
    template: {
      name: "order_shipped",
      language: "en",
      variables: [order.customerName, order.reference, order.etaLabel],
      button_variables: [order.reference],
    },
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.code}/${error.details?.reason}: ${error.message}`);
}
```

```python Python
import os, httpx

response = httpx.post(
    "https://services.amomic.in/api/v1/messages",
    headers={
        "Authorization": f"Bearer {os.environ['AMOMIC_API_KEY']}",
        "Idempotency-Key": f"order-{order.id}-shipped",
    },
    json={
        "to": order.customer_phone,
        "template": {
            "name": "order_shipped",
            "language": "en",
            "variables": [order.customer_name, order.reference, order.eta_label],
            "button_variables": [order.reference],
        },
    },
)
response.raise_for_status()
```

:::

```json title="201 Created"
{
  "message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSMkI5RUE4...",
  "channel": "whatsapp",
  "to": "+919876543210",
  "status": "sent"
}
```

`status: "sent"` means we accepted it and Meta took it. It is an acceptance acknowledgement, not a delivery receipt — delivery and read state arrive later on the `message.status` [webhook](/webhooks/events).

Send the `Idempotency-Key`. A network timeout on a template send is exactly the case where a naive retry messages a customer twice. See [Idempotency](/api/idempotency).

## Errors you will actually hit

```responses
[
  {"status": 404, "code": "not_found", "reason": "template_not_found", "description": "No approved template with that name. Either the name is wrong, or the template is still `pending` or was `rejected`."},
  {"status": 400, "code": "invalid_argument", "reason": "template_variables", "description": "Wrong number or shape of variables for that template's placeholders."},
  {"status": 403, "code": "permission_denied", "reason": "suppressed", "description": "The recipient has opted out. Templates do not bypass suppression."},
  {"status": 412, "code": "failed_precondition", "reason": "channel_not_connected", "description": "No WhatsApp sender is connected to this workspace."},
  {"status": 412, "code": "failed_precondition", "reason": "template_media_missing", "description": "The template has an image or video header and no file is attached in this workspace. Upload one on the template's dialog. See Image headers."}
]
```

Note what is *not* in that list: there is no window error on a template send. Templates work whether the window is open or closed — that is their entire purpose.

## Amomic-AI templates

A few features of ours have to reach a customer outside the window, so they need a template of their own. They sit on the **Templates** tab, in the **Amomic-AI templates** card below your own templates, and carry the reserved `amomic_` name prefix.

Today there is one. `amomic_ticket_update` is what lets a support reply reach someone who last messaged you more than 24 hours ago: they get the notification, tap **View update**, and the reply that was waiting is delivered. Without it, a [ticket](/console/tickets) reply can't reach a customer whose window has closed. Check the reply's delivery state on the ticket rather than assuming it will go out when they next write.

**We do not submit it without asking.** Meta reviews it against your WhatsApp Business Account and records any rejection there, not with us, so the card asks: *"Send this template to Meta for approval?"*, with **Send for approval** and **Skip for now**. Review is free, and nothing is sent to any customer until you reply to one.

**Skip for now** is a real answer. Replies inside the 24-hour window are unaffected, and the offer comes back wherever a missing template is actually holding a reply up. You can also send it later from the card.

![The Amomic-AI templates card asking Send this template to Meta for approval? with Send for approval and Skip for now, and amomic_ticket_update marked Not sent for approval](https://docs.amomic.in/screens/whatsapp/amomic-ai-templates-ask.light.webp)

*The card asks before anything is submitted to Meta.*

Each Amomic-AI template carries a badge: **Approved**, **Using yours**, **In review**, **Rejected** or **Not sent for approval**. **Recheck** asks Meta again.

The wording and variable shape are fixed, because the code that sends it relies on both. What you can do instead is nominate one of your own approved templates in its place: choose **Use one of your own templates**, pick it under **Send one of your own templates instead**, and choose **Use this template**. It has to be approved, accept the same three variables in the same order (customer name, ticket reference, ticket subject), and carry a tappable quick-reply button. **Go back to ours** undoes it. Sending for approval, skipping and nominating need the owner or admin role.

::: warning A link button cannot replace a quick reply here
Tapping a URL button opens a browser and sends nothing back to us, so it never re-opens the 24-hour window — the customer would tap, see a page, and still never receive the reply. A substitute with only a link button is refused when you nominate it, rather than failing silently on a customer's phone.
:::

## Practical guidance

::: tip One template per system event, named after the event
`order_shipped`, `order_delivered`, `payment_received`. Resist the general-purpose template with six placeholders that any system can fill — it reviews badly, reads badly, and the day one caller passes the wrong argument order you have shipped nonsense to real customers.
:::

- **Get templates approved before you write the integration.** Review time is not under your control. Building first means your launch date is Meta's decision.
- **Put a `QUICK_REPLY` on anything you want a response to.** A tapped quick reply is an inbound message, so it opens the 24-hour window and your agent can take the conversation from there in free-form text.
- **Keep the body under 1024 characters and well under it in practice.** Long templates read as promotional regardless of category.
- **Do not put an opt-out line in the footer of a utility template** unless you want it there; the opt-out keywords work whether or not you advertise them. `Reply STOP to opt out` is standard on marketing and unnecessary noise on a delivery notification.
- **Watch the quality rating** on the sender after you start sending at volume. Red quality means recipients are blocking or reporting you, and Meta reduces messaging limits if it stays that way.

## Next

::: cards

::: card The 24-hour window
[Why templates exist](/channels/whatsapp-window) and when a free-form send is legal instead.
:::

::: card POST /messages
[The complete send contract](/api/messages), including sender selection and metadata.
:::

::: card Order-shipped update
[End to end](/recipes/order-updates) — template, idempotent send, and the webhook that confirms delivery.
:::

::: card Tools & actions
[Let the agent send a template itself](/build/tools), restricted to the ones you allow.
:::

:::
