Reference
Webhooks
Subscribe to events from §Events. Deliveries are signed (HMAC-SHA256), retried with exponential backoff, and logged for 30 days.
List webhook endpoints
/v1/webhooks curl -X GET "https://api.clement.so/v1/webhooks" \
-H "Authorization: Bearer $CLEMENT_API_KEY" Query parameters
-
limitinteger -
starting_afterstring -
ending_beforestring
Response200
OK
| Property | Type | Description |
|---|---|---|
object
required
| enum (1) | "list" |
data
required
| array<WebhookEndpoint> | |
has_more
required
| boolean | |
next_cursor
required
| string | null | |
prev_cursor
required
| string | null |
Example response
{
"object": "list",
"data": [
{
"id": "whk_01HZ8X3J9K7P2QV6R5N4M3T2LA",
"object": "webhook_endpoint",
"mode": "live",
"url": "https://customer.example.com/webhooks/clement",
"description": "CRM sync — replies, bounces, unsubs",
"enabled_events": [
"message.replied",
"message.bounced",
"contact.unsubscribed"
],
"secret_last_four": "f4a2",
"disabled_at": null,
"consecutive_failures": 0,
"verified_at": "2026-04-26T11:20:01Z",
"created_at": "2026-04-26T11:20:00Z",
"updated_at": "2026-04-26T11:20:00Z"
}
],
"has_more": false,
"next_cursor": "string",
"prev_cursor": "string"
} Errors
-
400Invalid request -
401Authentication failed -
403Insufficient scope -
404Not found -
409Conflict -
422Validation failed -
429Rate limited
See Errors for the full envelope and per-code reference.
Create a webhook endpoint (signing secret returned once)
/v1/webhooks The endpoint takes the calling key's mode and receives only events of that mode: create it with a `cl_test_*` key to receive test-mode events. Live endpoints must use `https://` and a public host. Clement immediately sends the endpoint a signed `webhook.verification` event; once it answers with any 2xx, `verified_at` is set and events start flowing. Until then it receives no other events (verification retries on the normal schedule, and a successful `/test` delivery also verifies it).
curl -X POST "https://api.clement.so/v1/webhooks" \
-H "Authorization: Bearer $CLEMENT_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"url": "https://example.com/clement-webhook",
"enabled_events": [
"string"
],
"description": "A short human-readable description."
}' Request body
| Property | Type | Description |
|---|---|---|
url
required
| string (uri) | Must be `https://` for live-mode endpoints. Redirects are not followed. |
description | string | |
enabled_events
required
| array<string> | Event types to subscribe to (e.g. `["message.replied", "contact.unsubscribed"]`). Use `["*"]` for everything. |
Response201
Created
| Property | Type | Description |
|---|---|---|
id
required
| string |
Example: "whk_…" |
object
required
| enum (1) | "webhook_endpoint" |
mode
required
| enum (2) | Mode of the key that created the endpoint. An endpoint only receives events of its own mode. "live""test" |
url
required
| string (uri) | |
description
required
| string | null | |
enabled_events
required
| array<string> | Subscribed event types. Use `*` to subscribe to all events. |
secret_last_four
required
| string | |
disabled_at
required
| string (date-time) | null | |
consecutive_failures
required
| integer | Failed attempts since the last successful delivery. The endpoint is disabled after it has been failing for 7 days. |
verified_at
required
| string (date-time) | null | When the endpoint first answered with a 2xx. A new endpoint, or one whose `url` changed, receives a signed `webhook.verification` event and no other events until it acknowledges one. |
created_at
required
| string (date-time) | |
updated_at
required
| string (date-time) | |
secret
required
| string | The signing secret. Returned only on create + rotate; never again. |
Example response
{
"id": "whk_01HZ8X3J9K7P2QV6R5N4M3T2LA",
"object": "webhook_endpoint",
"mode": "live",
"url": "https://customer.example.com/webhooks/clement",
"description": "CRM sync — replies, bounces, unsubs",
"enabled_events": [
"message.replied",
"message.bounced",
"contact.unsubscribed"
],
"secret_last_four": "f4a2",
"disabled_at": null,
"consecutive_failures": 0,
"verified_at": "2026-04-26T11:20:01Z",
"created_at": "2026-04-26T11:20:00Z",
"updated_at": "2026-04-26T11:20:00Z"
} Errors
-
400Invalid request -
401Authentication failed -
403Insufficient scope -
404Not found -
409Conflict -
422Validation failed -
429Rate limited
See Errors for the full envelope and per-code reference.
Retrieve a webhook endpoint
/v1/webhooks/{id} curl -X GET "https://api.clement.so/v1/webhooks/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1" \
-H "Authorization: Bearer $CLEMENT_API_KEY" Path parameters
-
idstring required
Response200
OK
| Property | Type | Description |
|---|---|---|
id
required
| string |
Example: "whk_…" |
object
required
| enum (1) | "webhook_endpoint" |
mode
required
| enum (2) | Mode of the key that created the endpoint. An endpoint only receives events of its own mode. "live""test" |
url
required
| string (uri) | |
description
required
| string | null | |
enabled_events
required
| array<string> | Subscribed event types. Use `*` to subscribe to all events. |
secret_last_four
required
| string | |
disabled_at
required
| string (date-time) | null | |
consecutive_failures
required
| integer | Failed attempts since the last successful delivery. The endpoint is disabled after it has been failing for 7 days. |
verified_at
required
| string (date-time) | null | When the endpoint first answered with a 2xx. A new endpoint, or one whose `url` changed, receives a signed `webhook.verification` event and no other events until it acknowledges one. |
created_at
required
| string (date-time) | |
updated_at
required
| string (date-time) |
Example response
{
"id": "whk_01HZ8X3J9K7P2QV6R5N4M3T2LA",
"object": "webhook_endpoint",
"mode": "live",
"url": "https://customer.example.com/webhooks/clement",
"description": "CRM sync — replies, bounces, unsubs",
"enabled_events": [
"message.replied",
"message.bounced",
"contact.unsubscribed"
],
"secret_last_four": "f4a2",
"disabled_at": null,
"consecutive_failures": 0,
"verified_at": "2026-04-26T11:20:01Z",
"created_at": "2026-04-26T11:20:00Z",
"updated_at": "2026-04-26T11:20:00Z"
} Errors
-
400Invalid request -
401Authentication failed -
403Insufficient scope -
404Not found -
409Conflict -
422Validation failed -
429Rate limited
See Errors for the full envelope and per-code reference.
Update a webhook endpoint
/v1/webhooks/{id} curl -X PATCH "https://api.clement.so/v1/webhooks/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1" \
-H "Authorization: Bearer $CLEMENT_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"url": "https://example.com/clement-webhook",
"description": "A short human-readable description.",
"enabled_events": [
"string"
],
"disabled": false
}' Path parameters
-
idstring required
Request body
| Property | Type | Description |
|---|---|---|
url | string (uri) | |
description | string | null | |
enabled_events | array<string> | |
disabled | boolean | Set to `true` to disable; `false` to re-enable a previously-disabled endpoint. |
Response200
Updated
| Property | Type | Description |
|---|---|---|
id
required
| string |
Example: "whk_…" |
object
required
| enum (1) | "webhook_endpoint" |
mode
required
| enum (2) | Mode of the key that created the endpoint. An endpoint only receives events of its own mode. "live""test" |
url
required
| string (uri) | |
description
required
| string | null | |
enabled_events
required
| array<string> | Subscribed event types. Use `*` to subscribe to all events. |
secret_last_four
required
| string | |
disabled_at
required
| string (date-time) | null | |
consecutive_failures
required
| integer | Failed attempts since the last successful delivery. The endpoint is disabled after it has been failing for 7 days. |
verified_at
required
| string (date-time) | null | When the endpoint first answered with a 2xx. A new endpoint, or one whose `url` changed, receives a signed `webhook.verification` event and no other events until it acknowledges one. |
created_at
required
| string (date-time) | |
updated_at
required
| string (date-time) |
Example response
{
"id": "whk_01HZ8X3J9K7P2QV6R5N4M3T2LA",
"object": "webhook_endpoint",
"mode": "live",
"url": "https://customer.example.com/webhooks/clement",
"description": "CRM sync — replies, bounces, unsubs",
"enabled_events": [
"message.replied",
"message.bounced",
"contact.unsubscribed"
],
"secret_last_four": "f4a2",
"disabled_at": null,
"consecutive_failures": 0,
"verified_at": "2026-04-26T11:20:01Z",
"created_at": "2026-04-26T11:20:00Z",
"updated_at": "2026-04-26T11:20:00Z"
} Errors
-
400Invalid request -
401Authentication failed -
403Insufficient scope -
404Not found -
409Conflict -
422Validation failed -
429Rate limited
See Errors for the full envelope and per-code reference.
Delete a webhook endpoint
/v1/webhooks/{id} curl -X DELETE "https://api.clement.so/v1/webhooks/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1" \
-H "Authorization: Bearer $CLEMENT_API_KEY" Path parameters
-
idstring required
Response200
Deleted
| Property | Type | Description |
|---|---|---|
id
required
| string |
Example: "whk_…" |
object
required
| enum (1) | "webhook_endpoint" |
mode
required
| enum (2) | Mode of the key that created the endpoint. An endpoint only receives events of its own mode. "live""test" |
url
required
| string (uri) | |
description
required
| string | null | |
enabled_events
required
| array<string> | Subscribed event types. Use `*` to subscribe to all events. |
secret_last_four
required
| string | |
disabled_at
required
| string (date-time) | null | |
consecutive_failures
required
| integer | Failed attempts since the last successful delivery. The endpoint is disabled after it has been failing for 7 days. |
verified_at
required
| string (date-time) | null | When the endpoint first answered with a 2xx. A new endpoint, or one whose `url` changed, receives a signed `webhook.verification` event and no other events until it acknowledges one. |
created_at
required
| string (date-time) | |
updated_at
required
| string (date-time) |
Example response
{
"id": "whk_01HZ8X3J9K7P2QV6R5N4M3T2LA",
"object": "webhook_endpoint",
"mode": "live",
"url": "https://customer.example.com/webhooks/clement",
"description": "CRM sync — replies, bounces, unsubs",
"enabled_events": [
"message.replied",
"message.bounced",
"contact.unsubscribed"
],
"secret_last_four": "f4a2",
"disabled_at": null,
"consecutive_failures": 0,
"verified_at": "2026-04-26T11:20:01Z",
"created_at": "2026-04-26T11:20:00Z",
"updated_at": "2026-04-26T11:20:00Z"
} Errors
-
400Invalid request -
401Authentication failed -
403Insufficient scope -
404Not found -
409Conflict -
422Validation failed -
429Rate limited
See Errors for the full envelope and per-code reference.
List delivery attempts for this endpoint
/v1/webhooks/{id}/deliveries curl -X GET "https://api.clement.so/v1/webhooks/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/deliveries" \
-H "Authorization: Bearer $CLEMENT_API_KEY" Path parameters
-
idstring required
Query parameters
-
limitinteger -
starting_afterstring -
ending_beforestring -
outcomestring
Response200
OK
| Property | Type | Description |
|---|---|---|
object
required
| enum (1) | "list" |
data
required
| array<WebhookDelivery> | |
has_more
required
| boolean | |
next_cursor
required
| string | null | |
prev_cursor
required
| string | null |
Example response
{
"object": "list",
"data": [
{
"id": "whd_01HZ8X3J9K7P2QV6R5N4M3T2LB",
"object": "webhook_delivery",
"endpoint_id": "whk_01HZ8X3J9K7P2QV6R5N4M3T2LA",
"event_id": "evt_01HZ8X3J9K7P2QV6R5N4M3T2L9",
"attempt": 1,
"outcome": "succeeded",
"status_code": 200,
"latency_ms": 124,
"error": null,
"delivered_at": "2026-04-29T16:42:14Z",
"next_retry_at": null,
"created_at": "2026-04-29T16:42:14Z"
}
],
"has_more": false,
"next_cursor": "string",
"prev_cursor": "string"
} Errors
-
400Invalid request -
401Authentication failed -
403Insufficient scope -
404Not found -
409Conflict -
422Validation failed -
429Rate limited
See Errors for the full envelope and per-code reference.
Retrieve a single delivery attempt
/v1/webhooks/{id}/deliveries/{delivery_id} curl -X GET "https://api.clement.so/v1/webhooks/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/deliveries/del_01HZ8X3J9K7P2QV6R5N4M3T2L1" \
-H "Authorization: Bearer $CLEMENT_API_KEY" Path parameters
-
idstring required -
delivery_idstring required
Response200
OK
| Property | Type | Description |
|---|---|---|
id
required
| string | |
object
required
| enum (1) | "webhook_delivery" |
endpoint_id
required
| string | |
event_id
required
| string | null | |
attempt
required
| integer | |
outcome
required
| enum (3) | "pending""succeeded""failed" |
status_code
required
| integer | null | |
latency_ms
required
| integer | null | |
error
required
| string | null | |
delivered_at
required
| string (date-time) | null | |
next_retry_at
required
| string (date-time) | null | |
created_at
required
| string (date-time) |
Example response
{
"id": "whd_01HZ8X3J9K7P2QV6R5N4M3T2LB",
"object": "webhook_delivery",
"endpoint_id": "whk_01HZ8X3J9K7P2QV6R5N4M3T2LA",
"event_id": "evt_01HZ8X3J9K7P2QV6R5N4M3T2L9",
"attempt": 1,
"outcome": "succeeded",
"status_code": 200,
"latency_ms": 124,
"error": null,
"delivered_at": "2026-04-29T16:42:14Z",
"next_retry_at": null,
"created_at": "2026-04-29T16:42:14Z"
} Errors
-
400Invalid request -
401Authentication failed -
403Insufficient scope -
404Not found -
409Conflict -
422Validation failed -
429Rate limited
See Errors for the full envelope and per-code reference.
Replay a delivery (creates a new attempt with the same payload)
/v1/webhooks/{id}/deliveries/{delivery_id}/redeliver curl -X POST "https://api.clement.so/v1/webhooks/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/deliveries/del_01HZ8X3J9K7P2QV6R5N4M3T2L1/redeliver" \
-H "Authorization: Bearer $CLEMENT_API_KEY" Path parameters
-
idstring required -
delivery_idstring required
Response202
Redelivery queued
| Property | Type | Description |
|---|---|---|
id
required
| string | |
object
required
| enum (1) | "webhook_delivery" |
endpoint_id
required
| string | |
event_id
required
| string | null | |
attempt
required
| integer | |
outcome
required
| enum (3) | "pending""succeeded""failed" |
status_code
required
| integer | null | |
latency_ms
required
| integer | null | |
error
required
| string | null | |
delivered_at
required
| string (date-time) | null | |
next_retry_at
required
| string (date-time) | null | |
created_at
required
| string (date-time) |
Example response
{
"id": "whd_01HZ8X3J9K7P2QV6R5N4M3T2LB",
"object": "webhook_delivery",
"endpoint_id": "whk_01HZ8X3J9K7P2QV6R5N4M3T2LA",
"event_id": "evt_01HZ8X3J9K7P2QV6R5N4M3T2L9",
"attempt": 1,
"outcome": "succeeded",
"status_code": 200,
"latency_ms": 124,
"error": null,
"delivered_at": "2026-04-29T16:42:14Z",
"next_retry_at": null,
"created_at": "2026-04-29T16:42:14Z"
} Errors
-
400Invalid request -
401Authentication failed -
403Insufficient scope -
404Not found -
409Conflict -
422Validation failed -
429Rate limited
See Errors for the full envelope and per-code reference.
Rotate the signing secret
/v1/webhooks/{id}/rotate Issues a new `whsec_…` secret. The old one stops working immediately. Update your verifier to the new secret as soon as the rotate response lands.
curl -X POST "https://api.clement.so/v1/webhooks/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/rotate" \
-H "Authorization: Bearer $CLEMENT_API_KEY" Path parameters
-
idstring required
Response200
Rotated
| Property | Type | Description |
|---|---|---|
id
required
| string |
Example: "whk_…" |
object
required
| enum (1) | "webhook_endpoint" |
mode
required
| enum (2) | Mode of the key that created the endpoint. An endpoint only receives events of its own mode. "live""test" |
url
required
| string (uri) | |
description
required
| string | null | |
enabled_events
required
| array<string> | Subscribed event types. Use `*` to subscribe to all events. |
secret_last_four
required
| string | |
disabled_at
required
| string (date-time) | null | |
consecutive_failures
required
| integer | Failed attempts since the last successful delivery. The endpoint is disabled after it has been failing for 7 days. |
verified_at
required
| string (date-time) | null | When the endpoint first answered with a 2xx. A new endpoint, or one whose `url` changed, receives a signed `webhook.verification` event and no other events until it acknowledges one. |
created_at
required
| string (date-time) | |
updated_at
required
| string (date-time) | |
secret
required
| string | The signing secret. Returned only on create + rotate; never again. |
Example response
{
"id": "whk_01HZ8X3J9K7P2QV6R5N4M3T2LA",
"object": "webhook_endpoint",
"mode": "live",
"url": "https://customer.example.com/webhooks/clement",
"description": "CRM sync — replies, bounces, unsubs",
"enabled_events": [
"message.replied",
"message.bounced",
"contact.unsubscribed"
],
"secret_last_four": "f4a2",
"disabled_at": null,
"consecutive_failures": 0,
"verified_at": "2026-04-26T11:20:01Z",
"created_at": "2026-04-26T11:20:00Z",
"updated_at": "2026-04-26T11:20:00Z"
} Errors
-
400Invalid request -
401Authentication failed -
403Insufficient scope -
404Not found -
409Conflict -
422Validation failed -
429Rate limited
See Errors for the full envelope and per-code reference.
Send a synthetic test delivery to this endpoint
/v1/webhooks/{id}/test Delivers a `webhook.test` event with `metadata.test = true` so the customer's endpoint can verify wiring without depending on real product activity.
curl -X POST "https://api.clement.so/v1/webhooks/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/test" \
-H "Authorization: Bearer $CLEMENT_API_KEY" Path parameters
-
idstring required
Response202
Test queued
| Property | Type | Description |
|---|---|---|
id
required
| string | |
object
required
| enum (1) | "webhook_delivery" |
endpoint_id
required
| string | |
event_id
required
| string | null | |
attempt
required
| integer | |
outcome
required
| enum (3) | "pending""succeeded""failed" |
status_code
required
| integer | null | |
latency_ms
required
| integer | null | |
error
required
| string | null | |
delivered_at
required
| string (date-time) | null | |
next_retry_at
required
| string (date-time) | null | |
created_at
required
| string (date-time) |
Example response
{
"id": "whd_01HZ8X3J9K7P2QV6R5N4M3T2LB",
"object": "webhook_delivery",
"endpoint_id": "whk_01HZ8X3J9K7P2QV6R5N4M3T2LA",
"event_id": "evt_01HZ8X3J9K7P2QV6R5N4M3T2L9",
"attempt": 1,
"outcome": "succeeded",
"status_code": 200,
"latency_ms": 124,
"error": null,
"delivered_at": "2026-04-29T16:42:14Z",
"next_retry_at": null,
"created_at": "2026-04-29T16:42:14Z"
} Errors
-
400Invalid request -
401Authentication failed -
403Insufficient scope -
404Not found -
409Conflict -
422Validation failed -
429Rate limited
See Errors for the full envelope and per-code reference.