Skip to main content
Public API for managing webhook subscriptions. Use these endpoints to create, retrieve, update, and delete webhook subscriptions that deliver event notifications to your own HTTP endpoints.
  • Base URL: https://api.verbex.ai/v1/public/webhook/subscription
  • Auth: Bearer token (API key) in the Authorization header
  • Content type: application/json (for POST and PUT)
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:
Security note: Your API key is a credential. Never commit it to source control, share it in tickets, or expose it client-side. If a key is ever exposed, rotate it immediately.

Endpoints

1. Create a subscription

Description: Registers a new webhook subscription so Verbex delivers selected event notifications to your endpoint as HTTP POST 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.
Using signed webhooks (client side): on every delivery, verify the X-Webhook-Signature header with your stored secret before trusting the payload — see Verifying webhook signatures (client side).
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).
Sample Request
Sample Response201 Created (signed subscription; secret shown only here)
Notes
  • headers are forwarded on each delivery to your url. Use them to authenticate Verbex → your endpoint (this Authorization is 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 that id — it’s required for GET / PUT / DELETE.

2. Update a subscription

Description: Replaces the full configuration of an existing subscription (full-replacement PUT 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
Sample Response200 OK (signing already enabled, so no secret returned)
Notes
  • This is a full replacement (PUT semantics). Include every field you want to keep, or it may be cleared/reset.
  • updated_at changes on each successful update; created_at stays fixed.

3. Rotate the signing secret

Description: Generates a new signing secret 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.
Sample Request
Sample Response200 OK
Notes
  • Returns 409 Conflict if the subscription was created without signing enabled (no secret to rotate — re-create or PUT with enable_signed_webhooks: true first).
  • 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, set active: false via Update. Method: DELETE /v1/public/webhook/subscription/{id} Headers No request body. Path parameters Response204 No Content (empty body). Sample Request
Sample Response204 No Content (empty body). Notes
  • Returns 404 Not Found if no subscription exists for the id.
  • To pause deliveries without deleting, PUT with "active": false — this preserves the subscription and its id.

5. Get a subscription

Description: Retrieves a single subscription by its ID. Read endpoints never return the signing secret — 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
Sample Response200 OK (no secret)
Notes
  • Returns 404 Not Found if no subscription exists for the id.

6. List subscriptions

Description: Lists the subscriptions in your organization (cursor-paginated). Note the plural subscriptions 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
Sample Response200 OK
Each data item has the same shape as the get response (no secret).

Event types

Values for the events 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 HTTP POST 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
Sample delivery — callAnalysis.pcaCompleted
Delivery headers
X-Webhook-Event, X-Webhook-TraceId, and X-Webhook-Timestamp are routing hints and are not covered by the HMAC signature — do not rely on them for security. Only the raw request body and the t value inside X-Webhook-Signature are signed.Note: t (inside X-Webhook-Signature) is a Unix epoch-seconds integer and is what the replay check uses; the separate X-Webhook-Timestamp header is an ISO-8601 string. They are different formats and values — don’t conflate them.

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:
  • event
  • traceId
  • timestamp
  • organizationId
  • payload.call_id
  • payload.agent_id
  • payload.status
eventName is deprecated and will be removed in a future version — migrate to the event field. It is still sent today for backward compatibility, but do not build new integrations on it.
Optional / may change or be added later: additional 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)

When enable_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, two v1= values are sent (new secret first, then previous) — accept the request if either matches.
What is signed: the raw request body, a literal ., then t:
Key: use the full whsec_… secret string exactly as returned, as UTF-8 bytes. Do not strip the whsec_ prefix and do not base64-decode it. Steps
  1. Parse t and all v1 values from X-Webhook-Signature.
  2. Reject if |now − t| > 300 seconds (replay protection).
  3. Compute expected = hex(HMAC_SHA256(secret, raw_body + "." + t)).
  4. Accept if expected equals any v1 (use a constant-time comparison).
  5. Don’t rely on X-Webhook-Event, X-Webhook-TraceId, or X-Webhook-Timestamp for security — they are routing hints and are not covered by the signature.
Verify against the raw request body bytes exactly as received. Parsing then re-serializing the JSON will change the bytes and break the signature.
Node.js (Express)
Python (Flask)

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 (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:
The 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 url should respond 2xx quickly. Use the headers field to let your receiver authenticate inbound calls.
  • Pause vs. delete — to temporarily stop deliveries, PUT with "active": false rather than deleting; this preserves the subscription and its ID.