> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshoot WHATSAPP_TEMPLATE_VAR_COUNT_MISMATCH

> Fix 400s on WhatsApp template sends when the template_params count disagrees with the template's {{N}} placeholders — the send-side pre-flight that runs before the request reaches Meta.

# Troubleshoot WHATSAPP\_TEMPLATE\_VAR\_COUNT\_MISMATCH

You send a WhatsApp template and get an HTTP 400 back before any
message goes out. Orbit validates the parameter list against the
template body at accept time and refuses the send when the two
disagree, so a bad payload is a cheap 400 instead of a rejected Meta
delivery.

<Note>
  This page covers the send-side parameter check — the count and shape
  of the values you pass. For Meta-side template problems (pending,
  rejected, paused, reclassified, 24-hour window), see
  [WhatsApp template status](/troubleshooting/whatsapp-template).
</Note>

An example response:

```json theme={null}
{
  "error": {
    "code": "WHATSAPP_TEMPLATE_VAR_COUNT_MISMATCH",
    "message": "Template \"order_shipped\" expects 2 parameter(s) but 1 were supplied. Meta will reject the send (132000/131008) — fix the parameter list.",
    "details": {
      "template_name": "order_shipped",
      "expected": 2,
      "supplied": 1,
      "reason": "missing_params"
    }
  }
}
```

The same code fires for an empty value, with `reason:
empty_param_value` and a `details.empty_keys` list naming the slots:

```json theme={null}
{
  "error": {
    "code": "WHATSAPP_TEMPLATE_VAR_COUNT_MISMATCH",
    "message": "Template \"order_shipped\" has empty parameter(s): 2. Meta rejects template sends with empty positional vars (132012). Provide non-empty values.",
    "details": {
      "template_name": "order_shipped",
      "expected": 2,
      "supplied": 2,
      "empty_keys": ["2"],
      "reason": "empty_param_value"
    }
  }
}
```

## How parameter indexing works

A WhatsApp template body declares positional placeholders `{{1}}`,
`{{2}}`, … `{{N}}`. On the send you pass `template_params`, an object
whose keys are the 1-based placeholder numbers and whose values are
the replacement strings:

```
Template body:  "Hi {{1}}, order {{2}} shipped!"

Send payload:   { "template_params": { "1": "Ava", "2": "1042" } }
```

The pre-flight check reads the approved template body, counts its
placeholders, and compares that count against the parameters you
supplied. Two call-site shapes count as the parameter source: the
`template_params` object, or (for outbound templates saved with a
Media header) the Meta-native `metadata.templateComponents` array —
whichever is non-empty. Results of the comparison:

* **Fewer parameters than placeholders** → 400,
  `reason: missing_params`.
* **More parameters than placeholders** → 400,
  `reason: extra_params`.
* **Parameters on a zero-placeholder template** → 400,
  `reason: extra_params_on_zero_placeholder_template`.
* **Same count but a value is an empty or whitespace-only string** →
  400, `reason: empty_param_value`, with `details.empty_keys`.

A count match only clears this check. If the keys are non-positional
(`"name"`, `"first_name"`) or skip a number (`"1"`, `"3"` for a
two-placeholder template), the provider rejects the send again at
build time with
`WHATSAPP_TEMPLATE_PARAM_MISSING` even when the count lines up.
Keys must be exactly the contiguous series `"1"` … `"N"`.

## Common mismatches

Work the table in order — each row is one fixable cause.

| Cause                                               | Symptom                                                                             | Fix                                                                                                           |
| --------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Hard-coded parameters, template edited since        | `missing_params` or `extra_params` on every send                                    | Read the current template body (below) and bring the parameter list in line                                   |
| A value passed as `""`, `null`, or whitespace       | `empty_param_value` with the offending keys in `details.empty_keys`                 | Trim and validate values at your call site before the request leaves                                          |
| Named keys instead of positional keys               | `WHATSAPP_TEMPLATE_PARAM_MISSING` even when the count matches                       | Rename keys to `"1"…"N"`; order is assigned by key, not by insertion                                          |
| Incorrect one of the two parameter shapes           | `template_params` and `metadata.templateComponents` populated with different counts | Send one shape only — the checker uses whichever is non-empty and never adds the two together                 |
| A Media header variable passed in `template_params` | `extra_params` — body placeholders are one fewer than the total                     | Pass header and button variables through `metadata.templateComponents`; `template_params` fills the body only |

### Read the template body you are sending against

Do not count placeholders from memory — templates drift. Fetch the
current body and count the `{{N}}` markers yourself:

```bash theme={null}
curl -G "https://api.orbit.devotel.io/api/v1/messages/templates" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  --data-urlencode "search=order_shipped" \
  --data-urlencode "channel=whatsapp"
```

Fetch the full record at `GET /api/v1/messages/templates/{id}` for the
complete body text. The count check only runs when the template body
(or header) text is stored locally — authentication (OTP) templates
and Meta-synced templates whose body lives inside the stored
components defer to Meta instead, so a mismatch there surfaces as a
harder-to-read 132000/131008 rejection after the fact.

## Fix by example

Template with a Media header variable and one body placeholder:

* Header: `image` with a variable URL (or fixed media)
* Body: `Your order {{1}} shipped today.`

The correct send payload passes the header component through
`metadata.templateComponents` and the body value through
`template_params`:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/messages" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp",
    "to": "+14155552671",
    "template_name": "order_shipped_media",
    "template_language": "en_US",
    "template_params": { "1": "1042" },
    "metadata": {
      "templateComponents": "[{\"type\":\"header\",\"parameters\":[{\"type\":\"image\",\"image\":{\"link\":\"https://example-cdn.devotel.io/order-1042.png\"}}]}]"
    }
  }'
```

The body placeholder `{{1}}` gets one parameter, the header variable
rides in `templateComponents`, and the pre-flight passes. For a
text-only template, drop `metadata` and fill every `{{N}}` from
`template_params` alone.

## Validate before you send

The remediation loop is short — treat the 400 as a compile error:

1. Fetch the template body (above) and count `{{N}}` placeholders.
2. Build `template_params` with keys `"1"`–`"N"` in that order.
3. Confirm no value is empty or whitespace-only.
4. For header/button variables, move them into
   `metadata.templateComponents` in the Meta component shape.
5. Re-send. The same check runs on every WhatsApp template request, so
   a fixed payload clears it immediately.

In the dashboard template editor, the preview fills sample values into
each placeholder — use it to confirm which slots the template expects
before wiring your send code.

## Escalation checklist

If you have worked the page and the same 400 keeps coming back, email
[whatsapp-support@devotel.io](mailto:whatsapp-support@devotel.io) with:

1. The **template name and language** exactly as Meta has them
   (`order_shipped` + `en_US`).
2. The **full error response body**, including `details.expected`,
   `details.supplied`, and `details.reason`.
3. The **template body text** as fetched from
   `GET /api/v1/messages/templates/{id}`.
4. The **exact request payload** you sent, with phone numbers and
   parameter values redacted.

## See also

* [WhatsApp template status](/troubleshooting/whatsapp-template) —
  Meta-side approval, rejection, and quality gates. This page covers
  the send-side parameter pre-flight; that page covers everything Meta
  decides after the template exists.
* [Templates API](/api-reference/templates) — the
  create/read/update contract for the endpoints used above.
* [WhatsApp 24h freeform window](/guides/whatsapp/24h-window) — the
  window rules that decide whether a template send is required at all.
