Live: outbound calls, variables, signed callbacks

IVR Outbound Call API

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.

How it fits together

Your system starts the call; the flow does the talking; results come back to you

Build and publish a flow
Say or Play the message, add a keypad Menu, and connect keys to HTTP Request steps that call your API. Use {{var.name}} where per-call details go.
Create an API key
Create a key in the IVR portal and send it as X-API-Key on every request.
POST a call
Send the flow, the customer's number and their variables. You get a call id back straight away.
Receive the result
Keypresses reach your API during the call, and a signed status callback arrives when the call ends.

Place a call

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"}'

Safe retries with Idempotency-Key

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.

Speak per-call details

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.

Call your API on a keypress

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.

Status callback

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.

Verify that a request came from Edesy

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.

Guard rails built in

Checks that run before every API call is dialled

Calling hours

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.

Do-not-call list

Numbers on your do-not-call list are refused before dialling.

Prepaid wallet

Calls are charged from your wallet; a call is refused when the balance is too low.

Concurrency limit

Up to 10 API calls in progress per workspace by default; ask us to raise it.

Signed both ways

Callbacks and HTTP requests are HMAC-signed with a timestamp. The signing secret can be rotated in the portal.

Safe outbound requests

HTTPS only, no redirects followed, no requests to private addresses, and secret-looking headers masked in logs.

What people build with it

IVR Outbound Call API: FAQ

Start calls from your own system.

Tell us what the calls are for. We'll set up your workspace, API key and first flow with you.