---
title: Get webhook delivery
description: Read one delivery with the event’s exact JSON and every attempt.
doc_version: 0.1.0-preview
last_updated: 2026-09-14
---

# Get webhook delivery

`GET /webhooks/{webhook}/deliveries/{event}`

Read one delivery with the event’s exact JSON and every attempt.

**Permission:** Cluster key, or a scoped key with read on a namespace or collection that the webhook covers.

[API key types and authentication](/engine/reference/authentication.md).

Match a POST that your receiver logged by its batch_id (the webhook-id header) and sent_at.

Returns 404 when the webhook never queued the event, the event is older than 30 days, or the key cannot see it.

## Example request

```http
GET /webhooks/acme-status/deliveries/evt_01K6Q6N7D8E9F0G1H2J3K4M5N6 HTTP/1.1
Host: engine.example.com
Authorization: Bearer <GRAPHON_ENGINE_API_KEY>
```

## Path parameters

### `webhook`

**string · required**

Webhook ID with the whk_ prefix, or the webhook’s name.

### `event`

**string · required**

Event ID with the evt_ prefix, from the event body or the delivery log.

## Responses

### 200 · Success

A delivery whose first POST got 503 and whose retry got 204.

Content type: `application/json`.

#### Response headers

##### `x-engine-request-id`

**string · required**

The Engine request ID. Retain it when investigating a failed or unexpected request.

##### `x-request-id`

**string · required**

The X-Request-Id you sent, echoed when it is a valid ID. Otherwise, the Engine request ID.

```json
{
  "event_id": "evt_01K6Q6N7D8E9F0G1H2J3K4M5N6",
  "event_type": "file.status_changed",
  "occurred_at": "2026-10-06T10:05:10Z",
  "namespace": "acme",
  "collection": "incident-logs",
  "status": "succeeded",
  "attempt_count": 2,
  "next_attempt_at": null,
  "last_attempt": {
    "number": 2,
    "batch_id": "bat_01K6Q6N7D8E9F0G1H2J3K4M5N6",
    "event_count": 2,
    "status": "succeeded",
    "response_status": 204,
    "error": null,
    "duration_ms": 87,
    "sent_at": "2026-10-06T10:05:13Z"
  },
  "queued_at": "2026-10-06T10:05:11Z",
  "body": {
    "id": "evt_01K6Q6N7D8E9F0G1H2J3K4M5N6",
    "type": "file.status_changed",
    "occurred_at": "2026-10-06T10:05:10Z",
    "cluster": "graphon-engine",
    "namespace": "acme",
    "collection": "incident-logs",
    "job_id": "job_01K6Q6N7D8E9F0G1H2J3K4M5N6",
    "file_id": "fil_01K6Q6N7D8E9F0G1H2J3K4M5N6",
    "research_id": null,
    "data": {
      "status": "ready",
      "previous_status": "indexing",
      "credits": 12
    }
  },
  "attempts": [
    {
      "number": 1,
      "batch_id": "bat_01K6Q6N7D8E9F0G1H2J3K4M5N6",
      "event_count": 2,
      "status": "retry",
      "response_status": 503,
      "error": null,
      "duration_ms": 87,
      "sent_at": "2026-10-06T10:05:12Z"
    },
    {
      "number": 2,
      "batch_id": "bat_01K6Q6N7D8E9F0G1H2J3K4M5N6",
      "event_count": 2,
      "status": "succeeded",
      "response_status": 204,
      "error": null,
      "duration_ms": 87,
      "sent_at": "2026-10-06T10:05:13Z"
    }
  ]
}
```

#### Response fields

##### `attempt_count`

**integer · required**

POSTs of the event’s batch since the event was last queued. A replay starts the count again.

##### `attempts`

**array<object> · required**

Every POST of every batch that held the event in the last 30 days, oldest first, replays included.

###### `attempts[].batch_id`

**string · required**

The batch’s bat_ ID: the webhook-id header of this POST. Every retry of a batch keeps it. A replay uses a new batch.

###### `attempts[].duration_ms`

**integer · required**

Time from the start of the POST to the answer or the error, in milliseconds.

###### `attempts[].error`

**string · required · nullable**

Why the POST got no usable answer. Null when the receiver answered with a status other than 3xx.

###### `attempts[].event_count`

**integer · required**

Number of events in the batch.

###### `attempts[].number`

**integer · required**

Attempt number within the batch, from 1.

###### `attempts[].response_status`

**integer · required · nullable**

HTTP status of the receiver’s answer. Null when no answer came back.

###### `attempts[].sent_at`

**string (date-time) · required**

Time the POST was sent, as an ISO 8601 UTC timestamp.

###### `attempts[].status`

**string · required**

succeeded for a 2xx answer, retry when the batch will be sent again, failed when it will not.

- `succeeded`: The receiver answered 2xx.
- `retry`: The batch will be sent again.
- `failed`: The batch will not be sent again.

##### `body`

**map<string, JSON value> · required**

The event’s exact JSON inside its batch.

##### `collection`

**string · required · nullable**

Collection name that the event names, or null.

##### `event_id`

**string · required**

The event’s evt_ ID. It is the same at every webhook and in every retry or replay.

##### `event_type`

**string · required**

The event’s type, such as file.status_changed.

##### `last_attempt`

**object · required · nullable**

The last POST of the newest batch that held the event. Null before the first POST.

###### `last_attempt.batch_id`

**string · required**

The batch’s bat_ ID: the webhook-id header of this POST. Every retry of a batch keeps it. A replay uses a new batch.

###### `last_attempt.duration_ms`

**integer · required**

Time from the start of the POST to the answer or the error, in milliseconds.

###### `last_attempt.error`

**string · required · nullable**

Why the POST got no usable answer. Null when the receiver answered with a status other than 3xx.

###### `last_attempt.event_count`

**integer · required**

Number of events in the batch.

###### `last_attempt.number`

**integer · required**

Attempt number within the batch, from 1.

###### `last_attempt.response_status`

**integer · required · nullable**

HTTP status of the receiver’s answer. Null when no answer came back.

###### `last_attempt.sent_at`

**string (date-time) · required**

Time the POST was sent, as an ISO 8601 UTC timestamp.

###### `last_attempt.status`

**string · required**

succeeded for a 2xx answer, retry when the batch will be sent again, failed when it will not.

- `succeeded`: The receiver answered 2xx.
- `retry`: The batch will be sent again.
- `failed`: The batch will not be sent again.

##### `namespace`

**string · required · nullable**

Namespace name that the event names, or null.

##### `next_attempt_at`

**string (date-time) · required · nullable**

When the next POST of a retrying batch goes. Otherwise null.

##### `occurred_at`

**string (date-time) · required**

Time of the status change, as an ISO 8601 UTC timestamp.

##### `queued_at`

**string (date-time) · required**

When the event was queued for this webhook, or queued again by a replay.

##### `status`

**string · required**

pending (queued, or waiting for a retry), succeeded, or failed (a 3xx or 4xx answer, or the webhook turned off while the event waited).

- `pending`: Queued, or waiting for a retry.
- `succeeded`: The receiver answered 2xx to a batch that held the event.
- `failed`: Engine will not send the event again unless you replay it.

## Errors

[Error format and retry guidance](/engine/reference/errors.md).

### 400 · `invalid_request`

A field, path, parameter, or conditional request rule is invalid.

Correct the request. Unknown fields are rejected.

```json
{
  "error": {
    "code": "invalid_request",
    "message": "A field, path, parameter, or conditional request rule is invalid.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 401 · `unauthenticated`

The API key is missing, invalid, or revoked.

Send an active key in Authorization: Bearer or X-API-Key.

```json
{
  "error": {
    "code": "unauthenticated",
    "message": "The API key is missing, invalid, or revoked.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 403 · `forbidden`

The key can see the resource but lacks permission for this operation.

Use a key with the required action or scope kind.

```json
{
  "error": {
    "code": "forbidden",
    "message": "The key can see the resource but lacks permission for this operation.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 404 · `not_found`

The resource is absent, deleted, or outside this key’s scope.

Check the identifier and the key’s namespace and collection scope.

```json
{
  "error": {
    "code": "not_found",
    "message": "The resource is absent, deleted, or outside this key’s scope.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 503 · `temporarily_unavailable`

A dependency, cluster readiness condition, or capacity limit prevents this operation.

Inspect retryable and details. Retry only when permitted, preserving the idempotency key for the same write.

```json
{
  "error": {
    "code": "temporarily_unavailable",
    "message": "A dependency, cluster readiness condition, or capacity limit prevents this operation.",
    "request_id": "req_example",
    "retryable": true,
    "details": {}
  }
}
```

### 500 · `internal_error`

An unexpected server failure prevented this request from completing.

Retain request_id for investigation. Follow retryable before retrying a write.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An unexpected server failure prevented this request from completing.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

## Other request languages

### cURL

```bash
curl -sS "$GRAPHON_ENGINE_URL/webhooks/acme-status/deliveries/evt_01K6Q6N7D8E9F0G1H2J3K4M5N6" \
  -H "Authorization: Bearer $GRAPHON_ENGINE_API_KEY"
```

### Python

```python
import json
import os
import urllib.request

request = urllib.request.Request(
    os.environ["GRAPHON_ENGINE_URL"] + "/webhooks/acme-status/deliveries/evt_01K6Q6N7D8E9F0G1H2J3K4M5N6",
    headers={
        "Authorization": "Bearer " + os.environ["GRAPHON_ENGINE_API_KEY"],
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.read().decode())
```

### JavaScript

```javascript
const response = await fetch(process.env.GRAPHON_ENGINE_URL + "/webhooks/acme-status/deliveries/evt_01K6Q6N7D8E9F0G1H2J3K4M5N6", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GRAPHON_ENGINE_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
