Migrate from Twilio

Move Twilio Programmable Voice code to the Edesy Voice API - endpoint mapping, Basic auth with Account SID and Auth Token, PascalCase status callbacks, and the one real difference - VoiceML JSON instead of TwiML.

5 min read

Most Twilio Programmable Voice code ports with small changes. Edesy accepts Twilio-style Basic auth (Account SID and Auth Token), serves Twilio-shaped paths such as /v1/Accounts/{AccountSid}/Calls, and sends status callbacks with the same PascalCase fields. The one real difference is the instruction format: answer URLs return VoiceML (JSON), not TwiML (XML), and Edesy does not parse TwiML.

Endpoint mapping#

Twilio Edesy Notes
POST/2010-04-01/Accounts/{Sid}/Calls.json POST/v1/Accounts/{Sid}/Calls or POST/v1/calls JSON body, not form-encoded
GET.../Calls.json GET/v1/calls Filters: status, direction, from, to, start_date, end_date
GET.../Calls/{CallSid}.json GET/v1/calls/{CallSid} Response has sid and call_sid
POST.../Calls/{CallSid}.json with Status=completed POST/v1/calls/{CallSid} with {"status":"completed"}, or DELETE/v1/calls/{CallSid} See Update or End a Call
GET.../Calls/{CallSid}/Recordings.json GET/v1/calls/{CallSid}/recordings or /v1/Accounts/{Sid}/Calls/{CallSid}/Recordings
GET.../Recordings.json GET/v1/recordings or /v1/Accounts/{Sid}/Recordings Media via signed, expiring links
POST.../Calls/{CallSid}/Recordings.json POST/v1/calls/{CallSid}/recordings/start
Call Notifications / Events GET/v1/calls/{CallSid}/events Event timeline
IncomingPhoneNumbers (list, update VoiceUrl) GET/v1/phone-numbers, PATCH/v1/phone-numbers/{id}, PUT/v1/phone-numbers/{id}/inbound See Inbound Routing
AvailablePhoneNumbers + buy GET/v1/dids/available, POST/v1/dids/{id}/purchase Indian numbers, KYC first
Applications (ApplicationSid) /v1/applications; application_id (or application_sid) on the call
<Connect><Stream> Media Streams stream_url on the call, or the media_stream inbound target Different wire format, see below
<Connect><ConversationRelay> conversation_relay inbound target Same message protocol

Authentication#

# Twilio
curl -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls.json

# Edesy
curl -u "$ACCOUNT_SID:$AUTH_TOKEN" https://voice-api.edesy.in/v1/Accounts/$ACCOUNT_SID/Calls

Your Account SID is your workspace ID; the Auth Token is on the credentials card of the portal dashboard. For new code, a Bearer API key (Authorization: Bearer sk_...) is simpler. See Authentication.

Request parameters#

Edesy uses JSON with snake_case names.

Twilio parameter Edesy field
To, From to, from
Url url (must return VoiceML)
Method method
Twiml voiceml (a VoiceML JSON string), or text for a simple message
ApplicationSid application_id (application_sid also accepted)
StatusCallback status_callback
StatusCallbackMethod status_method
Timeout timeout (5 to 600)
(display name) caller_name, up to 50 characters
MachineDetection machine_detection (accepted; detection with a stored verdict runs on campaigns today)
custom metadata custom_parameters (echoed back on webhooks)

Fields Edesy does not recognise are not errors: they are listed in meta.ignored_fields, which makes leftover Twilio parameters easy to spot.

Status callbacks#

The payload keeps Twilio's PascalCase names: CallSid, AccountSid, From, To, CallStatus, Direction, Duration. CallStatus uses the same values (initiated, ringing, in-progress, completed, busy, no-answer, failed, canceled).

Differences to plan for:

  • With POST, the body is JSON, not form-encoded. Parse JSON.
  • Edesy adds SipResponseCode, HangupReason and CallOutcome on the final event, so you can tell "phone switched off" from "invalid number". See Call Events and Outcomes.
  • Your custom_parameters come back under Custom.
  • The signature header is X-Webhook-Signature (HMAC-SHA256 over timestamp and body), not X-Twilio-Signature. See Status Callbacks.
  • Call SIDs look like CA_ plus a UUID.

VoiceML instead of TwiML#

This is the main code change. Edesy does not parse TwiML; your answer URL must return VoiceML JSON with Content-Type: application/json.

TwiML:

<Response>
  <Say language="en-IN">Thanks for calling Acme.</Say>
  <Play>https://cdn.example.com/hold.mp3</Play>
  <Hangup/>
</Response>

VoiceML:

{
  "instructions": [
    { "verb": "say", "text": "Thanks for calling Acme.", "language": "en-IN" },
    { "verb": "play", "url": "https://cdn.example.com/hold.mp3" },
    { "verb": "hangup" }
  ]
}
TwiML verb VoiceML verb Notes
<Say> say text, language, voice
<Play> play url, loop
<Gather> gather Keypad digits. For speech, use Conversation Relay or Media Streams
<Record> record max_length, play_beep, finish_on_key
<Dial><Number> dial number, caller_id, timeout
<Connect><Stream> / <Stream> stream, or better stream_url
<Redirect> redirect
<Enqueue> enqueue In the schema but not executed today; use the Edesy contact center for queues
<Reject> reject SIP status (default 603) and reason
<Hangup> hangup
<Pause> pause length in seconds

Multi-step flows that chain <Gather action> callbacks do not translate one-to-one: Edesy runs the document your answer URL returns, and action callbacks are not covered by these docs yet. Rebuild multi-step keypad menus in IVR Studio, or move conversational flows to Conversation Relay. Full reference: VoiceML Reference.

If you have many TwiML handlers, one option is a small adapter in your own code that maps your TwiML builder's output to the VoiceML JSON above.

Media Streams#

Twilio Media Streams sends base64 mu-law audio inside JSON messages. Edesy Media Streams sends raw L16 PCM (16-bit, mono, 8 or 16 kHz) as binary WebSocket frames after one JSON metadata frame, and takes killAudio / disconnect control messages. A stock Twilio serializer will not work; swap it for an Edesy one. See Media Streams, which includes a Pipecat serializer.

ConversationRelay#

The Conversation Relay protocol is compatible with Twilio ConversationRelay: the same setup, prompt, interrupt, dtmf messages in, and text, play, sendDigits, language, end out. A bot written for Twilio usually works after pointing an Edesy number at its URL. See Conversation Relay.

Numbers and billing#

  • Numbers: buy Indian mobile, landline, toll-free and 92-series virtual numbers self-serve after KYC. You can also bring a Twilio Elastic SIP trunk to Edesy as a carrier; see SIP trunking.
  • Billing: prepaid INR wallet topped up with Razorpay, GST invoices with your GSTIN, Rs 0.50 per minute with a 60-second pulse, unanswered calls free, no minimum commitment. Prices exclude 18% GST.
  • SDKs: Edesy does not publish server SDK packages. Call the REST API with your HTTP client; the endpoints are small.

Migration checklist#

  1. Complete KYC and buy (or bring) your numbers.
  2. Create an API key, or copy your Account SID and Auth Token.
  3. Change the base URL to https://voice-api.edesy.in/v1 and send JSON bodies.
  4. Convert answer-URL handlers from TwiML to VoiceML.
  5. Update status-callback handlers to parse JSON and read HangupReason / CallOutcome.
  6. Swap Media Streams serializers, if you use them.
  7. Route inbound numbers with PUT/v1/phone-numbers/{id}/inbound.
  8. Place test calls and read GET/v1/calls/{CallSid}/events for each path.

Questions about a larger migration: /contact or book a demo.

Was this page helpful?