Webhooks

Be told when something happens, instead of asking.

A webhook endpoint is an organization-wide subscription to a list of events. Deliveries are signed with HMAC-SHA256 over <timestamp>.<body> and sent as X-Vocalis-Signature: t=…,v1=…; verify it before trusting a payload, and reject a timestamp that is not recent. Each delivery carries X-Vocalis-Event-Id, equal to the body's id and the same on every retry, so use it as an idempotency key. It is not the id the same event has on a stream: each surface numbers its own. Failures are retried with exponential backoff; a 4xx other than 408 or 429 is treated as a refusal and not retried, and an endpoint that fails continuously is switched off rather than being sent events into a void.

The signing secret is returned exactly once, when the endpoint is created and again when it is rotated. There is no endpoint that reads it back. While a rotation overlaps, a delivery carries one v1= signature per live secret (t=…,v1=<new>,v1=<old>); accept it when any of them matches a secret you hold.

List endpoints#

Every webhook endpoint in the organization.

GET/api/v1/webhook-endpoints

Responses

The endpoints.

curl -X GET "https://voice.evryhealth.com/api/v1/webhook-endpoints" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"data": {
"endpoints": [
{
"id": "c41a…",
"name": "Ops receiver",
"url": "https://hooks.example.com/vocalis",
"events": [
"call.ended",
"call.analysis.completed"
],
"secretPrefix": "whsec_9Qa3Kd",
"isEnabled": true,
"consecutiveFailures": 0,
"lastSuccessAt": "2026-09-15T13:58:02.000Z"
}
]
},
"meta": {
"requestId": "req_…"
}
}

Create an endpoint#

Subscribe a URL to a list of events. The URL is checked at creation: it must be public http(s), and one resolving to a private or link-local address is refused.

POST/api/v1/webhook-endpoints

Body

application/json
name
string
required
url
string
required
events
string[]
required

Responses

Created. The secret appears here and nowhere else.

curl -X POST "https://voice.evryhealth.com/api/v1/webhook-endpoints" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ops receiver",
"url": "https://hooks.example.com/vocalis",
"events": [
"call.ended",
"intelligence.speaker.identified"
]
}'
Response201
{
"data": {
"id": "c41a…",
"name": "Ops receiver",
"url": "https://hooks.example.com/vocalis",
"events": [
"call.ended",
"intelligence.speaker.identified"
],
"secret": "whsec_9Qa3Kd…",
"secretPrefix": "whsec_9Qa3Kd"
},
"meta": {
"requestId": "req_…"
}
}

Get an endpoint#

One endpoint. The signing secret is never returned here.

GET/api/v1/webhook-endpoints/{endpointId}

Path parameters

endpointIdstringrequired
Endpoint id.

Responses

The endpoint.

curl -X GET "https://voice.evryhealth.com/api/v1/webhook-endpoints/{endpointId}" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"data": {
"id": "c41a…",
"name": "Ops receiver",
"url": "https://hooks.example.com/vocalis",
"events": [
"call.ended"
],
"secretPrefix": "whsec_9Qa3Kd",
"isEnabled": true
},
"meta": {
"requestId": "req_…"
}
}

Update an endpoint#

Change the URL, the subscription, or pause it. Re-enabling clears the failure count, so an endpoint switched off after an outage starts clean.

PATCH/api/v1/webhook-endpoints/{endpointId}

Path parameters

endpointIdstringrequired
Endpoint id.

Body

application/json
name
string
url
string
events
string[]
isEnabled
boolean

Responses

The updated endpoint.

curl -X PATCH "https://voice.evryhealth.com/api/v1/webhook-endpoints/{endpointId}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
"call.ended"
],
"isEnabled": true
}'
Response200
{
"data": {
"id": "c41a…",
"events": [
"call.ended"
]
},
"meta": {
"requestId": "req_…"
}
}

Rotate the secret#

Issue a new signing secret. The previous one keeps signing deliveries alongside it for `expiresInSeconds` (24 hours by default, up to 7 days), so every delivery in that window, including retries of events queued before the rotation, carries a signature from each and your receiver can switch over when it is ready. `previousSecretExpiresAt` says when the old one stops. Send `expiresInSeconds: 0` to end the old secret at once, which is what you want after a leak: an overlap is a period in which the leaked secret still signs valid-looking traffic. Rotating again during an overlap retires the oldest secret immediately.

POST/api/v1/webhook-endpoints/{endpointId}/rotate-secret

Path parameters

endpointIdstringrequired
Endpoint id.

Body

application/json
expiresInSeconds
integer, 0 to 604800

Responses

The new secret, returned once.

curl -X POST "https://voice.evryhealth.com/api/v1/webhook-endpoints/{endpointId}/rotate-secret" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expiresInSeconds": 3600
}'
Response200
{
"data": {
"id": "c41a…",
"secret": "whsec_2Pb7Xm…",
"secretPrefix": "whsec_2Pb7Xm",
"previousSecretExpiresAt": "2026-09-16T14:07:44.000Z"
},
"meta": {
"requestId": "req_…"
}
}

Delivery log#

What we tried to send, how many times, and what the receiver said. Payloads are not returned. They were sent to you, and they are encrypted at rest because they carry PHI.

GET/api/v1/webhook-endpoints/{endpointId}/deliveries

Path parameters

endpointIdstringrequired
Endpoint id.

Query parameters

statusstring
pending, in_flight, succeeded, failed or dead_lettered.
limitnumber
Up to 200.
Default50
cursorstring
From meta.pagination.nextCursor.

Responses

A page of deliveries.

curl -X GET "https://voice.evryhealth.com/api/v1/webhook-endpoints/{endpointId}/deliveries" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"data": {
"deliveries": [
{
"id": "7f21…",
"eventType": "call.ended",
"eventId": "1789525082142-0",
"status": "succeeded",
"attemptCount": 1,
"responseStatus": 200,
"createdAt": "2026-09-15T14:07:44.400Z"
}
]
},
"meta": {
"requestId": "req_…",
"pagination": {
"nextCursor": null,
"limit": 50
}
}
}

Delete an endpoint#

Stop delivering. Queued deliveries for it are abandoned rather than chased.

DELETE/api/v1/webhook-endpoints/{endpointId}

Path parameters

endpointIdstringrequired
Endpoint id.

Responses

Deleted.

curl -X DELETE "https://voice.evryhealth.com/api/v1/webhook-endpoints/{endpointId}" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"data": {
"deleted": true
},
"meta": {
"requestId": "req_…"
}
}