# Responses and errors

Source: https://docs.revoplyai.com/flow-triggers/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:

```json
{ "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 [#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-or-not]

* **Retry later**, with the same `Idempotency-Key`: `rate_limited`,
  `template_limit_reached`, `customer_limit_reached`, `channel_unavailable`,
  `service_unavailable`, any `5xx`, 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.
* `accepted` and `duplicate` are both success. A `duplicate` means an earlier request with
  the same key already started the run whose `id` you receive.

## Order of checks [#order-of-checks]

A request is checked in this order, and the first failure is the answer:

1. the per-address limit (`429`, plain text);
2. `Content-Type` (`415`) and size (`413`);
3. whether flow triggers are paused on our side (`503`);
4. that the URL's token exists (`404`);
5. the per-URL and per-account rate limits (`429`, `503` if we cannot count);
6. the `Idempotency-Key` length and that the body is JSON (`400`);
7. the account's plan (`403`);
8. that the flow is published, switched on and started by this trigger (`409`);
9. the `Idempotency-Key` against earlier runs (`200 duplicate`);
10. the flow's WhatsApp number (`409 channel_unavailable`);
11. the customer's phone number (`422`);
12. the account's and the customer's template limits (`403`, `429`);
13. 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` [#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`](/webhooks/events/flow-completed/) and
[`flow.failed`](/webhooks/events/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.
