> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.pivolio.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.pivolio.com/_mcp/server.

# Delivery statuses

Every event produces one delivery attempt per connected destination, and each
attempt records its own status. Five statuses exist, and they are genuinely
different situations — reading `skipped` as a failure, or `suppressed` as a bug,
sends you debugging the wrong thing.

## The five statuses

| Status       | Meaning                                                           | Your move                              |
| ------------ | ----------------------------------------------------------------- | -------------------------------------- |
| `sent`       | The platform accepted the event.                                  | Nothing.                               |
| `skipped`    | Not applicable — no mapping, or consent withheld it.              | Check mapping and consent config.      |
| `retrying`   | A transient failure; the attempt will be repeated.                | Wait. Investigate only if it persists. |
| `failed`     | A permanent failure. No further attempts.                         | Act — this conversion is lost.         |
| `suppressed` | Withheld by traffic-quality rules. Never offered to the platform. | Review your risk actions.              |

### sent

The platform returned a success. Note the limit of that claim: the platform
accepted your event, which is not the same as matching it to a user. How
matchable it was is [match quality](/documentation/core-concepts/match-quality), and no platform reports
per-event match results back.

### skipped

Nothing was wrong — the event just didn't apply to this destination. Two common
causes:

* **No mapping.** Google Ads is mapping-only: it sends nothing unless the event
  name is mapped to a conversion action. An unmapped event at Google is normal
  and expected, not an error.
* **Consent.** With enforcement on, a denied required category skips the send.
  Defaults are `ad_user_data` and `ad_storage`. See
  [Consent & privacy](/documentation/core-concepts/consent-privacy).

Skipped events are never retried, because nothing about them would change.

### retrying

A transient failure — a timeout, a `5xx`, or a `429`. The attempt is repeated
automatically. A brief burst of `retrying` during a platform incident is the
system working correctly.

Sustained `retrying` on one destination is worth investigating: it usually means
you're being rate-limited rather than that the platform is down.

### failed

Permanent. No further attempts will be made, and the conversion did not reach
the platform.

This is the status to alarm on. A rising `failed` count almost always means one
of two things: a credential problem, or a payload the platform rejects for every
event of that shape. Both keep failing until you fix them.

### suppressed

The event was withheld by [traffic quality](/documentation/core-concepts/traffic-quality) rules — it was
never offered to the platform at all. This is distinct from `failed`: nothing
broke, a rule chose this.

A non-trivial suppressed rate is healthy on ad traffic. A sudden spike means
either a genuine fraud event or a rule you just changed catching real
conversions.

## Error codes

A failed or retrying attempt carries a `DispatchErrorCode`:

* **`AUTH_ERROR`** — a `401` or `403`. Credentials are wrong, expired, or the
  grant was revoked. Reconnect the destination.
* **`API_ERROR`** — the platform rejected the request. Usually a payload the
  platform won't accept.
* **`TIMEOUT`** — no response in time.
* **`NETWORK_ERROR`** — the request didn't complete.
* **`TRANSFORM_ERROR`** — the event couldn't be built into a valid payload
  before it ever left. **Never retried** — the same input produces the same
  broken payload every time. Fix the event, not the destination.

## Retryable vs permanent

The classification is mechanical:

| Response        | Outcome                |
| --------------- | ---------------------- |
| `2xx`           | `sent`                 |
| `429` or `5xx`  | Retryable → `retrying` |
| `401` / `403`   | `AUTH_ERROR`           |
| Any other `4xx` | Permanent → `failed`   |

> **Note**
>
> A `401` that survives a token refresh is treated as **permanent**. If refreshing
> the token and retrying still gets rejected, the grant itself is dead — retrying
> further just burns attempts against a revoked authorization.

Transform failures never retry, regardless of anything else.

## Platform quirks worth knowing

#### TikTok returns HTTP 200 on errors

TikTok answers with `200` and puts the real result in the body's `code`
field. Retryable codes are **40100, 40101, 40500, 50000, 50001**; every
other non-zero code is permanent. A `failed` TikTok delivery therefore never
corresponds to a non-2xx HTTP status.

#### Reddit rejects events older than 7 days

Reddit is the only destination with an age cap. Events older than seven days
are rejected outright — relevant if you backfill historical conversions or
if a queue backs up for days. Reddit also expects **millisecond**
timestamps where every other platform uses seconds.

#### Google Ads sends nothing without a mapping

Unmapped events are `skipped`, not `failed`. If Google shows no deliveries
at all, check your event-to-conversion-action mapping first — it's far more
often that than a credential problem.

> **Warning**
>
> An unsubscribed workspace records every event but dispatches none of them.
> Delivery is the paid part — if you see no delivery attempts whatsoever across
> all destinations, check the subscription before debugging any of the above.