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

# Debugging Deliveries

> See what Coalesce Quality sent to your webhook, PagerDuty or Opsgenie integration, why a notification did not arrive, and replay it

# Deliveries

Every notification Coalesce Quality sends to one of your integrations is recorded: the request as it was sent, the response your endpoint returned, how long it took, and — when nothing was sent at all — the reason. Open it under **Settings > Integrations > your integration > Deliveries**.

The tab is available for every integration that receives notifications: **webhooks**, **PagerDuty** and **Opsgenie**. Everything below applies to all three, and everything visible there is also readable over the API — see [Over the API](#over-the-api).

Deliveries are kept for **90 days**.

## What a delivery is

One **delivery** is one event addressed to one integration.

An event that matches three integrations produces three deliveries. They share an **event id** and each has its own delivery id. The event id is the same `event_id` your endpoint received in the webhook payload, so you can go from a payload you have in hand to the delivery that produced it — and to every other integration that got the same event.

Each delivery has one or more **attempts**. An attempt is one HTTP request and whatever came back.

## Outcomes

| Outcome        | What it means                                                                                                            | What to do                                                        |
| -------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- |
| **Delivered**  | Your endpoint accepted it. Only a `2xx` counts.                                                                          | Nothing.                                                          |
| **Pending**    | Accepted and queued, with an attempt still to come. The row shows when the next attempt is due.                          | Wait. If it stays pending, check that your endpoint is reachable. |
| **Failed**     | Your endpoint rejected it outright, or it was still failing when the retries ran out. Nothing further will be attempted. | Read the attempts, fix the endpoint, then **Redeliver**.          |
| **Skipped**    | The integration was found and its own configuration meant nothing was sent.                                              | See [Why nothing was sent](#why-nothing-was-sent).                |
| **Not routed** | No integration could be resolved for the event.                                                                          | See [Why nothing was sent](#why-nothing-was-sent).                |

A status code is not an outcome. A `500` that will be retried and a `500` that exhausted the retries are the same status and different outcomes, which is why the outcome is what to filter and alert on.

### Retries

A failing delivery is retried up to **10 times** with exponential backoff, over roughly 30 minutes.

* Only a `2xx` response counts as delivered.
* A `400`, `404` or `422` is treated as permanent and not retried: those say the request itself is wrong, and repeating it will not change the answer.
* A `5xx`, a timeout and a connection failure are retried.
* A `3xx` is **not followed** and is retried like any other transient failure. A delivery can carry credentials scoped to one host, so following a redirect could send them somewhere else. Point the integration at the final URL rather than at something that redirects.

Retries reuse the original signing timestamp, so a timestamp tolerance in your [signature verification](/api-reference/webhook#verifying-webhook-signatures) has to allow for the retry window.

## Why nothing was sent

A delivery that sent nothing always records a reason. Each one names something specific to change.

| Reason                                    | What it means                                                                                                                                                 | The fix                                                                    |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| **Event type not subscribed**             | The integration does not list this event type.                                                                                                                | Add it under the integration's event types.                                |
| **Event type unsupported by integration** | The destination has no representation for this kind of event. PagerDuty and Opsgenie carry issue and incident lifecycle events, not every webhook event type. | Nothing to fix. Use a webhook integration if you need this event type.     |
| **No action for lifecycle state**         | The change of state maps to no action on the destination — for example a status change that PagerDuty or Opsgenie has no equivalent for.                      | Nothing to fix.                                                            |
| **Integration not found**                 | The integration the event was addressed to no longer exists.                                                                                                  | Expected shortly after deleting an integration. Otherwise contact support. |
| **Integration disabled**                  | The integration exists but is switched off.                                                                                                                   | Enable it.                                                                 |
| **Empty event subject**                   | The event carried no issue or incident to describe.                                                                                                           | Contact support.                                                           |

This is the answer to "my alerting has gone quiet". A workspace whose deliveries are all **Skipped** with *Event type not subscribed* is configured to send nothing, which looks identical to a broken endpoint until you read the reason.

<Note>
  Deliveries describe what happened **after** an event was routed to an integration. An alert that was suppressed before that, or that matched no ownership, produces no delivery at all — so an event you expected and cannot find here is a question about the alert configuration, not about the integration. Each delivery names the alert configuration that selected it, which is where to start.
</Note>

## Reading a request and a response

Expand a delivery to see each attempt: the method and URL, the request headers and body, the response status, headers and body, and the duration.

### Withheld header values

A header whose value carries a credential is reported by name but its value is never returned. That covers:

* headers you marked **secret** on the integration,
* credentials Coalesce Quality holds for the destination, such as the Opsgenie API key,
* the standard credential-bearing names — `Authorization`, `Proxy-Authorization`, `Cookie`, `Set-Cookie`, `X-Api-Key`, `X-Auth-Token` — whatever they contain, on the request **or** the response side.

A withheld header shows a **fingerprint** instead: a short digest, stable for the same value. You cannot read the value from it, but you can compare two deliveries and tell whether the value changed — which is usually the actual question ("is this the same token as the one that worked yesterday"). Alongside it, the **secret version** says which stored secret the value was read from, so a delivery stays attributable across a rotation.

The header is still listed. A missing value means withheld, never "no header was sent".

### Signature headers are shown in full

`X-Coalesce-Signature` and `X-Coalesce-Timestamp` are returned complete. The signature is a digest of the body and the timestamp is a timestamp; neither is your signing secret, and reproducing a verification that failed needs the exact bytes. See [Verifying Webhook Signatures](/api-reference/webhook#verifying-webhook-signatures).

### Bodies

A request body value the destination requires to be secret — a PagerDuty routing key, for instance — is replaced by a marker naming the field it stood in, so the record shows a value was sent rather than implying none was. The reported request size is the real one, before that substitution.

A response body is kept up to a fixed size and marked as truncated past it, with the full size reported. The first error line is where the diagnostic value is.

## Redeliver

**Redeliver** sends a stored event again, to the same integration, with the same payload. The replay is a new delivery with its own id, linked to the original, and it appears in the feed like any other.

It is signed with the integration's **current** signing secret, not the one the original used. That makes it the way to confirm a [secret rotation](/api-reference/webhook#signing-secret) reached your receiver: rotate the secret, update your endpoint, redeliver an earlier event, and a **Delivered** outcome proves the new secret is accepted. There is no need to wait for a real alert, and no need to wait out the 24-hour dual-signing window to find out.

Redeliver is rate-limited per integration, so a replay loop cannot hammer an endpoint. Over the limit it is refused rather than queued.

## Send test event

**Send test event** delivers a test payload and reports the real outcome, rather than only that it was sent.

It is always sent, whatever event types the integration subscribes to. A test that silently matched no subscription would report exactly what a broken endpoint does, and telling those two apart is the only reason to run a test. On Opsgenie it opens a real alert, aliased so repeated tests do not stack up, because that is the only thing the integration does.

## Over the API

Everything above is available over the public API. Reading needs a token with **Read Deliveries** (`SCOPE_DELIVERY_READ`); replaying and testing need **Send and Replay Deliveries** (`SCOPE_DELIVERY_EDIT`). Create a token under [API settings](https://app.synq.io/settings/api) and get an access token as described in [Getting Started](/api-reference/getting-started).

The examples use the EU endpoint. For other regions use `api.us.synq.io` or `api.au.synq.io`.

### List deliveries

```bash theme={null}
curl -G https://developer.synq.io/api/deliveries/v1 \
  -H "Authorization: Bearer $TOKEN" \
  -d integrationId=d577b364-a867-11ed-b4b2-fe8020e7ba25 \
  -d since=2026-08-01T00:00:00Z \
  -d pagination.pageSize=50
```

Deliveries come back newest event first. Pass the response's `pageInfo.lastId` as `pagination.cursor` for the next page; an empty `lastId` means there are no more.

Omit `integrationId` for the whole workspace's feed. Add `includeRouting=true` to get the alert configurations that selected each integration — it is off by default because it costs an extra read per delivery.

### Filter by outcome

```bash theme={null}
curl -G https://developer.synq.io/api/deliveries/v1 \
  -H "Authorization: Bearer $TOKEN" \
  -d integrationId=d577b364-a867-11ed-b4b2-fe8020e7ba25 \
  -d outcomes=OUTCOME_FAILED \
  -d since=2026-08-01T00:00:00Z
```

`outcomes` and `statusCodes` repeat, so `-d outcomes=OUTCOME_FAILED -d outcomes=OUTCOME_SKIPPED` matches either. `eventTypes` filters by event type name, and `eventId` narrows to a single event across every integration it reached.

### Find every integration one event reached

```bash theme={null}
curl -G https://developer.synq.io/api/deliveries/v1 \
  -H "Authorization: Bearer $TOKEN" \
  -d eventId=0b6f9c2e-1a2b-4c3d-9e8f-123456789abc
```

`eventId` is the `event_id` from the webhook payload your endpoint received.

### Fetch the attempts of a delivery

```bash theme={null}
curl https://developer.synq.io/api/deliveries/v1/<delivery-id>/attempts \
  -H "Authorization: Bearer $TOKEN"
```

Oldest attempt first. A delivery is attempted at most ten times, so the result is small and is not paginated. It is empty for a delivery that sent nothing, and for one still waiting on its first attempt.

The `<delivery-id>` is the `id` of a delivery from the list — an opaque value composed by the service. Pass it back as it came; do not build one yourself.

### Replay a delivery

```bash theme={null}
curl -X POST https://developer.synq.io/api/deliveries/v1/<delivery-id>/redeliver \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" -d '{}'
```

The response carries the new delivery's id. The send is asynchronous, so poll for the outcome until it leaves `OUTCOME_PENDING`:

```bash theme={null}
curl -X POST https://developer.synq.io/api/deliveries/v1/by-id \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ids": ["<replay-delivery-id>"]}'
```

An id that does not exist, or that has passed out of the 90-day window, is absent from the response rather than an error.

### Send a test delivery

```bash theme={null}
curl -X POST https://developer.synq.io/api/deliveries/v1/test \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"integrationId": "d577b364-a867-11ed-b4b2-fe8020e7ba25", "message": "checking the new endpoint"}'
```

Returns the id of the delivery it created; poll it the same way.

### Watch your own delivery failure rate

```bash theme={null}
curl -X POST https://developer.synq.io/api/deliveries/v1/summarise \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"integrationId": "d577b364-a867-11ed-b4b2-fe8020e7ba25", "since": "2026-08-12T00:00:00Z"}'
```

Counts the window by outcome, by skip reason and by HTTP status class, without paging through the feed. This is the call to alert on if you want to know about a broken receiver before someone notices a missing alert.

A full worked example in Go — list, filter, attempts, redeliver and summarise — is on the [examples](/api-reference/examples) page as `deliveries`.

## Related

* [Using Webhooks](/api-reference/webhook) — event types, payload schema, custom headers and signature verification.
* [PagerDuty](/alert-integrations/pagerduty) and [Opsgenie](/alert-integrations/opsgenie) — setting those integrations up.
* [API Scopes](/api-reference/scopes) — the full scope reference.
