Build the call flow once in the visual builder. Then place calls from your CRM, store or backend with one request, pass each customer's details as variables, call your own APIs when they press a key, and get the result posted back when the call ends.
Your system starts the call; the flow does the talking; results come back to you
POST /v1/ivr/calls with your API key in X-API-Key (or Authorization: Bearer). The base URL is shared with your API key.
{
"flow_id": "5b1c2f0e-...",
"to": "+919800000000",
"variables": { "order_id": "A123", "name": "Ravi", "amount": 499 },
"reference_id": "crm-lead-42",
"status_callback_url": "https://crm.example.com/ivr/status",
"ring_timeout_sec": 45
}
| Field | Notes |
|---|---|
flow_id |
Required. The flow must be published. |
to |
Required. An Indian number: 10 digits, with 91, or with +91. |
from |
Optional. One of your numbers; defaults to your first IVR number. |
variables |
Optional. Strings, numbers or booleans, used in the flow as {{var.name}}. |
reference_id |
Optional. Your own id, up to 128 characters, echoed in callbacks. |
status_callback_url |
Optional, https:// only. Receives the call result. |
ring_timeout_sec |
Optional, 10 to 60 seconds, default 45. |
The response is 202 Accepted with a call_sid and status queued. GET /v1/ivr/calls/{call_sid} returns the call log: status, duration, price, keys pressed, variables and every HTTP request the flow made.
curl -X POST "$EDESY_API_BASE/v1/ivr/calls" \
-H "X-API-Key: $EDESY_API_KEY" \
-H "Idempotency-Key: order-A123-confirm" \
-H "Content-Type: application/json" \
-d '{"flow_id": "5b1c2f0e-...", "to": "9800000000", "variables": {"order_id": "A123"}, "reference_id": "A123"}'
Send Idempotency-Key with a unique value. A repeat of the same request with the same key within 24 hours returns the original response and does not dial again. The same key with a different body is rejected, so a bug in your retry logic can't call a customer twice.
In Say text and Menu prompts, {{var.order_id}} is replaced with that call's value: "Your order {{var.order_id}} is ready." A variable the call doesn't have is spoken as nothing. Up to 20 variables per call.
Add an HTTP Request step after a Menu branch, so each key calls a different endpoint. URL, headers and body can use {{var.*}}, {{digits}}, {{reference_id}}, {{call_sid}} and more. Values are escaped for where they land, so a variable can't break your JSON or change the host.
| Mode | Behaviour |
|---|---|
| Background (default) | The call continues immediately. Network errors, 5xx, 408 and 429 are retried with backoff (1 to 5 attempts, default 3). |
| Wait for response | The caller waits up to 5 seconds and the flow continues down a Success or Failure branch, for example "Your booking is cancelled" or "Please try again later". Never retried, because the caller is waiting. |
Every request is logged in the portal, and a failed background request can be retried from there.
When the call ends we POST to your status_callback_url, retried up to 5 times:
{
"event": "ivr.call.completed",
"timestamp": "2026-10-09T10:15:00Z",
"data": {
"call_sid": "...",
"reference_id": "crm-lead-42",
"status": "completed",
"duration_sec": 42,
"dtmf_path": ["1"],
"variables": { "order_id": "A123" },
"http_requests": [{ "node_id": "http-1", "status": "delivered", "response_code": 200 }]
}
}
event is ivr.call.completed, ivr.call.no-answer, ivr.call.busy or ivr.call.failed.
Status callbacks and HTTP Request steps carry X-Edesy-Timestamp and X-Edesy-Signature: sha256=<hex>, the HMAC-SHA256 of the timestamp, a dot and the raw body, using the signing secret from your portal. HTTP Request steps also send X-Edesy-Idempotency-Key so you can drop duplicates.
import hmac, hashlib, time
def verify(headers, raw_body: bytes, secret: str) -> bool:
ts = headers["X-Edesy-Timestamp"]
expected = "sha256=" + hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return abs(time.time() - int(ts)) < 300 and hmac.compare_digest(expected, headers.get("X-Edesy-Signature", ""))
Reject requests whose timestamp is more than 5 minutes old.
Checks that run before every API call is dialled
API calls go out only between 09:00 and 21:00 IST. Requests outside the window are refused with a clear error instead of dialling.
Numbers on your do-not-call list are refused before dialling.
Calls are charged from your wallet; a call is refused when the balance is too low.
Up to 10 API calls in progress per workspace by default; ask us to raise it.
Callbacks and HTTP requests are HMAC-signed with a timestamp. The signing secret can be rotated in the portal.
HTTPS only, no redirects followed, no requests to private addresses, and secret-looking headers masked in logs.
Press 1 to confirm, 2 to cancel, with your order API called on each key. For online stores, see COD order confirmation.
Read out the amount and due date, and send a callback request to your CRM on a keypress.
Confirm or reschedule with a keypress, and update your booking system during the call.
Ask for a 1 to 5 rating after a visit or delivery and post the answer to your system.
Call before delivery to confirm the customer is available, or let them check status.
Remind, then route the customer to a callback or payment follow-up.
Resources to help you evaluate and implement
Tell us what the calls are for. We'll set up your workspace, API key and first flow with you.