# Webhooks · Management API

> Management API reference for webhooks: GET /sites/{id}/webhooks, POST /sites/{id}/webhooks, PUT /sites/{id}/webhooks/{webhookId}/events, DELETE /sites/{id}/webhooks/{webhookId}, ….

> Markdown version of https://kanorio.com/en/developers/management-api/webhooks. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp

Base URL: `https://app.kanorio.com/api/v1` · [Errors](https://kanorio.com/en/developers/basics/errors) · [Pagination](https://kanorio.com/en/developers/basics/pagination)

### List webhooks

`GET /sites/{id}/webhooks`

- Stability: beta
- Permission: `site:integrations`
- Rate limit: default (120/minute per token)
- MCP tool: `webhooks_list`

List the website's webhook endpoints (all in one page) with recent deliveries, plus the endpoint quota and the subscribable event keys. Errors: FORBIDDEN.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].id` | string | yes | — |
| `data[].label` | string \| null | yes | — |
| `data[].addressPreview` | string \| null | yes | Masked destination URL (domain + last 4 chars of path). |
| `data[].status` | string | yes | ACTIVE or DISABLED. |
| `data[].withinPlan` | boolean | yes | — |
| `data[].autoDisabled` | boolean | yes | — |
| `data[].createdAt` | string | yes | — |
| `data[].subscribedEvents` | string[] \| null | yes | null = subscribed to all events (including future events); array = only these event keys. |
| `data[].filter` | object \| null | no | Optional sub-event filter for collection keys or form IDs. |
| `data[].filter.collectionKeys` | string[] | no | Only deliver collections.* events for these collection keys. |
| `data[].filter.formIds` | string[] | no | Only deliver customers.form.submitted events for these form IDs. |
| `data[].recentDeliveries` | object[] | yes | — |
| `data[].recentDeliveries[].id` | string | yes | — |
| `data[].recentDeliveries[].eventKey` | string | yes | — |
| `data[].recentDeliveries[].status` | string | yes | — |
| `data[].recentDeliveries[].error` | string \| null | yes | — |
| `data[].recentDeliveries[].createdAt` | string | yes | — |
| `data[].recentDeliveries[].attempts` | integer | yes | — |
| `data[].recentDeliveries[].nextRetryAt` | string \| null | yes | — |
| `data[].recentDeliveries[].statusCode` | integer \| null | yes | — |
| `data[].recentDeliveries[].durationMs` | integer \| null | yes | — |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |
| `subscribableEvents` | string[] | yes | — |
| `quota` | object | yes | — |
| `quota.planAllowed` | boolean | yes | — |
| `quota.limit` | integer | yes | Maximum endpoints for this site. |
| `quota.remaining` | integer | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/webhooks" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "data": [
    {
      "id": "wh_example01",
      "label": "Vercel ISR Revalidate",
      "addressPreview": "https://www.sunnybakery.example/…date",
      "status": "ACTIVE",
      "withinPlan": true,
      "autoDisabled": false,
      "createdAt": "2026-10-01T02:30:00.000Z",
      "subscribedEvents": [
        "collections.item.published",
        "collections.items.bulkChanged"
      ],
      "filter": null,
      "recentDeliveries": []
    }
  ],
  "subscribableEvents": [
    "collections.item.created",
    "collections.item.updated",
    "collections.item.published",
    "collections.item.unpublished",
    "collections.item.deleted",
    "collections.items.bulkChanged",
    "collections.schema.changed",
    "content.site.published",
    "content.page.published",
    "content.page.updated",
    "content.page.unpublished",
    "content.page.deleted",
    "customers.form.submitted"
  ],
  "quota": {
    "planAllowed": true,
    "limit": 10,
    "remaining": 9
  },
  "hasMore": false,
  "nextCursor": null
}
```

### Create a webhook

`POST /sites/{id}/webhooks`

- Stability: beta
- Permission: `site:integrations`
- Rate limit: default (120/minute per token)
- MCP tool: `webhooks_create`

Create a webhook endpoint (public https URL, up to 10 endpoints per site). Returns the HMAC-SHA256 signingSecret (whsec_…) once — store it in your receiver's environment to verify X-Kanorio-Signature. Pass events (from webhooks.list subscribableEvents) to subscribe to specific events, or omit/null to subscribe to all events. Errors: FORBIDDEN, WEBHOOK_LIMIT_REACHED, DUPLICATE_WEBHOOK, VALIDATION.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | yes | Public https endpoint URL. |
| `label` | string \| null | no | Display label in the dashboard, e.g. 'Vercel ISR Revalidate'. |
| `events` | string[] \| null | no | Event keys to subscribe to (e.g. ['collections.item.published', 'collections.items.bulkChanged']), or null/omit for all events. |
| `filter` | object \| null | no | Optional sub-event filter for collection keys or form IDs. |
| `filter.collectionKeys` | string[] | no | Only deliver collections.* events for these collection keys. |
| `filter.formIds` | string[] | no | Only deliver customers.form.submitted events for these form IDs. |

**Response** (201)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `signingSecret` | string \| null | yes | HMAC-SHA256 signing secret (whsec_…). Shown only on creation. |
| `subscribedEvents` | string[] \| null | yes | — |
| `filter` | object \| null | no | Optional sub-event filter for collection keys or form IDs. |
| `filter.collectionKeys` | string[] | no | Only deliver collections.* events for these collection keys. |
| `filter.formIds` | string[] | no | Only deliver customers.form.submitted events for these form IDs. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `WEBHOOK_LIMIT_REACHED` | 403 | — |
| `DUPLICATE_WEBHOOK` | 409 | — |
| `VALIDATION` | 400 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/webhooks" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://www.sunnybakery.example/api/revalidate","label":"Vercel ISR Revalidate","events":["collections.item.published","collections.items.bulkChanged"]}'
```

```json
{
  "id": "wh_example01",
  "signingSecret": "whsec_example_signing_secret_01",
  "subscribedEvents": [
    "collections.item.published",
    "collections.items.bulkChanged"
  ],
  "filter": null
}
```

### Update events

`PUT /sites/{id}/webhooks/{webhookId}/events`

- Stability: beta
- Permission: `site:integrations`
- Rate limit: default (120/minute per token)
- MCP tool: `webhooks_update_events`

Replace the subscribed event list and optional filter (collectionKeys / formIds) of a webhook endpoint. Pass null for events to subscribe to all events (including future events). Errors: FORBIDDEN, WEBHOOK_NOT_FOUND, VALIDATION.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `webhookId` | path | string | yes | Webhook destination id. Get it from webhooks.list. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `events` | string[] \| null | yes | Subscribable event keys, or null for all events. |
| `filter` | object \| null | no | Optional sub-event filter for collection keys or form IDs. |
| `filter.collectionKeys` | string[] | no | Only deliver collections.* events for these collection keys. |
| `filter.formIds` | string[] | no | Only deliver customers.form.submitted events for these form IDs. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `subscribedEvents` | string[] \| null | yes | — |
| `filter` | object \| null | no | Optional sub-event filter for collection keys or form IDs. |
| `filter.collectionKeys` | string[] | no | Only deliver collections.* events for these collection keys. |
| `filter.formIds` | string[] | no | Only deliver customers.form.submitted events for these form IDs. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `WEBHOOK_NOT_FOUND` | 404 | — |
| `VALIDATION` | 400 | — |

```bash
curl -X PUT "https://app.kanorio.com/api/v1/sites/{id}/webhooks/{webhookId}/events" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"events":["collections.item.published"],"filter":{"collectionKeys":["blog"]}}'
```

```json
{
  "id": "wh_example01",
  "subscribedEvents": [
    "collections.item.published"
  ],
  "filter": {
    "collectionKeys": [
      "blog"
    ]
  }
}
```

### Delete a webhook

`DELETE /sites/{id}/webhooks/{webhookId}`

- Stability: beta
- Permission: `site:integrations`
- Rate limit: default (120/minute per token)
- MCP tool: `webhooks_delete`

Delete a webhook endpoint. Errors: FORBIDDEN, WEBHOOK_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `webhookId` | path | string | yes | Webhook destination id. Get it from webhooks.list. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `deleted` | true | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `WEBHOOK_NOT_FOUND` | 404 | — |

```bash
curl -X DELETE "https://app.kanorio.com/api/v1/sites/{id}/webhooks/{webhookId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "id": "wh_example01",
  "deleted": true
}
```

### Test a webhook

`POST /sites/{id}/webhooks/{webhookId}/test`

- Stability: beta
- Permission: `site:integrations`
- Rate limit: default (120/minute per token)
- MCP tool: `webhooks_test`

Send a one-off team.test ping to a webhook endpoint and return the HTTP status and latency (does not count toward auto-disable failures). Errors: FORBIDDEN, WEBHOOK_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `webhookId` | path | string | yes | Webhook destination id. Get it from webhooks.list. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `success` | boolean | yes | — |
| `status` | integer \| null | yes | — |
| `durationMs` | integer | yes | — |
| `error` | string \| null | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `WEBHOOK_NOT_FOUND` | 404 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/webhooks/{webhookId}/test" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```json
{
  "id": "wh_example01",
  "success": true,
  "status": 200,
  "durationMs": 142,
  "error": null
}
```
