---
title: Webhook Endpoints
description: Generated HTTP reference for the 10 operations the published OpenAPI document describes under webhook-endpoints.
---

# Webhook Endpoints

This page is generated from the published OpenAPI document. It is complete with respect to that document and says nothing about surfaces the document does not describe yet. See [what is generated here](/docs/api/reference) for what that means.

Base URL: `https://api.codespar.dev`

Every operation below requires a Bearer token. See [Authentication](/docs/concepts/authentication).

### GET `/v1/webhook-endpoints`

<Endpoint method="GET" path="/v1/webhook-endpoints" base="https://api.codespar.dev" />

List triggers

**Query parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `before` | `string` | no | — |
| `event` | `string` | no | — |
| `limit` | `integer` | no | — |
| `server_id` | `string` | no | — |
| `status` | `"active"` \| `"paused"` \| `"error"` | no | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad Request — the body or query did not match the schema. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `next_before` | `string,null` | yes | — |
| `triggers` | array of [Trigger](/docs/api/reference/schemas#trigger) | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","TypeScript"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/webhook-endpoints \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/webhook-endpoints", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "triggers": [
    {
      "id": "trg_0000000000000000",
      "org_id": "org_0000000000000000",
      "name": "Example",
      "event": "string",
      "webhook_url": "https://example.com/hook",
      "status": "string",
      "total_runs": 0,
      "created_at": "2026-01-15T12:00:00.000Z",
      "signing_enabled": true
    }
  ]
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/webhook-endpoints" />

### POST `/v1/webhook-endpoints`

<Endpoint method="POST" path="/v1/webhook-endpoints" base="https://api.codespar.dev" />

Subscribe a webhook to an event

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `event` | `string` | yes | — |
| `name` | `string` | yes | — |
| `server_id` | `string` | no | — |
| `webhook_url` | `string (uri)` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `201` | [TriggerCreated](/docs/api/reference/schemas#triggercreated) | OK |
| `400` | object | Bad Request — the body or query did not match the schema. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","TypeScript"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/webhook-endpoints \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "name": "Example",
       "event": "string",
       "server_id": "srv_0000000000000000",
       "webhook_url": "https://example.com/hook"
     }'
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/webhook-endpoints", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Example",
    "event": "string",
    "server_id": "srv_0000000000000000",
    "webhook_url": "https://example.com/hook"
  }),
});

const data = await res.json();
```

</Tab>
</Tabs>

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/webhook-endpoints" body={"{\"name\":\"Example\",\"event\":\"string\",\"server_id\":\"srv_0000000000000000\",\"webhook_url\":\"https://example.com/hook\"}"} />

### POST `/v1/webhook-endpoints/deliveries/{delivery_id}/redeliver`

<Endpoint method="POST" path="/v1/webhook-endpoints/deliveries/{delivery_id}/redeliver" base="https://api.codespar.dev" />

Queue a delivery to be sent again

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `delivery_id` | `string` | yes | Delivery id as decimal digits, the string form of the `bigserial` column. Up to 19 digits, which is the width of a signed 64 bit maximum; anything else is refused with 400 `invalid_delivery_id` before the database is touched. |

**Responses**

| Status | Body | Description |
|---|---|---|
| `202` | object | Accepted |
| `400` | object | `delivery_id` is not 1 to 19 decimal digits. |
| `404` | object | No delivery with that id whose webhook endpoint belongs to the caller's organization and project. |

**Response `202`**

| Field | Type | Required | Description |
|---|---|---|---|
| `event_id` | `string` | yes | — |
| `redelivery_id` | `string` | yes | Id of the NEW pending delivery row, bigint as string. |
| `trigger_id` | `string` | yes | — |
| `trigger_status` | `string` | yes | Status of the endpoint the delivery belongs to, read before the insert. Check it: the drain only claims rows whose endpoint is `active`, so a redelivery queued against a `paused` or `error` endpoint sits there until a patch reactivates it. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","TypeScript"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/webhook-endpoints/deliveries/{delivery_id}/redeliver \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/webhook-endpoints/deliveries/{delivery_id}/redeliver", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 202">

```json title="application/json"
{
  "redelivery_id": "redelivery_0000000000000000",
  "trigger_id": "trg_0000000000000000",
  "event_id": "event_0000000000000000",
  "trigger_status": "string"
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/webhook-endpoints/deliveries/{delivery_id}/redeliver" />

### POST `/v1/webhook-endpoints/retry-pending`

<Endpoint method="POST" path="/v1/webhook-endpoints/retry-pending" base="https://api.codespar.dev" />

Drain the retry queue for this project

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `dead` | `integer` | yes | Attempts that failed on the last allowed attempt and were dead lettered. |
| `delivered` | `integer` | yes | — |
| `failed` | `integer` | yes | Attempts that failed and are scheduled for another retry. |
| `redispatched` | `integer` | yes | — |
| `scanned` | `integer` | yes | Rows the claim actually took, which is also the ceiling on the four counters below. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","TypeScript"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/webhook-endpoints/retry-pending \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/webhook-endpoints/retry-pending", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "scanned": 0,
  "redispatched": 0,
  "delivered": 0,
  "failed": 0,
  "dead": 0
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/webhook-endpoints/retry-pending" />

### GET `/v1/webhook-endpoints/{id}`

<Endpoint method="GET" path="/v1/webhook-endpoints/{id}" base="https://api.codespar.dev" />

Read one webhook endpoint

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Endpoint id, `trg_` followed by a 16 character nanoid, as minted by the create call. |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | No webhook endpoint with that id in the caller's organization and project. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `created_at` | `string (date-time)` | yes | — |
| `event` | `string` | yes | The event name this endpoint subscribes to, dot separated lowercase. |
| `id` | `string` | yes | — |
| `last_run_at` | `string,null (date-time)` | yes | Timestamp of the last DELIVERED attempt, on the same rule as `total_runs`. |
| `name` | `string` | yes | — |
| `org_id` | `string` | yes | — |
| `project_id` | `string,null` | yes | Nullable in the row type this response is serialized from; a later migration sets the column NOT NULL, so an endpoint created since then always carries one. |
| `server_id` | `string,null` | yes | — |
| `signing_enabled` | `boolean` | yes | Whether a signing secret exists. The secret itself is never read back: the serializer drops the vault reference and reports only this boolean. |
| `status` | `string` | yes | `active`, `paused` or `error`. Left as an open string because the column is `text` with no CHECK constraint: `active` and `paused` are what a patch may set, and `error` is what the dispatcher writes on its own when an endpoint auto-pauses after enough consecutive dead deliveries. |
| `total_runs` | `integer` | yes | Deliveries that landed. A failed or dead attempt does not count, which is why this can sit at 0 while the deliveries listing is full of rows. |
| `webhook_url` | `string` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","TypeScript"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/webhook-endpoints/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/webhook-endpoints/{id}", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "org_id": "org_0000000000000000",
  "name": "Example",
  "event": "string",
  "webhook_url": "https://example.com/hook",
  "status": "string",
  "total_runs": 0,
  "created_at": "2026-01-15T12:00:00.000Z",
  "signing_enabled": true
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/webhook-endpoints/{id}" />

### DELETE `/v1/webhook-endpoints/{id}`

<Endpoint method="DELETE" path="/v1/webhook-endpoints/{id}" base="https://api.codespar.dev" />

Delete a webhook endpoint

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Endpoint id, `trg_` followed by a 16 character nanoid, as minted by the create call. |

**Responses**

| Status | Body | Description |
|---|---|---|
| `204` | — | No Content |
| `404` | object | No webhook endpoint with that id in the caller's organization and project. |
| `503` | object | The signing secret could not be purged, so nothing was deleted. Retriable. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","TypeScript"]}>
<Tab value="curl">

```bash
curl -X DELETE https://api.codespar.dev/v1/webhook-endpoints/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/webhook-endpoints/{id}", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
</Tabs>

</SplitPane>
</Split>

<TryIt method="DELETE" path="/v1/webhook-endpoints/{id}" />

### GET `/v1/webhook-endpoints/{id}/deliveries`

<Endpoint method="GET" path="/v1/webhook-endpoints/{id}/deliveries" base="https://api.codespar.dev" />

List recent delivery attempts

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Endpoint id, `trg_` followed by a 16 character nanoid, as minted by the create call. |

**Query parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` | `integer` | no | Rows to return. Clamped into the range 1 to 200: a larger or smaller value is reduced or raised rather than refused, and a value that does not parse as a number falls back to 50. |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | No webhook endpoint with that id in the caller's organization and project. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `deliveries` | array of object | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","TypeScript"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/webhook-endpoints/{id}/deliveries \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/webhook-endpoints/{id}/deliveries", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "deliveries": [
    {
      "id": "obj_0000000000000000",
      "trigger_id": "trg_0000000000000000",
      "event_id": "event_0000000000000000",
      "attempt": 0,
      "status": "string",
      "created_at": "2026-01-15T12:00:00.000Z"
    }
  ]
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/webhook-endpoints/{id}/deliveries" />

### GET `/v1/webhook-endpoints/{id}/deliveries/{delivery_id}`

<Endpoint method="GET" path="/v1/webhook-endpoints/{id}/deliveries/{delivery_id}" base="https://api.codespar.dev" />

Inspect one delivery attempt

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `delivery_id` | `string` | yes | Delivery id as decimal digits, the string form of the `bigserial` column. Up to 19 digits, which is the width of a signed 64 bit maximum; anything else is refused with 400 `invalid_delivery_id` before the database is touched. |
| `id` | `string` | yes | Endpoint id, `trg_` followed by a 16 character nanoid, as minted by the create call. |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | `delivery_id` is not 1 to 19 decimal digits. |
| `404` | object | Either no such webhook endpoint in the caller's organization and project, or no delivery with that id belonging to it. Both answer the same code, on purpose. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `delivery` | object | yes | — |
| `event` | object | yes | — |
| `request` | object | yes | — |
| `response` | object | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","TypeScript"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/webhook-endpoints/{id}/deliveries/{delivery_id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/webhook-endpoints/{id}/deliveries/{delivery_id}", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "delivery": {
    "id": "obj_0000000000000000",
    "trigger_id": "trg_0000000000000000",
    "event_id": "event_0000000000000000",
    "attempt": 0,
    "status": "string",
    "created_at": "2026-01-15T12:00:00.000Z"
  },
  "request": {
    "headers": {
      "Content-Type": "string",
      "X-CodeSpar-Event": "string",
      "X-CodeSpar-Event-Id": "xcodesparevent_0000000000000000",
      "X-CodeSpar-Trigger-Id": "xcodespartrigger_0000000000000000",
      "X-CodeSpar-Attempt": "string"
    },
    "body": "string"
  },
  "response": {},
  "event": {
    "id": "obj_0000000000000000",
    "source": "string",
    "event_type": "string",
    "payload": {},
    "received_at": "2026-01-15T12:00:00.000Z"
  }
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/webhook-endpoints/{id}/deliveries/{delivery_id}" />

### GET `/v1/webhook-endpoints/{id}/dlq`

<Endpoint method="GET" path="/v1/webhook-endpoints/{id}/dlq" base="https://api.codespar.dev" />

List dead lettered deliveries

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Endpoint id, `trg_` followed by a 16 character nanoid, as minted by the create call. |

**Query parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` | `integer` | no | Rows to return. Clamped into the range 1 to 200: a larger or smaller value is reduced or raised rather than refused, and a value that does not parse as a number falls back to 50. |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | No webhook endpoint with that id in the caller's organization and project. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `dead_letters` | array of object | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","TypeScript"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/webhook-endpoints/{id}/dlq \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/webhook-endpoints/{id}/dlq", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "dead_letters": [
    {
      "id": "obj_0000000000000000",
      "event_id": "event_0000000000000000",
      "attempt": 0,
      "created_at": "2026-01-15T12:00:00.000Z"
    }
  ]
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/webhook-endpoints/{id}/dlq" />

### POST `/v1/webhook-endpoints/{id}/rotate-secret`

<Endpoint method="POST" path="/v1/webhook-endpoints/{id}/rotate-secret" base="https://api.codespar.dev" />

Mint a new signing secret for a webhook endpoint

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Endpoint id, `trg_` followed by a 16 character nanoid, as minted by the create call. |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | No webhook endpoint with that id in the caller's organization and project. |
| `503` | object | The new secret could not be persisted, so no rotation occurred and the previous secret is still in force. Retriable. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `secret` | `string` | yes | The new signing secret in plaintext, 32 random bytes as hex, shown EXACTLY ONCE. Later reads expose only `signing_enabled`, and a lost secret is replaced by rotating again. |
| `signing_enabled` | `true` | yes | — |
| `trigger_id` | `string` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","TypeScript"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/webhook-endpoints/{id}/rotate-secret \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/webhook-endpoints/{id}/rotate-secret", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "trigger_id": "trg_0000000000000000",
  "signing_enabled": true,
  "secret": "string"
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/webhook-endpoints/{id}/rotate-secret" />

