Responses and errors
The JSON every flow trigger request is answered with, every status it can carry, and which ones to retry.
Every answer from a flow trigger URL has the same JSON body, whatever its HTTP status:
{ "id": "8f14e45f-ceea-467a-9575-5b1e2c7d3a90", "status": "accepted", "detail": null }| Field | Type | Meaning |
|---|---|---|
id | UUID or null | The run the request started, or, for a repeat, the run the first request started. null when nothing started. |
status | string | accepted, duplicate, or why not. Branch on this. |
detail | string or null | Why, in words for people, such as No phone number at "customer.phone". Log it; it may change. |
The one exception: when one IP address sends more than 120 requests a minute to flow
trigger URLs, it is answered 429 with the plain-text body Too many requests. Treat any
429 without JSON as rate_limited.
Every status
Generated from the list the API itself answers with:
status | HTTP | Retry | Meaning | What to do |
|---|---|---|---|---|
accepted | 202 | — | The flow was queued for the customer the body names. id is the run. | Done. Keep id to match the flow.completed or flow.failed event. |
duplicate | 200 | — | A request with this Idempotency-Key already started a run; nothing new was queued. id is that run. | Treat as success: id is the run the first request started. |
invalid_json | 400 | No | The body is not JSON. | Fix the body. The same request fails the same way every time. |
idempotency_key_too_long | 400 | No | The Idempotency-Key header is longer than 200 characters. | Send a key of at most 200 characters. |
not_in_plan | 403 | No | The account's plan does not include automations, the developer API or automated templates. | Stop sending. Nothing starts until the account's plan includes it. |
not_found | 404 | No | No hook has this token. It may have been regenerated or deleted. | Check the URL: it may have been replaced or the flow deleted. |
flow_off | 409 | No | The flow is not published, is switched off, or is beyond the plan's number of flows. | Publish the flow and switch it on, then send again. |
flow_not_listening | 409 | No | The published flow is not started by its webhook. | Set the published flow's trigger to "Your system sends an event", then send again. |
channel_unavailable | 409 | Yes | The flow's WhatsApp number is not connected. | Reconnect the flow's WhatsApp number; retry later. |
opted_out | 409 | No | The customer opted out of messages from this business. | Do not retry: the customer asked not to be messaged. |
payload_too_large | 413 | No | The body is over 64 KB. | Send only what the flow reads. |
unsupported_media_type | 415 | No | The body was not sent as application/json. | Send Content-Type: application/json. |
invalid_phone | 422 | No | No phone number at the flow's phone path, or one that cannot be read. Send it in E.164, such as +9665XXXXXXXX. | Send the number with its country code, such as +966501234567. |
rate_limited | 429 | Yes | More than 60 requests a minute to this hook, or 1000 an hour to the account's hooks together. | Retry with backoff. |
template_limit_reached | 429 | Yes | Today's automated templates for the account are used up. | Retry later, when the day's allowance frees up, or drop the event. |
customer_limit_reached | 429 | Yes | This customer has had 3 automated templates in the last 24 hours. | Retry later, or drop the event. |
service_unavailable | 503 | Yes | Hooks are paused on our side, or we could not count requests. Try again in a minute. | Retry after a minute, with the same Idempotency-Key. |
status may gain values. Treat one you do not know as a failure you did not expect: log
it with its detail, and use the HTTP status to decide whether to retry.
Retry or not
- Retry later, with the same
Idempotency-Key:rate_limited,template_limit_reached,customer_limit_reached,channel_unavailable,service_unavailable, any5xx, and a timeout or a connection that failed. Back off exponentially, starting at about a minute. - Do not retry unchanged: every other status. The same request fails the same way until you change the request, the flow or its settings.
- Never retry
opted_out: the customer asked not to be messaged. acceptedandduplicateare both success. Aduplicatemeans an earlier request with the same key already started the run whoseidyou receive.
Order of checks
A request is checked in this order, and the first failure is the answer:
- the per-address limit (
429, plain text); Content-Type(415) and size (413);- whether flow triggers are paused on our side (
503); - that the URL's token exists (
404); - the per-URL and per-account rate limits (
429,503if we cannot count); - the
Idempotency-Keylength and that the body is JSON (400); - the account's plan (
403); - that the flow is published, switched on and started by this trigger (
409); - the
Idempotency-Keyagainst earlier runs (200 duplicate); - the flow's WhatsApp number (
409 channel_unavailable); - the customer's phone number (
422); - the account's and the customer's template limits (
403,429); - whether the customer opted out (
409 opted_out).
A request refused at any step starts nothing, and its Idempotency-Key stays unused.
After 202 accepted
The run is queued, not finished. It can still end without sending anything, for example
when someone on your team is already handling the conversation. Subscribe an endpoint to
flow.completed and
flow.failed to learn how a run ended; both carry the run
id you received. A run that is skipped or cancelled before it ends sends neither event.