Edesy's developer APIs share one base URL, https://voice-api.edesy.in/v1, and one API key that works as a Bearer token, an X-API-Key header or Twilio-style Basic auth. They cover calls, Indian numbers, SIP trunks, media streams, Conversation Relay, IVR, voice OTP, masking, missed calls, forwarding, collections, COD order calls, contact center, WebRTC and WhatsApp.
1
Base URL for REST APIs
3
Ways to send your key
HMAC-SHA256
Signed webhooks
INR
Wallet and GST invoices
Twilio-style Basic auth
Signed, retried webhooks
API console with cURL
Self-serve portals
| Base URL | https://voice-api.edesy.in/v1 (all REST APIs below, except where a row says otherwise) |
| API key | Create one in the portal. Keys look like sk_... |
| Header options | Authorization: Bearer sk_..., or X-API-Key: sk_... |
| Twilio-style | Authorization: Basic base64(AccountSid:AuthToken), for example curl -u "$ACCOUNT_SID:$AUTH_TOKEN" |
| Format | JSON request and response bodies |
| Accounts | One login across every Edesy app; workspaces, team roles and API keys per workspace |
| Billing | Prepaid INR wallet (Razorpay), GST invoices, real-time deduction |
curl https://voice-api.edesy.in/v1/phone-numbers \
-H "Authorization: Bearer $EDESY_API_KEY"
The same path for every product
One Edesy login covers every portal. Create a workspace and invite your team with roles.
Finish KYC (PAN and Aadhaar, about 24 hours) before buying numbers, and top up the INR wallet with Razorpay.
Generate an sk_ key in the portal, or use your Account SID and Auth Token for Basic auth.
Place a call or bind a number, then follow the request in the API logs, the per-call event timeline and the webhook delivery log.
| API | Endpoints | Docs | Portal |
|---|---|---|---|
| Voice API | POST /v1/calls, /v1/calls/{sid}/events, /v1/recordings, /v1/transcriptions, /v1/applications, /v1/webhooks/subscriptions; Twilio-shaped /v1/Accounts/{sid}/Calls |
Voice API docs | voice-app.edesy.in |
| Phone numbers | /v1/phone-numbers (available, purchase), PUT /{id}/inbound, PATCH /{id}/routing, stats, webhook logs |
Inbound routing | voice-app.edesy.in |
| SIP trunking | /v1/sip-trunks; each trunk gets tk_<id>.trunk.edesy.in, port 5070, UDP or TCP |
SIP trunking docs | voice-app.edesy.in |
| SIP for AI voice agents | SIP URI targets and trunks for LiveKit, Vapi, Retell, Bolna, xAI Grok and Dograh | Quickstarts | voice-app.edesy.in |
| Media Streams | stream_url on POST /v1/calls, target: media_stream on a number, POST /v1/stream-test |
Media Streams | voice-app.edesy.in |
| Conversation Relay | inbound_target_type: relay on a number, POST /v1/relay-test |
Conversation Relay | voice-app.edesy.in |
| IVR outbound API | POST /v1/ivr/calls, /v1/ivr/flows, /v1/ivr/call-logs, /v1/ivr/http-logs |
IVR docs | ivr.edesy.in |
| Voice OTP | POST /v1/voice-otp/send |
Voice OTP docs | voice-otp.edesy.in |
| Number masking | /v1/masking sessions, routing webhook, extension codes |
Masking docs | masking.edesy.in |
| Missed call | /v1/missed-calls (list, summary, CSV export, numbers) |
Missed call docs | missedcall.edesy.in |
| Call forwarding | /v1/forwarding/rules, call logs, stats, analytics |
Forwarding docs | forwarding.edesy.in |
| Edesy Collect | /v1/collect/ plans, pools, campaigns, contacts, payment webhook |
Collect docs | collect.edesy.in |
| COD Order Calls | Store webhook in: POST /v1/hooks/{token}; store events order.created, order.paid and more |
Order Calls docs | orders.edesy.in |
| Contact center | /v1/ccaas/* plus agent and supervisor WebSockets |
Contact center docs | ccaas.edesy.in, ccaas-agent.edesy.in |
| WebRTC SDK | Token: POST https://webrtc.edesy.in/api/v1/token with x-api-key; drop-in dialer script or @edesy/voice-sdk |
WebRTC docs | webrtc.edesy.in |
| WhatsApp messaging | /v1/whatsapp/messages; webhooks message.received, message.status |
WhatsApp number docs | wa-connect.edesy.in |
There are no server SDK packages: every REST API is plain HTTPS and JSON, and the Voice API console generates cURL for each request. The browser SDK is the exception.
POST /v1/calls takes one of five instructions: text to speak, an answer URL returning VoiceML, inline VoiceML, a WebSocket stream_url, or an application ID.
# Your first call: speak a message to a phone
curl -X POST https://voice-api.edesy.in/v1/calls \
-H "Authorization: Bearer $EDESY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "+9180XXXXXXXX",
"to": "+9198XXXXXXXX",
"url": "https://myapp.example.com/answer",
"status_callback": "https://myapp.example.com/call-status"
}'
# Your answer URL returns VoiceML (JSON), not TwiML
{ "instructions": [ { "verb": "say", "text": "Hello from Edesy." }, { "verb": "hangup" } ] }Every scheme is HMAC-SHA256 with your signing secret. Compute it over the raw request bytes, before any JSON parsing, and compare in constant time.
| Webhook | Headers | Signed message |
|---|---|---|
Voice API status_callback |
X-Webhook-Timestamp, X-Webhook-Signature: v1=<hex> |
<timestamp>.<raw body> |
Webhook subscriptions (call.initiated, call.ringing, call.answered, call.completed, call.failed, call.missed, recording.ready, disposition.submitted) |
X-Edesy-Signature: sha256=<hex> |
raw body |
| IVR status callbacks and HTTP Request steps | X-Edesy-Timestamp, X-Edesy-Signature: sha256=<hex> (HTTP steps add X-Edesy-Idempotency-Key) |
<timestamp>.<raw body> |
| Number masking events and routing webhook | X-Edesy-Signature: sha256=<hex> |
raw body |
WhatsApp message.received, message.status |
X-Edesy-Signature: sha256=<hex> |
raw body |
| COD Order Calls store webhook (your store to Edesy) | You send X-Edesy-Timestamp and X-Edesy-Signature: sha256=<hex> |
<timestamp>.<raw body>, within 5 minutes |
Subscription webhooks are retried with backoff (30 s, 1 m, 2 m, 4 m, 8 m; 6 attempts in all), and every delivery is in a log you can replay from. Reject timestamped requests older than 5 minutes.
Voice API status callbacks and IVR requests sign the timestamp, a dot and the raw body. Only the header names and the prefix differ.
// Timestamped schemes: Voice API status_callback (v1=) and IVR (sha256=)
const crypto = require("crypto");
function verifyTimestamped(rawBody, ts, header, secret, prefix /* "v1=" or "sha256=" */) {
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // 5 minutes
const expected = prefix + crypto.createHmac("sha256", secret)
.update(`${ts}.`).update(rawBody).digest("hex");
return header.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
// Voice API: verifyTimestamped(body, req.get("X-Webhook-Timestamp"), req.get("X-Webhook-Signature"), SECRET, "v1=")
// IVR: verifyTimestamped(body, req.get("X-Edesy-Timestamp"), req.get("X-Edesy-Signature"), SECRET, "sha256=")Subscription webhooks, masking and WhatsApp sign the raw body alone. If verification fails, the usual cause is hashing re-serialised JSON instead of the bytes received.
# Body-only scheme: subscriptions, masking, WhatsApp (X-Edesy-Signature: sha256=<hex>)
import hmac, hashlib
def verify_body(raw: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")
# Flask: verify_body(request.get_data(), request.headers.get("X-Edesy-Signature"), SECRET)See what the platform did, without opening a ticket
Build a request in the portal and copy it as cURL. The Make a call screen shows the API request behind each test call.
Recent calls and webhook deliveries side by side, with request and response.
Every API request your workspace made, with status codes.
GET /v1/calls/{sid}/events: ringing, answered, stream and relay events, completion, with SIP response code and hangup reason.
POST /v1/stream-test and POST /v1/relay-test check your bot's WebSocket from Edesy's network before a real call.
Failed webhooks are retried with backoff. Test a subscription, read each delivery and replay it once you've fixed your endpoint.
Programmable calls with VoiceML, Twilio-shaped endpoints and PascalCase callbacks.
Termination trunks with IP or digest auth, BYOC recipes and FreePBX guides.
Indian numbers for LiveKit, Vapi, Retell, Bolna and others.
Raw L16 call audio to your own bot over WebSocket.
Text-in, text-out bots on the ConversationRelay protocol.
Eleven inbound targets per number, switchable by API.
Indian mobile, landline, toll-free and 92-series numbers.
Visual flows plus an outbound call API with per-call variables.
What carries over from Twilio, and what doesn't.
Explore more comparisons, alternatives, and resources
Bots and SIP
Products
Tell us what you're integrating and your expected volume. We'll point you at the right API and help with the first request.
Create an API key, place a test call from the console and watch the signed callback arrive.