---
title: "List webhook deliveries"
method: GET
path: "/v1/projects/{project_id}/webhook-deliveries"
tags: ["Webhooks"]
---

# List webhook deliveries

`GET /v1/projects/{project_id}/webhook-deliveries`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/webhook-deliveries`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

Every delivery attempted in the project, newest first — what was sent, where, how many times, and what came back. Filter by endpoint, by lifecycle status, or by event type.
Deliveries are per (event, endpoint): an event matching two subscribed endpoints produces two rows, retried and observed independently.
A `success` or `failed` delivery is deleted 30 days after it was created, and can no longer be read or redelivered. A `pending` one is kept until it settles.

## Query parameters

- `limit` integer
- `cursor` string
- `webhook_id` string
- `status` 'pending' | 'success' | 'failed'
- `type` string

## Response `200`

A page of deliveries.

- WebhookDeliveryList
  - `data` WebhookDelivery[], required
    - `id` string, required: Public delivery ID (whd_ prefix). Sent with the request as `X-Naturali-Delivery`.
    - `project_id` string, required
    - `webhook_id` string, required
    - `event_id` string, required: The event's id; shared by every delivery and redelivery of it.
    - `type` 'conversation.started' | 'message.received' | 'generation.completed' | 'generation.failed' | 'usage.threshold_crossed' | 'project.paused' | 'project.resumed' | 'decision.completed' | 'decision.failed' | 'task.created' | 'task.transitioned' | 'task.closed' | 'task.migrated' | 'task.stalled' | 'task.paused' | 'task.resumed' | 'task.approval_failed' | 'task.automation_retrying' | 'task.automation_rejected' | 'task.automation_unrouted' | 'approval.created' | 'approval.approved' | 'approval.rejected' | 'approval.expired', required: A naturali event type. The set below is what this API emits — deliberately only the events it can itself cause, so no declared name is one that never fires. It grows as modules land.
    - `payload` Event, required: The envelope POSTed to your endpoint. It is also what a delivery's `payload` holds, byte for byte, so the signed body and the recorded one are the same document.
      - `id` string, required: Unique per event (`evt_` prefix), and stable across redeliveries — dedupe on this if your receiver must process an event exactly once.
      - `type` 'conversation.started' | 'message.received' | 'generation.completed' | 'generation.failed' | 'usage.threshold_crossed' | 'project.paused' | 'project.resumed' | 'decision.completed' | 'decision.failed' | 'task.created' | 'task.transitioned' | 'task.closed' | 'task.migrated' | 'task.stalled' | 'task.paused' | 'task.resumed' | 'task.approval_failed' | 'task.automation_retrying' | 'task.automation_rejected' | 'task.automation_unrouted' | 'approval.created' | 'approval.approved' | 'approval.rejected' | 'approval.expired', required: A naturali event type. The set below is what this API emits — deliberately only the events it can itself cause, so no declared name is one that never fires. It grows as modules land.
      - `project_id` string, required
      - `resource_type` 'conversation' | 'message' | 'generation' | 'usage_threshold' | 'project' | 'decision' | 'task' | 'approval', required: What the event is about.
      - `resource_id` string, required: The id of that resource, in the form its own API uses.
      - `data` object, required: The resource, shaped exactly as its own API returns it — a `generation.completed` payload carries the same object `getGeneration` would.
      - `created_at` string, date-time, required
    - `status` 'pending' | 'success' | 'failed', required: `pending` until a 2xx is received (`success`) or the attempts are exhausted (`failed`). A failed delivery can be replayed with `…:redeliver`.
    - `status_code` integer, nullable, required: The receiver's HTTP status on the last attempt; null when the attempt never got a response (DNS, TLS, refused, timed out).
    - `attempts` integer, required: Attempts made so far.
    - `next_attempt_at` string, date-time, nullable, required: When the next retry is due; null once the delivery is terminal.
    - `last_attempt_at` string, date-time, nullable, required
    - `response_body` string, nullable, required: A truncated snippet of the receiver's response, or the transport error when there was no response.
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
  - `next_cursor` string, nullable, required: Cursor for the next page, or null when there are no more.

## Other responses

- `400`: The request was malformed or failed validation.
- `401`: Missing or invalid credentials.
- `404`: The resource does not exist (existence is not leaked).

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
