WhatsApp API & Developers

WhatsApp Template Variables: Named vs Positional

Variables are what turn one approved template into thousands of personal messages: the customer's name, their order number, the delivery date. They are also where most template bugs live, both at review time, when Meta rejects the format, and at send time, when the API rejects your payload.

WhatsApp template variables come in two formats, named and positional, and the format you pick at creation decides how you must send values later. This guide covers both, with the creation and send JSON for headers, bodies, URL buttons and copy code buttons, the formatting rules Meta enforces, and the errors you will hit when something doesn't line up.

All examples use the Cloud API as documented in October 2026.

Named vs positional: pick one per template

Meta calls these "parameters" in the API and "variables" in WhatsApp Manager. You set the format with parameter_format when you create the template. Leave it out and you get positional.

PositionalNamed
Placeholder{{1}}, {{2}}, {{3}}{{first_name}}, {{order_id}}
RulesWhole numbers from 1, in order of appearanceLowercase letters and underscores, unique within the template
Creation samplesbody_text (array inside an array)body_text_named_params (name and example pairs)
Send valuesMust match placeholder orderMatched by parameter_name, any order
Best forShort, stable templatesAnything you will edit or hand to another developer

Named parameters are easier to maintain. When marketing asks to swap the order of two phrases, a named template's send code doesn't change. With positional, every caller must reorder its values, and a silent mix-up can put the date where the name should be.

Defining variables at creation

Every variable needs a sample value when you create the template, and Meta's reviewers see those samples. This creates a named template with a text header variable and three body variables:

{
  "name": "delivery_window_v1",
  "language": "en",
  "category": "utility",
  "parameter_format": "named",
  "components": [
    {
      "type": "header",
      "format": "text",
      "text": "Delivery for order {{order_id}}",
      "example": {
        "header_text_named_params": [
          { "param_name": "order_id", "example": "A-48213" }
        ]
      }
    },
    {
      "type": "body",
      "text": "Hi {{first_name}}, your parcel arrives on {{delivery_date}} between {{time_window}}. Reply here if you need a different slot.",
      "example": {
        "body_text_named_params": [
          { "param_name": "first_name", "example": "Priya" },
          { "param_name": "delivery_date", "example": "Friday 7 November" },
          { "param_name": "time_window", "example": "2 pm and 5 pm" }
        ]
      }
    }
  ]
}

The positional version of a body looks like this. Note the nested array: body_text holds one array of example strings, in order.

{
  "type": "body",
  "text": "Hi {{1}}, your parcel arrives on {{2}} between {{3}}. Reply here if you need a different slot.",
  "example": {
    "body_text": [["Priya", "Friday 7 November", "2 pm and 5 pm"]]
  }
}

Where variables are allowed:

  • Text header: one variable, 60 characters in total.
  • Body: multiple variables, 1,024 characters in total including the values you send.
  • URL button: one variable, appended to the end of the URL, 2,000 characters in total.
  • Copy code button: the code itself, up to 20 characters, supplied at send time.
  • Footer: no variables. It is fixed text.

Media headers are a different kind of variable. You upload a sample at creation to get a header handle, then pass the real image, video or document at send time.

Filling variables at send time

At send time, each component with variables gets a parameters array. Here is the named template above:

curl -X POST "https://graph.facebook.com/v23.0/PHONE_NUMBER_ID/messages" \
  -H "Authorization: Bearer $WHATSAPP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messaging_product": "whatsapp",
    "recipient_type": "individual",
    "to": "+919811122233",
    "type": "template",
    "template": {
      "name": "delivery_window_v1",
      "language": { "code": "en" },
      "components": [
        {
          "type": "header",
          "parameters": [
            { "type": "text", "parameter_name": "order_id", "text": "A-48213" }
          ]
        },
        {
          "type": "body",
          "parameters": [
            { "type": "text", "parameter_name": "first_name", "text": "Priya" },
            { "type": "text", "parameter_name": "delivery_date", "text": "Friday 7 November" },
            { "type": "text", "parameter_name": "time_window", "text": "2 pm and 5 pm" }
          ]
        }
      ]
    }
  }'

Use the current Graph API version from Meta's changelog in place of v23.0. Buttons take their own components, identified by sub_type and a zero-based index that must match the button's position in the template:

[
  {
    "type": "button",
    "sub_type": "url",
    "index": "0",
    "parameters": [{ "type": "text", "text": "A-48213" }]
  },
  {
    "type": "button",
    "sub_type": "copy_code",
    "index": "1",
    "parameters": [{ "type": "coupon_code", "coupon_code": "WINTER25" }]
  }
]

URL button values must be percent-encoded. Meta's own example: a customer named Gonçalves must be sent as Gon%C3%A7alves, and a space becomes %20. Unencoded characters can fail validation and the send errors out.

Body parameters also support currency and date_time types with a fallback_value, which is what the customer sees:

{ "type": "currency", "currency": { "fallback_value": "INR 1,250.00", "code": "INR", "amount_1000": 1250000 } }

amount_1000 is the amount multiplied by 1,000. In practice most teams send a pre-formatted text value instead, since the fallback is what displays.

Formatting rules that trip review

Meta's template review page and status webhooks list the variable problems that get templates rejected. Check every template against them:

  1. No variable at the start or end of the text. {{first_name}}, your order... fails; Hi {{first_name}}, your order... passes.
  2. No adjacent variables. {{currency}}{{amount}} gets an INVALID_FORMAT rejection, with a recommendation to separate parameters with descriptive text.
  3. Positional numbers must be sequential. {{1}}, {{2}}, {{5}}, {{4}} fails.
  4. No special characters in placeholders. Meta names #, $ and %. Named parameters allow only lowercase letters and underscores.
  5. Enough fixed text. Too many variables relative to the message length is its own rejection reason.
  6. Exactly two braces on each side: {{1}}, never {1}} or {{ 1 }}.
  7. A realistic sample for every variable. Samples are required, and appeals must include them.

A useful mental test: if the template still reads like a sentence with every variable deleted, it is likely to pass. If it reads as gibberish, Meta will probably agree. Our guide to fixing a rejected WhatsApp template has more before-and-after rewrites.

Errors when values don't line up

Once the template is approved, send-time errors take over. The ones tied to variables, from Meta's error codes page:

CodeMeaningUsual cause
132000Parameter count doesn't match the templateA missing header or body value, or an extra one
132012Parameter values formatted incorrectlySending positional values to a named template, or the wrong type
132001Template doesn't exist in that language or isn't approvedWrong language.code, or the template is still pending
131008A required parameter is missingA malformed component object
131009A parameter value is invalidAn unencoded URL value, or a value outside allowed ranges

Two more traps. Values count toward the component's character limit, so a 300-character product name can push a body past 1,024. And templates created from Meta's Template Library are type-checked at send time: a library AMOUNT or DATE parameter rejects values outside its expected format. The full list lives in our guide to WhatsApp API error codes.

Building parameters in code

Hand-writing component arrays invites 132000 errors. Generate them from a plain object instead, so the template definition and the send code can't drift:

// Build Cloud API components for a NAMED template from plain objects.
function buildComponents({ header = {}, body = {}, urlButton } = {}) {
  const toParams = (values) =>
    Object.entries(values).map(([name, value]) => ({
      type: "text",
      parameter_name: name,
      text: String(value),
    }));

  const components = [];
  if (Object.keys(header).length) components.push({ type: "header", parameters: toParams(header) });
  if (Object.keys(body).length) components.push({ type: "body", parameters: toParams(body) });
  if (urlButton !== undefined) {
    components.push({
      type: "button",
      sub_type: "url",
      index: "0",
      parameters: [{ type: "text", text: encodeURIComponent(urlButton) }],
    });
  }
  return components;
}

const components = buildComponents({
  header: { order_id: "A-48213" },
  body: { first_name: "Priya", delivery_date: "Friday 7 November", time_window: "2 pm and 5 pm" },
});

Pair it with a test that fetches the template (GET /TEMPLATE_ID?fields=components,parameter_format) and checks every placeholder has a value, and you catch mismatches before production does. For the rest of the send flow, see sending a WhatsApp message by API.

Where Tenashi fits

Variables exist so that one approved template can be personalised safely at scale. Tenashi works differently: it runs on your existing WhatsApp number, linked by QR scan, outside Meta's official platform, so there are no templates and no placeholders. You write the final text yourself and send it, from the shared inbox or the API.

Personalisation still matters there, for a different reason. Tenashi pauses the same exact text sent to 5 chats within 10 minutes (a 422 send_blocked response), so you fill in names and details before sending:

curl https://usetenashi.com/api/v1/messages \
  -H "Authorization: Bearer $TENASHI_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: delivery-A-48213" \
  -d '{"to": "+919811122233", "text": "Hi Priya, order A-48213 arrives Friday between 2 pm and 5 pm. Reply here if you need a different slot."}'

The reply lands in your team inbox with the full chat history. The limits: no buttons or templates, paced sends, a daily cap on new chats, no green tick, and WhatsApp can still ban numbers that spam. For template-based sending at scale, see our complete guide to the WhatsApp API.

Read the API docs · Get started

FAQ

Should I use named or positional variables?

Use named variables for anything you expect to maintain. They are matched by name, so send code survives wording changes, and they make templates readable in code review. Positional variables work for short, stable templates. You choose the format with parameter_format at creation, and positional is the default if you leave it out.

How many variables can a WhatsApp template have?

Meta doesn't publish a fixed number for the body, but it rejects templates with too many variables relative to their length. A text header allows one variable, a URL button allows one at the end of the URL, and footers allow none. Values you send count toward the 1,024-character body limit.

Can a WhatsApp template start with a variable?

No. Meta rejects templates that start or end with a variable, which it calls dangling parameters. Put fixed words before the first variable and after the last one, for example "Hi first_name, your order is confirmed. Thank you." Two variables side by side with nothing between them are also rejected.

Why do I get error 132000 when sending a template?

Error 132000 means the number of values in your request doesn't match the number of variables in the template. Usually a header variable was forgotten, or a body value is missing or extra. Fetch the template's components through the API and compare them with your payload, component by component.

Do URL button variables need encoding?

Yes. Meta requires percent-encoding for special characters in URL button values. A space becomes %20, a colon %3A, and accented letters use their UTF-8 encoding, so Gonçalves becomes Gon%C3%A7alves. Unencoded values can fail validation and the send returns an error instead of a message ID.


Get variables right once and every send after is boring, which is the goal. For conversations that don't need templates at all, get started with Tenashi.