Webhooks

Signed contact-center and dialer webhooks - call.answered, call.completed, disposition.submitted, recording.ready - with retries, delivery logs and replay.

2 min read

Base URL
https://voice-api.edesy.in/v1/ccaas
Auth
Authorization: Bearer sk_...Get a key

Edesy sends signed HTTPS webhooks so your CRM can record contact-center outcomes. For agent-dialer calls you get call.answered, call.completed, disposition.submitted and recording.ready, each carrying your own external_id. Every request is signed with HMAC-SHA256 and retried up to 6 attempts with backoff, and each delivery is logged with replay.

Facts#

Item Detail
Subscribe POST/v1/webhooks/subscriptions
Events call.answered, call.completed, disposition.submitted, recording.ready, call.missed
Filter event_types on the subscription; empty means all events
Signature X-Edesy-Signature: sha256=<hex HMAC-SHA256 of the raw body>
Timeout 5 seconds per attempt
Retries 6 attempts; backoff 30 s, 1 m, 2 m, 4 m, 8 m
Success Any 2xx response
Tools Test event, delivery list, replay one or many deliveries
Contact-center log Settings > Webhooks in ccaas.edesy.in, or /v1/ccaas/webhook-logs

Envelope#

{
  "event": "disposition.submitted",
  "timestamp": "2026-10-28T08:20:31Z",
  "data": {
    "call_sid": "c0a8...",
    "external_id": "crm-lead-1042",
    "campaign_id": "5d2e...",
    "contact_id": "91ab...",
    "to": "+919876543210",
    "direction": "outbound",
    "agent": { "id": "8e1f...", "number": "1001", "name": "Agent name" },
    "disposition": { "id": "3c7d...", "name": "Not interested", "is_terminal": true, "notes": "Already bought elsewhere" },
    "custom_fields": { "plan": "gold" }
  }
}

All event types use the same data shape. Fields an event does not use are left out. call.completed adds status (completed, busy, no-answer, failed), duration_sec, sip_status, sip_reason and answered_by (when AMD ran). recording.ready adds recording_url.

Events#

Event When
call.answered A dialer lead picks up
call.completed A dialer call ends, whatever the outcome
disposition.submitted An agent submits a wrap-up code on a dialer call
recording.ready A dialer call's recording is stored and playable
call.missed A number set to the missed-call target declines a call

How to receive webhooks#

  1. Create a subscription with your HTTPS URL and a secret.
  2. Send a test with POST/v1/webhooks/subscriptions/{id}/test; you receive a webhook.test event.
  3. Verify the signature on every request (below).
  4. Return 2xx quickly. Do heavy work after responding.
  5. Use external_id to update your own record without storing Edesy IDs.
  6. Check deliveries at GET/v1/webhooks/subscriptions/{id}/deliveries and replay failures with POST.../deliveries/replay or .../deliveries/{delivery_id}/replay.
curl -X POST https://voice-api.edesy.in/v1/webhooks/subscriptions \
  -H "Authorization: Bearer sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://crm.example.com/edesy", "secret": "your_secret", "event_types": ["disposition.submitted", "call.completed"]}'

Verify the signature (Node.js)#

import crypto from 'crypto';

function isValid(rawBody, header, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return header && crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

Compute the HMAC over the raw request body, before any JSON parsing.

FAQ#

Do inbound queue calls send webhooks?#

The events above are for agent-dialer calls (and call.missed for missed-call numbers). For inbound queue history, use reports and the agent call APIs.

What if my endpoint is down?#

Each delivery is retried up to 6 attempts with growing gaps. After that it is marked exhausted; replay it from the delivery log.

Can I receive only some events?#

Yes. Set event_types on the subscription.

Are webhooks sent to private IP addresses?#

No. Targets must be public HTTPS endpoints.

Can I have more than one subscription?#

Yes. Each subscription gets its own events and deliveries.

Why does the signature not match?#

Make sure you hash the raw bytes of the body with the same secret, and compare the full sha256=... string.

Was this page helpful?