- Base URL:
https://api.verbex.ai/v1/public/webhook/subscription - Auth: Bearer token (API key) in the
Authorizationheader - Content type:
application/json(forPOSTandPUT)
Response convention — null/unset fields are omitted. Optional fields that have no value (e.g.
headers, description, agent_ids, workspace_ids) are left out of the response body entirely rather than returned as null. Treat a missing key as “not set”. The … | null notation in the tables below indicates such a field; in practice the key is simply absent when unset.Authentication
Every request must include your API key as a Bearer token:Endpoints
1. Create a subscription
Description: Registers a new webhook subscription so Verbex delivers selected event notifications to your endpoint as HTTPPOST requests. Optionally scope it to specific agents or workspaces, filter the events it receives, and enable HMAC-signed delivery. Returns the created subscription — including a one-time signing secret when enable_signed_webhooks is true. Use that secret to verify the authenticity of incoming deliveries: Verbex signs each request (HMAC) so your endpoint can confirm it genuinely originated from Verbex and reject forged or tampered payloads. The secret (prefixed whsec_) is returned only once at creation, so store it securely; if it’s lost or leaked, rotate it via the rotate-secret endpoint.
Method: POST /v1/public/webhook/subscription
Headers
Request Body
Response Body
secret is view-once. It is returned in the response body only at issuance (create with signing on, a PUT that enables signing, or the rotate-secret endpoint) and is never returned by read endpoints (GET / list). Store it as soon as you receive it; if it’s lost, obtain a new one via rotate-secret (the old one stays valid for a 72-hour grace window).201 Created (signed subscription; secret shown only here)
headersare forwarded on each delivery to yoururl. Use them to authenticate Verbex → your endpoint (thisAuthorizationis your receiver’s token, separate from the API key used to call this API).- The response returns the created subscription, including its generated
id. Save thatid— it’s required for GET / PUT / DELETE.
2. Update a subscription
Description: Replaces the full configuration of an existing subscription (full-replacementPUT semantics — send every field you want to keep, or it may be cleared/reset). The required enable_signed_webhooks also drives the signing lifecycle (enable or disable signing). Returns the updated subscription — including a new one-time signing secret only if this call just enabled signing on a previously-unsigned subscription.
Method: PUT /v1/public/webhook/subscription/{id}
Headers
Path parameters
Request Body
Response Body
Signing lifecycle on update (
enable_signed_webhooks):
Sample Request
200 OK (signing already enabled, so no secret returned)
- This is a full replacement (
PUTsemantics). Include every field you want to keep, or it may be cleared/reset. updated_atchanges on each successful update;created_atstays fixed.
3. Rotate the signing secret
Description: Generates a new signingsecret for a subscription while keeping the previous secret valid for a 72-hour grace window, so in-flight receivers don’t break. During that window Verbex signs each delivery with both secrets (two v1= values in X-Webhook-Signature), so a receiver still using the old secret keeps verifying until it switches to the new one. The new secret is returned once in this response — store it immediately.
Method: POST /v1/public/webhook/subscription/{id}/rotate/secret
Headers
No request body.
Path parameters
Response Body
Returns only the two rotation fields — not the full subscription object:
The response does not include
id, signing_enabled, or any other subscription fields. To read the rest of the subscription after rotating, call Get a subscription.200 OK
- Returns
409 Conflictif the subscription was created without signing enabled (no secret to rotate — re-create orPUTwithenable_signed_webhooks: truefirst). - Update your stored secret to the new value before the previous one expires; after the 72-hour window the old secret is rejected.
4. Delete a subscription
Description: Permanently deletes a subscription; Verbex stops delivering its events. This cannot be undone — to temporarily stop deliveries instead, setactive: false via Update.
Method: DELETE /v1/public/webhook/subscription/{id}
Headers
No request body.
Path parameters
Response —
204 No Content (empty body).
Sample Request
204 No Content (empty body).
Notes
- Returns
404 Not Foundif no subscription exists for theid. - To pause deliveries without deleting,
PUTwith"active": false— this preserves the subscription and itsid.
5. Get a subscription
Description: Retrieves a single subscription by its ID. Read endpoints never return the signingsecret — use signing_enabled to tell whether signing is on.
Method: GET /v1/public/webhook/subscription/{id}
Headers
No request body.
Path parameters
Response Body
Sample Request
200 OK (no secret)
- Returns
404 Not Foundif no subscription exists for theid.
6. List subscriptions
Description: Lists the subscriptions in your organization (cursor-paginated). Note the pluralsubscriptions path — distinct from the singular get-by-id. secret is never included.
Method: GET /v1/public/webhook/subscriptions
Headers
No request body.
Query parameters
Response Body
Sample Request
200 OK
Each
data item has the same shape as the get response (no secret).Event types
Values for theevents array use the Service.Event pattern — the service name and event name joined by a dot (e.g. CallHandler.CallStarted). Names are case-sensitive; use them exactly as listed below (note CallHandler is PascalCase, callAnalysis is lower-camelCase). A bare event name without its service prefix (e.g. CallStarted) is rejected with 400 — Event type '…' does not exist.
Currently subscribable:
Notes
- An empty array (
[]) or["*"]subscribes to all subscribable event types. - Some event types exist but are internal-only and cannot be subscribed to via public webhooks — subscribing to them returns
400 — Event type '…' is internal-only and cannot be subscribed to via public webhooks(e.g.CallHandler.AnsweringMachineDetected).
Event Delivery
Every delivery is an HTTPPOST to your subscription url with a JSON body in the following envelope. The event-specific data lives under payload; the surrounding fields are the same for every event type. Test deliveries use the identical envelope (the only difference is payload comes from the event type’s sample payload and traceId is prefixed test-).
Envelope fields
Sample delivery —
CallHandler.CallStarted
callAnalysis.pcaCompleted
Stable vs. optional fields
Verbex maintains backward compatibility: the fields below remain stable, and new optional fields may be added over time. Your receiver should tolerate unknown fields rather than strictly validate the whole body. Stable — safe to rely on:eventtraceIdtimestamporganizationIdpayload.call_idpayload.agent_idpayload.status
payload fields (e.g. reason, analysisTimestamp, and event-specific extras), and any future top-level fields.
Webhook subscription changes, including deletion, may take up to 10 minutes to fully propagate due to internal caching.
Verifying webhook signatures (client side)
Whenenable_signed_webhooks is true, every delivery carries an X-Webhook-Signature header. Verify it before trusting the payload.
Header format
t— Unix timestamp (seconds) when the delivery was signed.v1— lowercase hex HMAC-SHA256. During the 72-hour grace window after a secret rotation, twov1=values are sent (new secret first, then previous) — accept the request if either matches.
., then t:
whsec_… secret string exactly as returned, as UTF-8 bytes. Do not strip the whsec_ prefix and do not base64-decode it.
Steps
- Parse
tand allv1values fromX-Webhook-Signature. - Reject if
|now − t| > 300seconds (replay protection). - Compute
expected = hex(HMAC_SHA256(secret, raw_body + "." + t)). - Accept if
expectedequals anyv1(use a constant-time comparison). - Don’t rely on
X-Webhook-Event,X-Webhook-TraceId, orX-Webhook-Timestampfor security — they are routing hints and are not covered by the signature.
Error responses
Errors are returned with a non-2xx status code and this JSON body:
Authentication errors use a different shape. The schema above is returned by the webhook service for application-level errors (The
400, 404, 409, 500). Authentication failures rejected at the API gateway (401) return a leaner body with only error, message, and a camelCase traceId — no status, type, path, or timestamp:message text is informational (e.g. "Authentication failed" for an invalid token, "Authentication failure" when the Authorization header is missing). Note traceId is camelCase here, unlike the snake_case trace_id on service-level errors. Parse error bodies defensively: rely on the HTTP status code, and treat status/type/path/trace_id as present only on service-level errors.Quick reference
Common headers
Tips
events: []— an empty array (or["*"]) subscribes to all event types. To receive only specific events, list them explicitly (see Event types).- Verify delivery — your
urlshould respond2xxquickly. Use theheadersfield to let your receiver authenticate inbound calls. - Pause vs. delete — to temporarily stop deliveries,
PUTwith"active": falserather than deleting; this preserves the subscription and its ID.

