Templates & Variables
Templates let you define a message once and fill in the details at send time. The same variables object is accepted by every endpoint that sends from a template:
- Send Message — one recipient.
- Send Batch Campaign — up to 1,000 recipients.
This page is the single reference for how those variables are resolved, so the endpoint pages only cover what is specific to them.
Templates by channel
| SMS | ||
|---|---|---|
| Template required? | No. You can send free text with content instead. | Yes. Always send with template_id. |
| Template source | Created in Ziett. | Approved by Meta. |
| Placeholders | In the message body only. | Header, body and buttons. |
To use a template, send its template_id in place of content.
The variables object
| Field | Type | Default | Description |
|---|---|---|---|
values | object | {} | Literal values by variable key. Always take priority. |
mapping | object | {} | Maps a variable key to a contact attribute, with an optional fallback. See Mapping object. |
strict | boolean | true | If true, a variable that cannot be resolved is an error. If false, it is replaced with an empty string. |
Mapping object
| Field | Type | Required | Description |
|---|---|---|---|
attribute | string | ✅ | Name of the contact attribute to read. |
default | string | ❌ | Fallback used when the contact doesn't exist or doesn't have the attribute. |
{
"variables": {
"values": { "body:order_id": "A-10293" },
"mapping": {
"body:customer_name": { "attribute": "first_name", "default": "customer" }
},
"strict": true
}
}
Resolution order
For every variable the template requires, the value comes from the first strategy that succeeds:
values— an explicit literal in the request. It always wins, even if the contact or a mapping exists.mapping— read from the contact's attributes. If the contact doesn't exist, or doesn't have that attribute, the mapping'sdefaultis used when defined.- Auto-match — if the variable is in neither
valuesnormapping, the system looks for a contact attribute with the same name as the variable. No explicit mapping is needed when the names already match. - Unresolved — with
strict: truethe request fails with a validation error; withstrict: falsethe variable becomes an empty string.
Contact lookup
On both channels, the system checks whether the recipient's number belongs to a contact in your organization's audience, and uses that contact's attributes for steps 2 and 3. If the number isn't in your audience, those steps are skipped. Send everything through values and nothing else is needed.
Single vs batch. In a single message there is one recipient, so
valuesand contact data both describe that person. In a batch campaign,valuesare the same for every recipient; to personalize per recipient, rely onmappingand auto-match, which are resolved against each recipient's own contact.
Variable keys
The key you use in values and mapping depends on the channel.
WhatsApp templates have a header, a body and buttons, and each can have placeholders. To avoid collisions, keys are written as <scope>:<name>:
| Scope | Key format | Template placeholder | Notes |
|---|---|---|---|
| Header | header:1 | {{1}} | Positional templates. |
| Header | header:<name> | {{<name>}} | Named templates. |
| Body | body:1, body:2, … | {{1}}, {{2}}, … | Positional templates. Numbering starts at 1. |
| Body | body:<name> | {{<name>}} | Named templates, e.g. body:customer_name. |
| Button | button:0, button:1, … | Button parameter | Numbering starts at 0 — the position of the button in the template. |
Buttons start at 0. Header and body placeholders are numbered from 1, as in Meta's template editor, but buttons are indexed from 0. The first button is
button:0, the second isbutton:1.
A template is either positional ({{1}}, {{2}}) or named ({{customer_name}}), never both, so use the key style that matches how the template was created. Since the scope is part of the key, the same name can exist in two scopes without conflict: header:name and body:name are different variables.
SMS
SMS templates have a single body, so the key is the variable name on its own, for example code for a {{code}} placeholder.
Examples
Literal values only. No contact data involved:
{
"variables": {
"values": { "body:1": "Carlos", "body:2": "482910" }
}
}
Contact data with a fallback. The name comes from the contact's first_name, or customer if the contact doesn't exist or has no such attribute:
{
"variables": {
"mapping": {
"body:customer_name": { "attribute": "first_name", "default": "customer" }
}
}
}
Auto-match. The template has {{customer_name}} in the body and the contact has a customer_name attribute. No mapping is needed:
{
"variables": {}
}
Button with a dynamic suffix. The first button is a URL button with a variable:
{
"variables": {
"values": { "button:0": "track/A-10293" }
}
}
Lenient mode. Unresolved variables are sent as empty strings instead of failing:
{
"variables": {
"values": { "body:1": "Carlos" },
"strict": false
}
}
Prefer the default
strict: truefor transactional traffic such as OTPs and payment alerts, where a message with a missing value is worse than a rejected request.
When a variable can't be resolved
With strict: true, Send Message rejects the request with a 422 and names each unresolved key in fields. To fix it, send the value in values, add a mapping with a default, make sure the contact has the attribute, or set strict to false.
Conventions
The patterns and contracts that apply to every endpoint in the Ziett API — base URL, request format, responses, errors, pagination, and idempotency.
Send Message
Submit a single transactional message to a specific recipient via SMS or WhatsApp, with optional template variables and contact capture.