List webhook endpoints
GET/webhook-endpoints
- Scope:
- webhooks
- Rate class:
- light
- Operation id:
- list_webhook_endpoints
Lists the workspace's webhook endpoints, oldest first (at most 5). A single endpoint the dashboard had before is moved into this list the first time it is needed. Needs the webhooks scope. The signing secret is never in a list.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| limit | query | integeroptional | Items per page, 1 to 200.Range: 1 to 200. Default: 50. |
| cursor | query | stringoptional | The next_cursor of the previous page. Opaque: never build one. |
Answer 200
OK. A page of WebhookEndpoint objects, with next_cursor and has_more.
| Field | Type | Description |
|---|---|---|
| data | array of object | The items of this page, in the endpoint's fixed order. |
| data[].id | integer | The endpoint's id. |
| data[].url | string | Where deliveries are POSTed (https, a public address). |
| data[].events | array of string | The events it takes: names from GET /webhook-events. |
| data[].enabled | boolean | False while the endpoint is paused: no event is sent to it. |
| data[].description | string or null | Your note about the endpoint. |
| data[].source | string | Where it was made: the dashboard, this API, or moved from the single endpoint the dashboard had before.One of: dashboard, api, migrated. |
| data[].signed | boolean | True when deliveries carry a PerfBee-Signature. An endpoint moved from before without a secret is unsigned until you rotate one. |
| data[].previous_secret_valid_until | string or null | While a rotation overlaps: when the old secret stops signing (ISO 8601 UTC); otherwise null. |
| data[].legacy_secret_header_until | string or null | Only on an endpoint moved from before: when the old plain X-PerfBee-Secret header stops (ISO 8601 UTC); otherwise null. |
| data[].last_delivery_at | string or null | When the last attempt was made (ISO 8601 UTC); null before the first. |
| data[].last_status | string or null | The last delivery's state: delivered, retrying or failed. |
| data[].consecutive_failures | integer | Failed attempts in a row; a delivered event ends the run. |
| data[].created_at | string or null | When the endpoint was made (ISO 8601 UTC). |
| has_more | boolean | True when another page follows. |
| next_cursor | string or null | Pass as cursor to get the next page; null on the last page. |
{
"data": [
{
"id": 3,
"url": "https://hooks.example.com/perfbee",
"events": [
"scan_data_ready",
"scan_failed"
],
"enabled": true,
"description": "CI gate",
"source": "api",
"signed": true,
"last_delivery_at": "2026-11-01T09:30:12Z",
"last_status": "delivered",
"consecutive_failures": 0,
"created_at": "2026-10-20T14:02:00Z"
}
],
"has_more": true,
"next_cursor": "eyJ2IjoxfQ.opaque"
}Problems this call documents
- 400 Invalid cursor:
invalid_cursor - 422 Validation failed:
validation_error
Create a webhook endpoint
POST/webhook-endpoints
- Scope:
- webhooks
- Rate class:
- light
- Operation id:
- create_webhook_endpoint
Adds an endpoint that receives the events you pick, signed with PerfBee-Signature. The answer carries the signing secret: it is shown once, here, and cannot be read again (rotate it to get a new one). The address must be an https URL on a public host. Needs the webhooks scope.
Request body
application/json, required.
| Field | Type | Description |
|---|---|---|
| url | stringrequired | An https:// address on a public host, without credentials. It is checked again at every delivery.Range: at most 512 characters. |
| events | array of stringrequired | The events to send: names from GET /webhook-events.Range: at most 40 items. |
| description | string or nulloptional | A note for yourself.Range: at most 200 characters. |
{
"url": "https://hooks.example.com/perfbee",
"events": [
"scan_data_ready",
"scan_failed"
],
"description": "CI gate"
}Answer 201
Created. The body is a WebhookEndpointSecret.
| Field | Type | Description |
|---|---|---|
| id | integer | The endpoint's id. |
| url | string | Where deliveries are POSTed (https, a public address). |
| events | array of string | The events it takes: names from GET /webhook-events. |
| enabled | boolean | False while the endpoint is paused: no event is sent to it. |
| description | string or null | Your note about the endpoint. |
| source | string | Where it was made: the dashboard, this API, or moved from the single endpoint the dashboard had before.One of: dashboard, api, migrated. |
| signed | boolean | True when deliveries carry a PerfBee-Signature. An endpoint moved from before without a secret is unsigned until you rotate one. |
| previous_secret_valid_until | string or null | While a rotation overlaps: when the old secret stops signing (ISO 8601 UTC); otherwise null. |
| legacy_secret_header_until | string or null | Only on an endpoint moved from before: when the old plain X-PerfBee-Secret header stops (ISO 8601 UTC); otherwise null. |
| last_delivery_at | string or null | When the last attempt was made (ISO 8601 UTC); null before the first. |
| last_status | string or null | The last delivery's state: delivered, retrying or failed. |
| consecutive_failures | integer | Failed attempts in a row; a delivered event ends the run. |
| created_at | string or null | When the endpoint was made (ISO 8601 UTC). |
| secret | string | The signing secret (whsec_...). Shown ONCE, here: store it now, it cannot be read again. |
{
"id": 3,
"url": "https://hooks.example.com/perfbee",
"events": [
"scan_data_ready",
"scan_failed"
],
"enabled": true,
"description": "CI gate",
"source": "api",
"signed": true,
"last_delivery_at": "2026-11-01T09:30:12Z",
"last_status": "delivered",
"consecutive_failures": 0,
"created_at": "2026-10-20T14:02:00Z",
"secret": "whsec_p3Qx1kT0n8a5ZyV2mL7cR4dB9wUeHsJfGiNoAqXtYbc"
}Problems this call documents
- 409 Webhook endpoint limit reached:
webhook_endpoint_limit - 422 Invalid webhook events:
invalid_webhook_events - 422 Invalid webhook URL:
invalid_webhook_url - 422 Validation failed:
validation_error - 503 Webhook signing is not available:
webhook_signing_unavailable
Get a webhook endpoint
GET/webhook-endpoints/{endpoint_id}
- Scope:
- webhooks
- Rate class:
- light
- Operation id:
- get_webhook_endpoint
One endpoint of the workspace: where it sends, which events, whether it is paused, how its last delivery went. Needs the webhooks scope. The signing secret is never returned.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| endpoint_id | path | integerrequired | The webhook endpoint's id, from GET /webhook-endpoints. |
Answer 200
OK. The body is a WebhookEndpoint.
| Field | Type | Description |
|---|---|---|
| id | integer | The endpoint's id. |
| url | string | Where deliveries are POSTed (https, a public address). |
| events | array of string | The events it takes: names from GET /webhook-events. |
| enabled | boolean | False while the endpoint is paused: no event is sent to it. |
| description | string or null | Your note about the endpoint. |
| source | string | Where it was made: the dashboard, this API, or moved from the single endpoint the dashboard had before.One of: dashboard, api, migrated. |
| signed | boolean | True when deliveries carry a PerfBee-Signature. An endpoint moved from before without a secret is unsigned until you rotate one. |
| previous_secret_valid_until | string or null | While a rotation overlaps: when the old secret stops signing (ISO 8601 UTC); otherwise null. |
| legacy_secret_header_until | string or null | Only on an endpoint moved from before: when the old plain X-PerfBee-Secret header stops (ISO 8601 UTC); otherwise null. |
| last_delivery_at | string or null | When the last attempt was made (ISO 8601 UTC); null before the first. |
| last_status | string or null | The last delivery's state: delivered, retrying or failed. |
| consecutive_failures | integer | Failed attempts in a row; a delivered event ends the run. |
| created_at | string or null | When the endpoint was made (ISO 8601 UTC). |
{
"id": 3,
"url": "https://hooks.example.com/perfbee",
"events": [
"scan_data_ready",
"scan_failed"
],
"enabled": true,
"description": "CI gate",
"source": "api",
"signed": true,
"last_delivery_at": "2026-11-01T09:30:12Z",
"last_status": "delivered",
"consecutive_failures": 0,
"created_at": "2026-10-20T14:02:00Z"
}Problems this call documents
- 404 Not found:
not_found - 422 Validation failed:
validation_error
Change or pause a webhook endpoint
PATCH/webhook-endpoints/{endpoint_id}
- Scope:
- webhooks
- Rate class:
- light
- Operation id:
- update_webhook_endpoint
Changes the fields you send: the address (its delivery history starts again), the events, the note, or enabled (false pauses it: nothing is sent while it is paused). Needs the webhooks scope.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| endpoint_id | path | integerrequired | The webhook endpoint's id, from GET /webhook-endpoints. |
Request body
application/json, required.
| Field | Type | Description |
|---|---|---|
| description | string or nulloptional | A new note, or null to clear it.Range: at most 200 characters. |
| enabled | boolean or nulloptional | false pauses the endpoint, true resumes it. |
| events | array of string or nulloptional | The new list of events.Range: at most 40 items. |
| url | string or nulloptional | A new address; the endpoint's delivery history starts again.Range: at most 512 characters. |
{
"enabled": false
}Answer 200
OK. The body is a WebhookEndpoint.
| Field | Type | Description |
|---|---|---|
| id | integer | The endpoint's id. |
| url | string | Where deliveries are POSTed (https, a public address). |
| events | array of string | The events it takes: names from GET /webhook-events. |
| enabled | boolean | False while the endpoint is paused: no event is sent to it. |
| description | string or null | Your note about the endpoint. |
| source | string | Where it was made: the dashboard, this API, or moved from the single endpoint the dashboard had before.One of: dashboard, api, migrated. |
| signed | boolean | True when deliveries carry a PerfBee-Signature. An endpoint moved from before without a secret is unsigned until you rotate one. |
| previous_secret_valid_until | string or null | While a rotation overlaps: when the old secret stops signing (ISO 8601 UTC); otherwise null. |
| legacy_secret_header_until | string or null | Only on an endpoint moved from before: when the old plain X-PerfBee-Secret header stops (ISO 8601 UTC); otherwise null. |
| last_delivery_at | string or null | When the last attempt was made (ISO 8601 UTC); null before the first. |
| last_status | string or null | The last delivery's state: delivered, retrying or failed. |
| consecutive_failures | integer | Failed attempts in a row; a delivered event ends the run. |
| created_at | string or null | When the endpoint was made (ISO 8601 UTC). |
{
"id": 3,
"url": "https://hooks.example.com/perfbee",
"events": [
"scan_data_ready",
"scan_failed"
],
"enabled": true,
"description": "CI gate",
"source": "api",
"signed": true,
"last_delivery_at": "2026-11-01T09:30:12Z",
"last_status": "delivered",
"consecutive_failures": 0,
"created_at": "2026-10-20T14:02:00Z"
}Problems this call documents
- 404 Not found:
not_found - 422 Invalid webhook events:
invalid_webhook_events - 422 Invalid webhook URL:
invalid_webhook_url - 422 Validation failed:
validation_error
Delete a webhook endpoint
DELETE/webhook-endpoints/{endpoint_id}
- Scope:
- webhooks
- Rate class:
- light
- Operation id:
- delete_webhook_endpoint
Removes the endpoint and its delivery list. Nothing is sent to it from now on. Needs the webhooks scope.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| endpoint_id | path | integerrequired | The webhook endpoint's id, from GET /webhook-endpoints. |
Answer 204
Done. There is no body.
Problems this call documents
- 404 Not found:
not_found - 422 Validation failed:
validation_error
List an endpoint's deliveries
GET/webhook-endpoints/{endpoint_id}/deliveries
- Scope:
- webhooks
- Rate class:
- light
- Operation id:
- list_webhook_deliveries
The endpoint's deliveries, newest first, kept for 14 days: one row per event sent to it, with its state, tries and the receiver's answer. A delivery that is not yet delivered carries its payload (a failed one for 3 days). Filter with status. Needs the webhooks scope.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| endpoint_id | path | integerrequired | The webhook endpoint's id, from GET /webhook-endpoints. |
| status | query | stringoptional | Only deliveries in this state: pending, retrying, delivered or failed. |
| limit | query | integeroptional | Items per page, 1 to 200.Range: 1 to 200. Default: 50. |
| cursor | query | stringoptional | The next_cursor of the previous page. Opaque: never build one. |
Answer 200
OK. A page of WebhookDelivery objects, with next_cursor and has_more.
| Field | Type | Description |
|---|---|---|
| data | array of object | The items of this page, in the endpoint's fixed order. |
| data[].id | integer | The delivery's id (one event sent to one endpoint, with its retries). |
| data[].event | string | The event, or test for a test delivery. |
| data[].event_id | string | The event's id, the same as id in the payload and in PerfBee-Event-Id; it stays the same on every attempt and every redelivery. |
| data[].status | string | pending (waiting for its first attempt), retrying, delivered or failed.One of: pending, retrying, delivered, failed. |
| data[].attempt | integer | The number of the attempt made last (0 before the first). |
| data[].max_attempts | integer | How many attempts a delivery gets. |
| data[].http_status | integer or null | The receiver's HTTP status on the last attempt; null when it did not answer. |
| data[].error | string or null | Why the last attempt failed when there was no usable answer: a short sentence. |
| data[].duration_ms | integer or null | How long the last attempt took. |
| data[].next_attempt_at | string or null | While retrying: when the next attempt is due (ISO 8601 UTC). |
| data[].created_at | string or null | When the event was queued for this endpoint (ISO 8601 UTC). |
| data[].payload | object or null | The body sent, kept until the delivery is delivered; a failed delivery keeps it for 3 days. null otherwise. |
| has_more | boolean | True when another page follows. |
| next_cursor | string or null | Pass as cursor to get the next page; null on the last page. |
{
"data": [
{
"id": 4182,
"event": "scan_data_ready",
"event_id": "evt_5f0c2b7e9a1d4c3e8b6a7d9f01234567",
"status": "failed",
"attempt": 5,
"max_attempts": 5,
"http_status": 503,
"duration_ms": 412,
"created_at": "2026-11-01T09:30:12Z",
"payload": {
"api_version": "v1",
"data": {
"crawl_id": "0f7c9a52-3d4b-4a39-8c0e-5e2a9d1b6f10",
"mode": "full",
"site_id": 7
},
"event": "scan_data_ready",
"event_description": "Crawl data ready",
"id": "evt_5f0c2b7e9a1d4c3e8b6a7d9f01234567",
"timestamp": "2026-11-01T09:30:12.512Z"
}
}
],
"has_more": true,
"next_cursor": "eyJ2IjoxfQ.opaque"
}Problems this call documents
- 400 Invalid cursor:
invalid_cursor - 404 Not found:
not_found - 422 Validation failed:
validation_error
Send a failed delivery again
POST/webhook-endpoints/{endpoint_id}/deliveries/{delivery_id}/redeliver
- Scope:
- webhooks
- Rate class:
- heavy
- Operation id:
- redeliver_webhook_delivery
Starts a new chain of attempts for a failed delivery: a new delivery with the same event id and payload, answered at once with state pending (the attempts run in the background). Only a failed delivery whose payload is still stored (3 days) can be sent again. Needs the webhooks scope.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| endpoint_id | path | integerrequired | The webhook endpoint's id, from GET /webhook-endpoints. |
| delivery_id | path | integerrequired | The delivery's id, from GET /webhook-endpoints/{endpoint_id}/deliveries. |
Answer 202
Accepted. The body is a WebhookDelivery.
| Field | Type | Description |
|---|---|---|
| id | integer | The delivery's id (one event sent to one endpoint, with its retries). |
| event | string | The event, or test for a test delivery. |
| event_id | string | The event's id, the same as id in the payload and in PerfBee-Event-Id; it stays the same on every attempt and every redelivery. |
| status | string | pending (waiting for its first attempt), retrying, delivered or failed.One of: pending, retrying, delivered, failed. |
| attempt | integer | The number of the attempt made last (0 before the first). |
| max_attempts | integer | How many attempts a delivery gets. |
| http_status | integer or null | The receiver's HTTP status on the last attempt; null when it did not answer. |
| error | string or null | Why the last attempt failed when there was no usable answer: a short sentence. |
| duration_ms | integer or null | How long the last attempt took. |
| next_attempt_at | string or null | While retrying: when the next attempt is due (ISO 8601 UTC). |
| created_at | string or null | When the event was queued for this endpoint (ISO 8601 UTC). |
| payload | object or null | The body sent, kept until the delivery is delivered; a failed delivery keeps it for 3 days. null otherwise. |
{
"id": 4182,
"event": "scan_data_ready",
"event_id": "evt_5f0c2b7e9a1d4c3e8b6a7d9f01234567",
"status": "failed",
"attempt": 5,
"max_attempts": 5,
"http_status": 503,
"duration_ms": 412,
"created_at": "2026-11-01T09:30:12Z",
"payload": {
"api_version": "v1",
"data": {
"crawl_id": "0f7c9a52-3d4b-4a39-8c0e-5e2a9d1b6f10",
"mode": "full",
"site_id": 7
},
"event": "scan_data_ready",
"event_description": "Crawl data ready",
"id": "evt_5f0c2b7e9a1d4c3e8b6a7d9f01234567",
"timestamp": "2026-11-01T09:30:12.512Z"
}
}Problems this call documents
- 404 Not found:
not_found - 409 Delivery in progress:
delivery_in_progress - 409 Payload is no longer stored:
delivery_payload_unavailable - 422 Validation failed:
validation_error - 429 Too many heavy calls at once:
too_many_heavy_requests - 503 Service busy:
capacity_busy
Rotate the signing secret
POST/webhook-endpoints/{endpoint_id}/rotate-secret
- Scope:
- webhooks
- Rate class:
- light
- Operation id:
- rotate_webhook_secret
Makes a new signing secret and returns it, once. The old secret keeps signing for 24 hours beside the new one (both signatures are sent), so a receiver can switch without a gap. On an endpoint moved from the old single endpoint this also stops the plain X-PerfBee-Secret header. Needs the webhooks scope.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| endpoint_id | path | integerrequired | The webhook endpoint's id, from GET /webhook-endpoints. |
Answer 200
OK. The body is a WebhookEndpointSecret.
| Field | Type | Description |
|---|---|---|
| id | integer | The endpoint's id. |
| url | string | Where deliveries are POSTed (https, a public address). |
| events | array of string | The events it takes: names from GET /webhook-events. |
| enabled | boolean | False while the endpoint is paused: no event is sent to it. |
| description | string or null | Your note about the endpoint. |
| source | string | Where it was made: the dashboard, this API, or moved from the single endpoint the dashboard had before.One of: dashboard, api, migrated. |
| signed | boolean | True when deliveries carry a PerfBee-Signature. An endpoint moved from before without a secret is unsigned until you rotate one. |
| previous_secret_valid_until | string or null | While a rotation overlaps: when the old secret stops signing (ISO 8601 UTC); otherwise null. |
| legacy_secret_header_until | string or null | Only on an endpoint moved from before: when the old plain X-PerfBee-Secret header stops (ISO 8601 UTC); otherwise null. |
| last_delivery_at | string or null | When the last attempt was made (ISO 8601 UTC); null before the first. |
| last_status | string or null | The last delivery's state: delivered, retrying or failed. |
| consecutive_failures | integer | Failed attempts in a row; a delivered event ends the run. |
| created_at | string or null | When the endpoint was made (ISO 8601 UTC). |
| secret | string | The signing secret (whsec_...). Shown ONCE, here: store it now, it cannot be read again. |
{
"id": 3,
"url": "https://hooks.example.com/perfbee",
"events": [
"scan_data_ready",
"scan_failed"
],
"enabled": true,
"description": "CI gate",
"source": "api",
"signed": true,
"last_delivery_at": "2026-11-01T09:30:12Z",
"last_status": "delivered",
"consecutive_failures": 0,
"created_at": "2026-10-20T14:02:00Z",
"secret": "whsec_p3Qx1kT0n8a5ZyV2mL7cR4dB9wUeHsJfGiNoAqXtYbc"
}Problems this call documents
- 404 Not found:
not_found - 422 Validation failed:
validation_error - 503 Webhook signing is not available:
webhook_signing_unavailable
Send a test event
POST/webhook-endpoints/{endpoint_id}/test
- Scope:
- webhooks
- Rate class:
- heavy
- Operation id:
- test_webhook_endpoint
Sends one test event to the endpoint now, signed like a real one, and answers with the delivery: whether it arrived, the receiver's HTTP status and how long it took. A failed test is not retried and does not count towards the endpoint's failure run. Needs the webhooks scope.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| endpoint_id | path | integerrequired | The webhook endpoint's id, from GET /webhook-endpoints. |
Answer 200
OK. The body is a WebhookDelivery.
| Field | Type | Description |
|---|---|---|
| id | integer | The delivery's id (one event sent to one endpoint, with its retries). |
| event | string | The event, or test for a test delivery. |
| event_id | string | The event's id, the same as id in the payload and in PerfBee-Event-Id; it stays the same on every attempt and every redelivery. |
| status | string | pending (waiting for its first attempt), retrying, delivered or failed.One of: pending, retrying, delivered, failed. |
| attempt | integer | The number of the attempt made last (0 before the first). |
| max_attempts | integer | How many attempts a delivery gets. |
| http_status | integer or null | The receiver's HTTP status on the last attempt; null when it did not answer. |
| error | string or null | Why the last attempt failed when there was no usable answer: a short sentence. |
| duration_ms | integer or null | How long the last attempt took. |
| next_attempt_at | string or null | While retrying: when the next attempt is due (ISO 8601 UTC). |
| created_at | string or null | When the event was queued for this endpoint (ISO 8601 UTC). |
| payload | object or null | The body sent, kept until the delivery is delivered; a failed delivery keeps it for 3 days. null otherwise. |
{
"id": 4182,
"event": "scan_data_ready",
"event_id": "evt_5f0c2b7e9a1d4c3e8b6a7d9f01234567",
"status": "failed",
"attempt": 5,
"max_attempts": 5,
"http_status": 503,
"duration_ms": 412,
"created_at": "2026-11-01T09:30:12Z",
"payload": {
"api_version": "v1",
"data": {
"crawl_id": "0f7c9a52-3d4b-4a39-8c0e-5e2a9d1b6f10",
"mode": "full",
"site_id": 7
},
"event": "scan_data_ready",
"event_description": "Crawl data ready",
"id": "evt_5f0c2b7e9a1d4c3e8b6a7d9f01234567",
"timestamp": "2026-11-01T09:30:12.512Z"
}
}Problems this call documents
- 404 Not found:
not_found - 422 Validation failed:
validation_error - 429 Too many heavy calls at once:
too_many_heavy_requests - 503 Service busy:
capacity_busy