---
title: "Recent delivery attempts"
method: GET
path: "/v1/webhooks/{id}/deliveries"
tags: ["Delivery webhooks"]
---

# Recent delivery attempts

`GET /v1/webhooks/{id}/deliveries`

## Request

- Base URL: `https://api.cadencercs.com`
- URL: `https://api.cadencercs.com/v1/webhooks/{id}/deliveries`
- Auth: HTTP bearer

Each attempt with your endpoint's own status and the first 2 kB of its response. A webhook you cannot inspect is one you debug by guessing.

## Path parameters

- `id` string, required

## Query parameters

- `limit` integer

## Response `200`

Attempts, newest first.

- object
  - `webhookId` string, required
  - `deliveries` WebhookDelivery[], required
    - `id` string, required
    - `eventId` string, required
    - `eventType` string, required
    - `conversationId` string, nullable
    - `attempts` number, required
    - `status` 'delivered' | 'pending' | 'abandoned', required
    - `responseStatus` number, nullable: What your endpoint returned.
    - `responseBody` string, nullable: First 2 kB of your endpoint's response — kept so "my handler 500s on your payload" is answerable.
    - `lastError` string, nullable
    - `nextAttemptAt` string, nullable
    - `deliveredAt` string, nullable
    - `createdAt` string
    - `payload` DeliveryEvent: The body we POST. Signed with `Cadence-Signature: t=<unix>,v1=<hex>` — recompute HMAC-SHA256 over the exact string `${t}.${rawBody}` and compare in constant time. **Sign the raw bytes**; re-serialising the parsed JSON often produces identical bytes today, which is the trap — it passes your tests and fails later when a proxy reformats the body or a number normalises. Reject a `t` outside your tolerance (ours is 300s). Also sent as headers: `Cadence-Event`, `Cadence-Event-Id`, `Cadence-Delivery-Id`, `Cadence-Attempt` — enough to route and dedupe before parsing the body. Deliveries are at-least-once: dedupe on `Cadence-Event-Id`.
      - `id` string, required: Stable per event. Retries of the same event reuse it — dedupe on this.
      - `type` 'reply' | 'fellback' | 'help' | 'optout' | 'optin' | 'completed' | 'ping', required
      - `createdAt` string, required: ISO 8601.
      - `data` object, required
        - `conversationId` string, required
        - `agentId` string, required
        - `brandId` string, nullable
        - `to` string: The recipient, E.164.
        - `type` string: Same as the envelope's, kept for callers that only read `data`.
        - `intent` 'free_text' | 'postback' | 'optin' | 'optout' | 'help' | 'media': How the reply was classified. `optout` and `help` arrive as their own event types rather than as a `reply`, so a `reply` carries `free_text` or `postback` in practice; `optin` (START) reverses an opt-out and is reported on the reply that carried it.
        - `body` string: What they sent.
        - `value` string: A chip's postback payload, when the reply was a tap.
        - `optionIndex` number: **1-based** position in the chips offered — the same number the SMS fallback names. Prefer `optionLabel` over indexing.
        - `optionLabel` string
        - `trustLevel` string
        - `channel` 'rcs' | 'sms': On `fellback`: the conversation's channel **after** the fallback. `sms` means the recipient cannot receive RCS and the conversation flipped for good. `rcs` means one message degraded — our payload or your media URL was rejected — and rich sends still work.
        - `fallback` FellBack
          - `cause` 'recipient' | 'sender', required: `recipient`: their handset has no RCS, or your agent is not launched on their carrier. `sender`: we were rejected — a bad payload, a media URL Google could not fetch, a timeout. Nothing was learned about the recipient.
          - `reason` string, required: Google's or the carrier's own words, trimmed.
          - `ourFault` boolean, required: True when the fallback says nothing about the recipient. Retrying rich is reasonable once the reason is fixed.
          - `remediation` string
          - `mediaRejected` object: Present when Google refused the message because of the file behind `media.url` or a card's media. The URL must answer GET and HEAD with 200, a real Content-Type and Content-Length, over public https with no login. The recipient is reachable; the SMS carried the fallback text.
            - `url` string, required: The file Google refused.
            - `stage` 'google_fetch', required: Google fetches `media.url` itself; we never see the HTTP exchange.
            - `status` number: The HTTP status Google's error names, when it names one.
            - `reason` string, required: Google's sentence about the file.
        - `completion` number: On `completed`: fraction of base steps answered.
        - `resubscribed` boolean: On `optin`: whether this reversed an opt-out, or was a stray START.
        - `answers` FlowAnswer[]: On `completed`: the answers themselves, so a handler woken by this event does not have to call back for the thing the event is about. This **is** the array — it briefly carried the whole `/answers` object with the array nested inside, which is a reasonable way to earn a TypeError from a field called `answers`.
          - `stepId` string, required
          - `question` string, nullable: The step's text, so results read without holding the flow definition alongside.
          - `text` string, required: What they actually sent.
          - `optionIndex` number: **1-based position among the REPLY chips** — the same number the SMS fallback tells the recipient to reply with, so it means the same thing on both legs. Action chips are not counted and never carry one: there is no number you could reply with to open a URL. Absent on an action tap, which arrives with `optionLabel` and `chipAction` instead. `optionLabel` is provided so you never have to index at all.
          - `optionLabel` string: The matched chip's label.
          - `freeText` boolean: True when they typed something rather than choosing an offered option.
        - `flow` object
          - `flowId` string, required
          - `flowName` string
          - `done` boolean, required
          - `completion` number
          - `currentStepId` string, nullable
  - `note` string

## Other responses

- `401`: Not signed in, and no account-linked API key was sent.
- `404`: No such webhook on this account.
- `500`: Unexpected server error. The body is always JSON.

---

[API](https://skmtc.dev/cadencercs/apis/cadence-api.md) · [All operations](https://skmtc.dev/cadencercs/apis/cadence-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cadencercs/apis/cadence-api/revisions/9438443c2081?raw)
