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#
- Create a subscription with your HTTPS URL and a secret.
- Send a test with POST
/v1/webhooks/subscriptions/{id}/test; you receive awebhook.testevent. - Verify the signature on every request (below).
- Return 2xx quickly. Do heavy work after responding.
- Use
external_idto update your own record without storing Edesy IDs. - Check deliveries at GET
/v1/webhooks/subscriptions/{id}/deliveriesand replay failures with POST.../deliveries/replayor.../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.