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:

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

SMSWhatsApp
Template required?No. You can send free text with content instead.Yes. Always send with template_id.
Template sourceCreated in Ziett.Approved by Meta.
PlaceholdersIn the message body only.Header, body and buttons.

To use a template, send its template_id in place of content.


The variables object

FieldTypeDefaultDescription
valuesobject{}Literal values by variable key. Always take priority.
mappingobject{}Maps a variable key to a contact attribute, with an optional fallback. See Mapping object.
strictbooleantrueIf true, a variable that cannot be resolved is an error. If false, it is replaced with an empty string.

Mapping object

FieldTypeRequiredDescription
attributestring✅Name of the contact attribute to read.
defaultstring❌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:

  1. values — an explicit literal in the request. It always wins, even if the contact or a mapping exists.
  2. mapping — read from the contact's attributes. If the contact doesn't exist, or doesn't have that attribute, the mapping's default is used when defined.
  3. Auto-match — if the variable is in neither values nor mapping, the system looks for a contact attribute with the same name as the variable. No explicit mapping is needed when the names already match.
  4. Unresolved — with strict: true the request fails with a validation error; with strict: false the 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 values and contact data both describe that person. In a batch campaign, values are the same for every recipient; to personalize per recipient, rely on mapping and 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

WhatsApp templates have a header, a body and buttons, and each can have placeholders. To avoid collisions, keys are written as <scope>:<name>:

ScopeKey formatTemplate placeholderNotes
Headerheader:1{{1}}Positional templates.
Headerheader:<name>{{<name>}}Named templates.
Bodybody:1, body:2, …{{1}}, {{2}}, …Positional templates. Numbering starts at 1.
Bodybody:<name>{{<name>}}Named templates, e.g. body:customer_name.
Buttonbutton:0, button:1, …Button parameterNumbering 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 is button: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: true for 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.