What is a webhook endpoint?
A webhook endpoint is an HTTPS URL you register to receive events as they happen.GET /v1/webhooks/event-types lists every event type Endstate emits; the examples in this guide use the placeholder example.updated. Each delivery is a signed POST with a JSON envelope. You can register up to 16 endpoints per organization, each subscribed to its own set of event types.
Registering an endpoint
CallPOST /v1/webhooks/endpoints with the URL and the event types to subscribe to. The response includes the endpoint’s signing secret. Store it; you need it to verify deliveries. The URL must use https and resolve to a public host.
The list is the same for every organization and grows as products add events.
The delivery
Every delivery carries three headers in the Standard Webhooks format:
The body is the event envelope:
2xx once you have durably recorded the event. Anything else, or no response within 15 seconds, is a failure and the delivery is retried: 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, 10 hours. After the eighth attempt the event is marked failed for that endpoint; the attempt log shows every try.
Delivery is at least once. If your handler processed the event but timed out before responding, the same webhook-id arrives again.
Verifying a delivery
Verify before you parse. Compute HMAC-SHA256 over${id}.${timestamp}.${rawBody} with the secret’s bytes (the part after whsec_, base64-decoded), base64-encode the result, and compare it in constant time against each v1, value in webhook-signature. Accept the delivery if any matches and the timestamp is within tolerance. Any Standard Webhooks library does this for you.
Secrets
The secret is returned when you create the endpoint and again fromPOST .../rotate-secret. An idempotent replay of either request returns the endpoint without the secret, so store it when you first receive it. An admin API key can read it at any time with GET /v1/webhooks/endpoints/{id}/secret; every read and every rotation is recorded.
After a rotation, deliveries carry a signature from the new secret and one from the previous secret for 24 hours. Switch your verifier to the new secret at any point in that window without missing events.
When an endpoint is disabled
Every failed delivery to an otherwise healthy endpoint setsfail_since on the endpoint to the time the trouble started; a successful delivery clears it. Watch it: the next failed delivery after fail_since is five days old moves the endpoint to disabled, and a 410 Gone response disables it immediately. Deliveries then stop and disabled_reason says why.
Re-enable with PATCH /v1/webhooks/endpoints/{id} and { "status": "enabled" }. That clears fail_since and starts the five-day clock over. Re-enabling does not resend what was missed; recover it, below.
You can also pause an endpoint yourself, for example while its receiver is being redeployed: the same PATCH with { "status": "disabled" } stops deliveries and sets disabled_reason to disabled by request. Re-enable and recover the same way.
Testing an endpoint
POST /v1/webhooks/endpoints/{id}/test with { "event_type": "..." } sends the catalog’s example payload for that type to this endpoint only. The event has audience: "test" and is otherwise a normal delivery: signed with the endpoint’s secret, retried on the usual schedule, and counted toward endpoint health, so a receiver that fails a test shows up in fail_since and one that succeeds clears it. The response carries the event and its first attempt; follow the rest through the event’s attempts.
Recovering missed events
POST /v1/webhooks/endpoints/{id}/recover with { "since": "<time>" } queues a delivery for every event the endpoint subscribes to that was created since that time and never reached it, for example while it was disabled, and the next attempt for every event whose latest attempt to it failed. Events that got through, or still have an attempt pending, are left alone, so recovering the same window twice queues nothing the second time. The response says how many were queued; deliveries start within about a minute and follow the normal retry schedule. since must be within the last 30 days.
Redelivering one event
POST /v1/webhooks/events/{id}/redeliver with { "endpoint_id": "..." } sends that event to that endpoint now. If a retry is already scheduled for later, that retry goes out at once instead of waiting; otherwise one more attempt is made, for example after the retry schedule was exhausted. It is refused only while an attempt is being sent at that moment, and, for an event that never reached the endpoint, unless the endpoint subscribes to its type.
Reading the delivery log
GET /v1/webhooks/events lists events, GET /v1/webhooks/events/{id} returns one event with the data payload it was delivered with, and GET /v1/webhooks/events/{id}/attempts shows every attempt for one event: status, response code, duration, and the endpoint it went to. Attempts to endpoints you have since deleted are still listed, marked deleted: true.
Deleting an endpoint
DELETE /v1/webhooks/endpoints/{id} stops deliveries and removes the endpoint from lists. Its delivery history remains readable through webhook events.
