A subscription is an HTTPS endpoint registered on your workspace that receives Voice API events. You choose which event_types it gets (or all), set an optional signing secret, and every delivery is logged with its response code so you can debug and replay. Manage subscriptions on the Webhooks page of voice-app.edesy.in or with the API below.
Endpoints#
| Method | Path | Description |
|---|---|---|
| GET | /v1/webhooks/subscriptions |
List |
| POST | /v1/webhooks/subscriptions |
Create |
| PATCH | /v1/webhooks/subscriptions/{id} |
Update URL, secret, event types or active flag |
| DELETE | /v1/webhooks/subscriptions/{id} |
Delete |
| POST | /v1/webhooks/subscriptions/{id}/test |
Send a webhook.test event and return your endpoint's status code |
| GET | /v1/webhooks/subscriptions/{id}/deliveries |
Delivery log (limit, default 50) |
| POST | /v1/webhooks/subscriptions/{id}/deliveries/{delivery_id}/replay |
Re-send one delivery |
| POST | /v1/webhooks/subscriptions/{id}/deliveries/replay |
Re-send many. See Retries and Replay |
Create#
curl -X POST https://voice-api.edesy.in/v1/webhooks/subscriptions \
-H "Authorization: Bearer $EDESY_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://crm.example.com/edesy/events",
"secret": "a-long-random-string",
"event_types": ["call.completed", "disposition.submitted", "recording.ready"]
}'
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | Required | Public HTTPS endpoint |
secret |
string | Optional | Signs every delivery with X-Edesy-Signature. Strongly recommended |
event_types |
array | Optional | Events to receive. Empty or omitted means all |
{
"data": {
"id": "0b9f5d2c-0000-4000-8000-000000000000",
"url": "https://crm.example.com/edesy/events",
"event_types": ["call.completed", "disposition.submitted", "recording.ready"],
"has_secret": true,
"is_active": true,
"created_at": "2026-10-28T09:00:00Z",
"updated_at": "2026-10-28T09:00:00Z"
}
}
The secret is never returned; has_secret tells you one is set. To rotate it, PATCH a new secret and accept both old and new signatures on your side for a short overlap.
Valid event types: call.answered, call.completed, recording.ready, disposition.submitted, call.missed. Unknown types are rejected so a subscription cannot wait for an event that never fires.
Envelope#
Every delivery is a JSON POST:
{
"event": "call.completed",
"timestamp": "2026-10-28T11:19:02Z",
"data": {
"call_sid": "CA_6f1c2d3e-0000-4000-8000-000000000000",
"external_id": "crm-lead-77812",
"campaign_id": "...",
"contact_id": "...",
"to": "+9198XXXXXXXX",
"from": "+9180XXXXXXXX",
"direction": "outbound",
"status": "completed",
"duration_sec": 142,
"sip_status": 200,
"answered_by": "human",
"custom_fields": { "city": "Pune" }
}
}
data has one shape for every event; each event fills the fields that apply:
| Field | Description |
|---|---|
call_sid |
The call |
external_id, custom_fields |
Your CRM ID and fields from the lead |
campaign_id, contact_id |
Edesy campaign and contact |
to, from, direction |
Call details |
phone_number_id, phone_number_name |
Your number (call.missed) |
status |
completed, busy, no-answer, failed (call.completed), answered, or missed |
duration_sec, sip_status, sip_reason |
Call result |
answered_by |
human, machine or unknown when AMD ran |
agent |
id, number, name |
disposition |
id, name, is_terminal, notes |
recording_url |
Playable recording (recording.ready) |
Responding#
Reply with any 2xx within 5 seconds. Do the slow work (CRM writes) after responding, from a queue. Anything else, including a timeout, is retried. See Retries and Replay.