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.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.
reason: empty_param_value and a details.empty_keys list naming the slots:
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_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, withdetails.empty_keys.
"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.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:
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:
imagewith a variable URL (or fixed media) - Body:
Your order {{1}} shipped today.
metadata.templateComponents and the body value through
template_params:
{{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:- Fetch the template body (above) and count
{{N}}placeholders. - Build
template_paramswith keys"1"–"N"in that order. - Confirm no value is empty or whitespace-only.
- For header/button variables, move them into
metadata.templateComponentsin the Meta component shape. - Re-send. The same check runs on every WhatsApp template request, so a fixed payload clears it immediately.
Escalation checklist
If you have worked the page and the same 400 keeps coming back, email whatsapp-support@devotel.io with:- The template name and language exactly as Meta has them
(
order_shipped+en_US). - The full error response body, including
details.expected,details.supplied, anddetails.reason. - The template body text as fetched from
GET /api/v1/messages/templates/{id}. - The exact request payload you sent, with phone numbers and parameter values redacted.
See also
- WhatsApp template status — 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 — the create/read/update contract for the endpoints used above.
- WhatsApp 24h freeform window — the window rules that decide whether a template send is required at all.