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

# List deliveries

> View delivery attempts and their statuses for a specific webhook

<blockquote className="agent-llms-directive">
  For the complete documentation index, see <a href="/llms.txt">llms.txt</a>.
</blockquote>

<Callout icon="circle-check" color="#16a34a">
  **Free** - does not consume credits
</Callout>

<CodeGroup>
  ```bash cURL theme={null}
  curl https://xquik.com/api/v1/webhooks/15/deliveries \
    -H "x-api-key: xq_YOUR_KEY_HERE" \
    | jq '[.deliveries[] | {
      delivery_id: .id,
      stream_event_id: .streamEventId,
      status,
      attempts,
      receiver_status: (.lastStatusCode // null),
      last_error: (.lastError // null),
      queued_at: .createdAt,
      delivered_at: (.deliveredAt // null),
      action: (
        if .status == "exhausted" then "page"
        elif .status == "failed" and .attempts >= 3 then "warn"
        else "track"
        end
      )
    }]'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://xquik.com/api/v1/webhooks/15/deliveries",
    {
      headers: { "x-api-key": "xq_YOUR_KEY_HERE" },
    }
  );
  const data = await response.json();
  const deliveryTriage = data.deliveries.map((delivery) => ({
    delivery_id: delivery.id,
    stream_event_id: delivery.streamEventId,
    status: delivery.status,
    attempts: delivery.attempts,
    receiver_status: delivery.lastStatusCode ?? null,
    last_error: delivery.lastError ?? null,
    queued_at: delivery.createdAt,
    delivered_at: delivery.deliveredAt ?? null,
    action:
      delivery.status === "exhausted"
        ? "page"
        : delivery.status === "failed" && delivery.attempts >= 3
          ? "warn"
          : "track",
  }));
  const retryCandidates = deliveryTriage.filter((row) => row.action !== "track");
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      "https://xquik.com/api/v1/webhooks/15/deliveries",
      headers={"x-api-key": "xq_YOUR_KEY_HERE"},
  )
  data = response.json()
  delivery_triage = [
      {
          "delivery_id": delivery["id"],
          "stream_event_id": delivery["streamEventId"],
          "status": delivery["status"],
          "attempts": delivery["attempts"],
          "receiver_status": delivery.get("lastStatusCode"),
          "last_error": delivery.get("lastError"),
          "queued_at": delivery["createdAt"],
          "delivered_at": delivery.get("deliveredAt"),
          "action": "page"
          if delivery["status"] == "exhausted"
          else "warn"
          if delivery["status"] == "failed" and delivery["attempts"] >= 3
          else "track",
      }
      for delivery in data["deliveries"]
  ]
  retry_candidates = [
      row for row in delivery_triage if row["action"] != "track"
  ]
  ```

  ```go Go theme={null}
  package main

  import (
    "encoding/json"
    "log"
    "net/http"
  )

  type Delivery struct {
    ID             string  `json:"id"`
    StreamEventID  string  `json:"streamEventId"`
    Status         string  `json:"status"`
    Attempts       int     `json:"attempts"`
    LastStatusCode *int    `json:"lastStatusCode"`
    LastError      *string `json:"lastError"`
    CreatedAt      string  `json:"createdAt"`
    DeliveredAt    *string `json:"deliveredAt"`
  }

  type DeliveriesResponse struct {
    Deliveries []Delivery `json:"deliveries"`
  }

  type DeliveryTriage struct {
    DeliveryID     string  `json:"delivery_id"`
    StreamEventID  string  `json:"stream_event_id"`
    Status         string  `json:"status"`
    Attempts       int     `json:"attempts"`
    ReceiverStatus *int    `json:"receiver_status"`
    LastError      *string `json:"last_error"`
    QueuedAt       string  `json:"queued_at"`
    DeliveredAt    *string `json:"delivered_at"`
    Action         string  `json:"action"`
  }

  func actionForDelivery(delivery Delivery) string {
    if delivery.Status == "exhausted" {
      return "page"
    }
    if delivery.Status == "failed" && delivery.Attempts >= 3 {
      return "warn"
    }
    return "track"
  }

  func main() {
    req, err := http.NewRequest("GET", "https://xquik.com/api/v1/webhooks/15/deliveries", nil)
    if err != nil {
      log.Fatal(err)
    }
    req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
      log.Fatal(err)
    }
    defer resp.Body.Close()

    var data DeliveriesResponse
    if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
      log.Fatal(err)
    }

    deliveryTriage := make([]DeliveryTriage, 0, len(data.Deliveries))
    for _, delivery := range data.Deliveries {
      row := DeliveryTriage{
        DeliveryID:     delivery.ID,
        StreamEventID:  delivery.StreamEventID,
        Status:         delivery.Status,
        Attempts:       delivery.Attempts,
        ReceiverStatus: delivery.LastStatusCode,
        LastError:      delivery.LastError,
        QueuedAt:       delivery.CreatedAt,
        DeliveredAt:    delivery.DeliveredAt,
        Action:         actionForDelivery(delivery),
      }
      deliveryTriage = append(deliveryTriage, row)
    }

    retryCandidates := make([]DeliveryTriage, 0)
    for _, row := range deliveryTriage {
      if row.Action != "track" {
        retryCandidates = append(retryCandidates, row)
      }
    }
    _ = retryCandidates
  }
  ```
</CodeGroup>

## Path parameters

<ParamField path="id" type="string" required>
  The webhook ID to retrieve deliveries for.
</ParamField>

## Headers

<ParamField header="x-api-key" type="string" required>
  Your API key.
</ParamField>

## Operational handoff

Use this endpoint when your queue, CRM, warehouse, or alerting system needs to reconcile webhook delivery health. It returns the 100 most recent delivery records for one webhook, newest first.

Map each delivery into a small incident row before sending it downstream. Keep `id`, `streamEventId`, `status`, `attempts`, receiver status, timestamps, and the chosen action; avoid dumping the full response into logs.

This endpoint returns delivery attempt metadata only. It does not return the
webhook URL, event type filter, signing secret, raw payload body, raw signature,
or full request headers.

Do not depend on a `nextRetryAt` field. The response does not expose one; route incidents from `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt`.

Store these fields for support and retry triage:

<CardGroup cols={2}>
  <Card title="Delivery ID" icon="fingerprint">
    Store `id` for delivery-level idempotency and support lookup.
  </Card>

  <Card title="Event Join" icon="link">
    Store `streamEventId` to join back to the stored monitor event with
    `GET /events/{id}`.
  </Card>

  <Card title="Status Route" icon="route">
    Use `status` to route `pending`, `failed`, and `exhausted` deliveries to the
    right queue.
  </Card>

  <Card title="Attempt Count" icon="repeat-2">
    Use `attempts` to decide whether a failed delivery is early, repeated, or at
    the retry cap.
  </Card>

  <Card title="Receiver Result" icon="activity">
    Use `lastStatusCode` to separate receiver errors such as `500` from
    unreachable endpoints with status `0`.
  </Card>

  <Card title="Failure Reason" icon="triangle-alert">
    Show `lastError` as the most recent failure reason for the operator.
  </Card>

  <Card title="Timing" icon="timer">
    Compare `createdAt` and `deliveredAt` to measure delivery latency and
    recovery time.
  </Card>
</CardGroup>

## Incident response handoff

Use one compact handoff row when a delivery needs receiver-owner action. Route
`delivered` rows out of the incident path, track `pending`, warn on repeated
`failed`, and page on `exhausted`.

```json theme={null}
{
  "record_type": "webhook_delivery_incident_handoff",
  "webhook_id": "15",
  "delivery_id": "503",
  "stream_event_id": "9003",
  "status": "exhausted",
  "attempts": 1,
  "receiver_status": 410,
  "last_error": "HTTP 410",
  "terminal_reason": "receiver_returned_410",
  "event_join": "GET /api/v1/events/9003",
  "verification_check": "POST /api/v1/webhooks/15/test",
  "action": "page_receiver_owner",
  "handoff_state": "fix_receiver_then_send_signed_test"
}
```

<CardGroup cols={2}>
  <Card title="Repeated failure" icon="repeat-2">
    Warn after repeated `failed` rows. Include `attempts`, `lastStatusCode`,
    and `lastError` so the receiver owner can separate code errors from
    reachability failures.
  </Card>

  <Card title="Terminal delivery" icon="circle-x">
    Page when `status` is `exhausted`. This means retryable failures reached
    the 10-attempt cap, or the receiver returned `410 Gone`.
  </Card>

  <Card title="Event join" icon="link">
    Store `streamEventId` and link `GET /api/v1/events/{id}` so support can
    inspect the monitor event that triggered the delivery.
  </Card>

  <Card title="Receiver proof" icon="shield-check">
    After fixing the endpoint, send `POST /webhooks/{id}/test` and attach the
    signed test result to the incident before waiting for the next event.
  </Card>
</CardGroup>

Failures retry up to 10 attempts with exponential backoff, starting at 1 second and capped at 60 seconds. A `410 Gone` response marks the delivery `exhausted` immediately. Other non-`2xx` responses and network failures stay `failed` until they are delivered or exhaust all attempts.

For incident response, page on `exhausted`, warn on repeated `failed`, and ignore `delivered`. Fix the receiving endpoint, then use [`POST /webhooks/{id}/test`](/api-reference/webhooks/test) to confirm it accepts signed requests before waiting for the next production event.

### Receiver backfill handoff

This endpoint returns the latest 100 delivery rows for one webhook. Use those
rows to identify receiver failures, then use stored event pages to rebuild your
own downstream queue after the receiver is fixed.

```json theme={null}
{
  "record_type": "webhook_delivery_backfill_handoff",
  "delivery_source": "GET /api/v1/webhooks/15/deliveries",
  "event_source": "GET /api/v1/events?limit=100&after={nextCursor}",
  "source_filter": "monitorId for account monitors, keywordMonitorId for keyword monitors",
  "join_key": "delivery.streamEventId == event.id",
  "store": [
    "deliveryId",
    "streamEventId",
    "status",
    "attempts",
    "eventId",
    "nextCursor"
  ],
  "stop_when": "hasMore is false",
  "handoff_state": "receiver_fixed_backfill_events_then_resume_webhooks"
}
```

Store `nextCursor` after every event page. Continue
`GET /api/v1/events?limit=100&after={nextCursor}` until `hasMore` is `false`,
then compare each event `id` with delivery `streamEventId` values before
replaying your own downstream work. Add `monitorId` when replaying one account
monitor, or `keywordMonitorId` when replaying one keyword monitor.

## Response

<Tabs>
  <Tab title="200 OK">
    <ResponseField name="deliveries" type="array">
      List of delivery attempts, most recent first. Returns up to 100 deliveries.

      <Expandable title="delivery object">
        <ResponseField name="id" type="string">
          Unique delivery identifier.
        </ResponseField>

        <ResponseField name="streamEventId" type="string">
          ID of the stream event that triggered this delivery.
        </ResponseField>

        <ResponseField name="status" type="string">
          Current delivery status: `pending`, `delivered`, `failed`, or `exhausted`.
        </ResponseField>

        <ResponseField name="attempts" type="number">
          Total number of delivery attempts made.
        </ResponseField>

        <ResponseField name="lastStatusCode" type="number">
          HTTP status code returned by your endpoint on the most recent attempt. Omitted if no attempt has been made yet.
        </ResponseField>

        <ResponseField name="lastError" type="string">
          Error message from the most recent failed attempt. Omitted on success.
        </ResponseField>

        <ResponseField name="createdAt" type="string">
          ISO 8601 timestamp of when the delivery was queued.
        </ResponseField>

        <ResponseField name="deliveredAt" type="string">
          ISO 8601 timestamp of successful delivery. Omitted if not yet delivered.
        </ResponseField>
      </Expandable>
    </ResponseField>

    ```json theme={null}
    {
      "deliveries": [
        {
          "id": "501",
          "streamEventId": "9001",
          "status": "delivered",
          "attempts": 1,
          "lastStatusCode": 200,
          "createdAt": "2026-02-24T14:22:01.000Z",
          "deliveredAt": "2026-02-24T14:22:02.000Z"
        },
        {
          "id": "502",
          "streamEventId": "9002",
          "status": "failed",
          "attempts": 3,
          "lastStatusCode": 500,
          "lastError": "HTTP 500",
          "createdAt": "2026-02-24T14:25:00.000Z"
        },
        {
          "id": "503",
          "streamEventId": "9003",
          "status": "exhausted",
          "attempts": 10,
          "lastStatusCode": 503,
          "lastError": "HTTP 503",
          "createdAt": "2026-02-24T14:30:00.000Z"
        },
        {
          "id": "504",
          "streamEventId": "9004",
          "status": "pending",
          "attempts": 0,
          "createdAt": "2026-02-24T14:35:00.000Z"
        }
      ]
    }
    ```
  </Tab>

  <Tab title="401 Unauthenticated">
    ```json theme={null}
    { "error": "unauthenticated" }
    ```

    Missing or invalid API key. Check the `x-api-key` header value.
  </Tab>

  <Tab title="400 Invalid ID">
    ```json theme={null}
    { "error": "invalid_id" }
    ```

    The provided webhook ID is not a valid format.
  </Tab>

  <Tab title="404 Not Found">
    ```json theme={null}
    { "error": "not_found" }
    ```

    No webhook exists with this ID, or it belongs to a different account.
  </Tab>

  <Tab title="429 Rate Limited">
    ```json theme={null}
    { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 }
    ```

    Too many requests. Wait for the `Retry-After` header before retrying.
  </Tab>
</Tabs>

## Delivery statuses

<CardGroup cols={2}>
  <Card title="pending" icon="clock">
    Delivery is queued and waiting for the next attempt.
  </Card>

  <Card title="delivered" icon="circle-check">
    Your endpoint returned `2xx`. Delivery is complete.
  </Card>

  <Card title="failed" icon="triangle-alert">
    The most recent attempt failed because the endpoint returned non-`2xx` or
    the network request failed. Xquik retries with exponential backoff.
  </Card>

  <Card title="exhausted" icon="circle-x">
    All retry attempts have been used. Xquik will not retry this delivery.
    Check the receiver endpoint, then use
    [Resume Webhook](/api-reference/webhooks/resume) if the webhook needs
    attention before new deliveries continue.
  </Card>
</CardGroup>

Deliveries follow an exponential backoff retry schedule. After each failed attempt, the wait time increases. Once all retries are exhausted, the status transitions to `exhausted` and no further attempts are made.

<Note>
  **Related:** [Webhooks Overview](/webhooks/overview) · [Resume Webhook](/api-reference/webhooks/resume) · [Webhook Testing Guide](/guides/webhook-testing)
</Note>
