> ## 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.

# Twitter webhook deliveries & retry history

> Inspect a webhook's 100 most recent tweet and profile event deliveries with attempts, HTTP statuses, errors, and timestamps. Listing deliveries is free.

<Panel>
  <Tabs defaultTabIndex={0} sync={false}>
    <Tab title="200" id="response-webhooks-deliveries-200">
      ```json theme={null}
      {
        "deliveries": [],
        "hasMore": false
      }
      ```
    </Tab>

    <Tab title="400" id="response-webhooks-deliveries-400">
      ```json theme={null}
      {
        "error": "invalid_input",
        "message": "Invalid input. Check the request body."
      }
      ```
    </Tab>

    <Tab title="401" id="response-webhooks-deliveries-401">
      ```json theme={null}
      {
        "error": "unauthenticated",
        "message": "Authentication required. Provide a valid API key or bearer token."
      }
      ```
    </Tab>

    <Tab title="404" id="response-webhooks-deliveries-404">
      ```json theme={null}
      {
        "error": "not_found",
        "message": "Resource not found."
      }
      ```
    </Tab>

    <Tab title="429" id="response-webhooks-deliveries-429">
      ```json theme={null}
      {
        "error": "rate_limit_exceeded",
        "message": "Too many requests. Try again later.",
        "retryAfter": 60
      }
      ```
    </Tab>
  </Tabs>
</Panel>

<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.** This endpoint 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 == "failed" then "fix_receiver"
        elif .status == "exhausted" then "resume_webhook"
        elif .status == "pending" then "wait"
        else "done"
        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 deliveryActions = {
    delivered: "done",
    exhausted: "resume_webhook",
    failed: "fix_receiver",
    pending: "wait",
  };
  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: deliveryActions[delivery.status],
  }));
  const failedDeliveries = deliveryTriage.filter((row) => row.action === "fix_receiver");
  ```

  ```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_actions = {
      "delivered": "done",
      "exhausted": "resume_webhook",
      "failed": "fix_receiver",
      "pending": "wait",
  }
  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": delivery_actions[delivery["status"]],
      }
      for delivery in data["deliveries"]
  ]
  failed_deliveries = [
      row for row in delivery_triage if row["action"] == "fix_receiver"
  ]
  ```

  ```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 {
    switch delivery.Status {
    case "failed":
      return "fix_receiver"
    case "exhausted":
      return "resume_webhook"
    case "pending":
      return "wait"
    }
    return "done"
  }

  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)
    }

    failedDeliveries := make([]DeliveryTriage, 0)
    for _, row := range deliveryTriage {
      if row.Action == "fix_receiver" {
        failedDeliveries = append(failedDeliveries, row)
      }
    }
    _ = failedDeliveries
  }
  ```
</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 check webhook delivery status. It returns the 100 most recent delivery records for one webhook, newest first. Follow `nextCursor` for older ones.

Map each delivery to a small incident row before you pass it on. Keep `id`, `streamEventId`, `status`, `attempts`, receiver status, timestamps, and the chosen action. Do not write the full response to 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 has none. Triage incidents from `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt`.

| Webhook delivery incident column | Response source | Triage rule |
| - | - | - |
| Delivery ID | `deliveries[].id` | Use this ID for idempotency and support lookup. |
| Monitor event ID | `deliveries[].streamEventId` | Join the delivery to `GET /events/{id}`. |
| Delivery state | `deliveries[].status` | Handle pending, failed, exhausted, and delivered rows separately. |
| Attempt count | `deliveries[].attempts` | Count tries so far. There is no attempt limit. |
| Receiver status | `deliveries[].lastStatusCode` | Separate HTTP failures from unreachable receivers. |
| Failure reason | `deliveries[].lastError` | Show the latest receiver error to operators. |
| Delivery timing | `createdAt` and `deliveredAt` | Measure queue time and recovery time. |

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 see how often Xquik tried a delivery. Retries continue
    until your endpoint returns `2xx`.
  </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 row per delivery when the receiver owner must act. Ignore
`delivered` rows, wait on `pending`, and act on `failed`. An `exhausted` row
means the webhook was paused or deleted.

```json theme={null}
{
  "record_type": "webhook_delivery_incident_handoff",
  "webhook_id": "15",
  "delivery_id": "503",
  "stream_event_id": "9003",
  "status": "failed",
  "attempts": 4,
  "receiver_status": 503,
  "last_error": "HTTP 503",
  "event_join": "GET /api/v1/events/9003",
  "verification_check": "POST /api/v1/webhooks/15/test",
  "action": "fix_receiver",
  "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="No final failure" icon="rotate-ccw">
    Xquik retries every `failed` delivery until your endpoint returns `2xx`.
    No delivery ends early, even after `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>

Xquik retries failed deliveries with no attempt limit. While your endpoint fails, Xquik retries 1 waiting delivery at a time. It checks the endpoint at least every 15 minutes. New deliveries wait as `pending`. Return `2xx` for a delivery you will never accept. See the [retry policy](/webhooks/overview#retry-policy).

Fix the receiving endpoint first. Use [`POST /webhooks/{id}/test`](/api-reference/webhooks/test) to confirm it accepts signed requests. Then call [Resume Webhook](/api-reference/webhooks/resume) to start sending waiting and rejected deliveries at once. Without it, a rejected delivery can wait up to 7 days.

### Receiver backfill handoff

This endpoint returns 100 delivery rows per page for 1 webhook, newest first. Xquik
resends failed deliveries on its own. Use stored event pages only to rebuild
work your receiver lost.

```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&cursor={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_lost_events"
}
```

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

## Query parameters

<ParamField query="limit" type="integer">
  Maximum deliveries per page: 1 to 100, default 100.
</ParamField>

<ParamField query="cursor" type="string">
  `nextCursor` from the previous page. The `after` alias also works. Offset pagination is not
  supported.
</ParamField>

## Response

### 200 OK

<ResponseField name="deliveries" type="array">
  List of delivery attempts, most recent first. Returns up to 100 deliveries per page.
  **Delivery object fields.**

  <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. There is no attempt limit.
  </ResponseField>

  <ResponseField name="lastStatusCode" type="number">
    HTTP status code returned by your endpoint on the most recent attempt. Omitted if Xquik has made no attempt 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 Xquik queued the delivery.
  </ResponseField>

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

<ResponseField name="hasMore" type="boolean">
  Whether more rows follow this page.
</ResponseField>

<ResponseField name="nextCursor" type="string">
  Pass it as `cursor` for the next page. Present when `hasMore` is `true`.
</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": "failed",
      "attempts": 12,
      "lastStatusCode": 410,
      "lastError": "HTTP 410",
      "createdAt": "2026-02-24T14:30:00.000Z"
    },
    {
      "id": "504",
      "streamEventId": "9004",
      "status": "pending",
      "attempts": 0,
      "createdAt": "2026-02-24T14:35:00.000Z"
    }
  ],
  "hasMore": false
}
```

### 400 Invalid cursor

```json theme={null}
{
  "error": "invalid_input",
  "message": "Cursor invalid. Use nextCursor from the previous page, or omit it to start over."
}
```

The `cursor` is not one this list returned. Send the `nextCursor` of the previous page, or omit it.

### 401 Unauthenticated

```json theme={null}
{ "error": "unauthenticated" }
```

Missing or invalid API key. Check the `x-api-key` header value.

### 400 Invalid ID

```json theme={null}
{ "error": "invalid_id" }
```

The provided webhook ID is not a valid format.

### 404 Not found

```json theme={null}
{ "error": "not_found" }
```

No webhook exists with this ID, or it belongs to a different account.

### 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.

## Delivery statuses

<CardGroup cols={2}>
  <Card title="pending" icon="clock">
    Xquik has not sent this delivery yet. It may wait while your endpoint is
    failing.
  </Card>

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

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

  <Card title="exhausted" icon="pause">
    You paused or deleted the webhook while this delivery waited. Resume it
    to receive waiting deliveries.
  </Card>
</CardGroup>

Xquik retries deliveries until your endpoint returns `2xx`. Retries never stop on their own. Pausing or deleting the webhook stops them. Within 1 day, its waiting deliveries show `exhausted`. Xquik keeps queued events until all deliveries finish. Events expire 30 days after Xquik creates them.

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.