RevDesk POSTs an event to your URL whenever something happens in your workspace — a call ends, an SMS
is delivered, a number is provisioned. Subscribe once per event type; we deliver as the events occur.
Subscribe
curl -X POST https://api.revdesk.com/api/v1/webhook_subscriptions \
-H "Authorization: Bearer $REVDESK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "webhook_url": "https://example.com/hooks/revdesk", "event_type": "call_completed" }'
# → { "id": "whk_…" }
| Field | Required | Description |
|---|
webhook_url | yes | HTTPS URL that receives the POST. |
event_type | yes | One of the event types below. |
workflow_id | no | Scope delivery to a single workflow. |
campaign_id | no | Scope delivery to a single campaign. |
Unsubscribe
curl -X DELETE https://api.revdesk.com/api/v1/webhook_subscriptions/whk_… \
-H "Authorization: Bearer $REVDESK_API_KEY"
# → 204 No Content
Event types
call_completed fires on every call end. The disposition events below fire in addition, only
on the matching outcome — subscribe to them to branch (retry, SMS fallback) without parsing call
records. Answered/successful calls emit no disposition event.
event_type | Fires when |
|---|
new_call | A call starts. |
call_completed | A call ends (any outcome). |
call_no_answer | A call ended unanswered (rang out). |
call_voicemail | A call reached voicemail / an answering machine. |
call_busy | The destination was busy. |
call_failed | A call failed (declined, unreachable, carrier rejection). |
contact_replied | An inbound SMS was received. |
sms_sent | An outbound SMS was accepted by the carrier. |
sms_delivered | An outbound SMS was delivered to the handset. |
sms_failed | An outbound SMS failed / was undelivered. |
contact_created | A new contact was created. |
number_purchased | A phone number finished provisioning and went active. |
number_released | A phone number was released from your workspace. |
texting_registration_status_changed | Your A2P texting registration (brand or campaign) was approved or rejected by carriers. On campaign approval the payload lists texting_enabled_numbers — the moment to start sending. |
new_appointment | A booking was created. |
workflow_contact_completed | A contact finished a workflow. |
Delivery
Each event is a POST with a JSON body to your webhook_url. Delivery is fire-and-forget; return a
2xx quickly and do any heavy work asynchronously. Subscribe to the same event type multiple times
to fan out to multiple URLs.
Two delivery shapes. Call-disposition and SMS events are delivered flat (the event object
is the request body) and are not signed. Number-lifecycle events are delivered enveloped
(triggerEvent / createdAt / payload) and HMAC-signed. Match the shape to the event family
you subscribed to — see the examples below.
Signature verification (number-lifecycle only)
Number-lifecycle deliveries carry an X-revdesk-Signature-256 header. It is the lowercase hex
HMAC-SHA256 of the raw request body, keyed with your subscription’s signing secret. Compute the
same HMAC over the bytes you receive and compare with a constant-time equality check; reject on
mismatch. If the subscription has no signing secret configured, the header carries the literal value
no-secret-provided instead of a signature.
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
Call-disposition and SMS events are unsigned — they carry no signature header. Restrict those
handlers to a secret path or IP allowlist if you need to authenticate the sender.
Payload examples
Call disposition (call_no_answer, call_voicemail, call_busy, call_failed) — flat, unsigned:
{
"id": "call_abc123",
"from_number": "+15551234567",
"to_number": "+15557654321",
"direction": "outbound",
"duration_seconds": 0,
"status": "VOICEMAIL",
"failure_code": "voicemail_reached",
"failure_category": "voicemail",
"disconnection_reason": null,
"created_at": "2026-06-30T19:30:00.000Z"
}
Outbound SMS (sms_sent, sms_delivered, sms_failed) — flat, unsigned:
{
"id": "msg_uuid",
"phone_number": "+15557654321",
"from_number": "+15551234567",
"direction": "outbound",
"status": "delivered",
"contact_token": "…",
"provider_id": "…",
"error_message": null,
"channel": "sms",
"created_at": "2026-06-30T19:30:00.000Z"
}
Number lifecycle (number_purchased, number_released) — enveloped + HMAC-signed. The event
data is nested under payload, and triggerEvent is the internal event name (number.purchased /
number.released):
{
"triggerEvent": "number.purchased",
"createdAt": "2026-06-30T19:30:00.000Z",
"payload": {
"id": 4821,
"phone_number": "+15551234567",
"area_code": "555",
"status": "ACTIVE",
"organization_id": "…"
}
}
Delivered with header X-revdesk-Signature-256: <hex HMAC-SHA256 of the raw body>.