Side-by-side Twilio and Vobiz code for the most common voice tasks — make an outbound call, answer with XML, control a live call, search a number, and validate webhooks. Copy the Vobiz tab and you’re migrated.
Refactoring a Twilio app to Vobiz is mostly a tab-swap: same call flow, a few renamed methods, and an explicit auth_id. Each task below shows the Twilio code and the equivalent Vobiz code in Python and Node — copy the Vobiz tab.
Set your credentials once: AUTH_ID is your Vobiz Auth ID (MA_…), AUTH_TOKEN is your Auth Token. In the Vobiz SDK, api_keyis the Auth ID, and every account-scoped method takes that auth_id explicitly.
The XML builder maps verb-for-verb: VoiceResponse() → ResponseElement(), and Gather keeps its name. On the builder, response.gather(...) → resp.add_gather(...), nested .say() → .add_speak(), and str(response) → resp.to_string(). Twilio’s input/timeout become Vobiz’s input_type/execution_timeout; num_digits carries over unchanged.
from twilio.twiml.voice_response import VoiceResponse, Gatherresp = VoiceResponse()menu = resp.gather(input='dtmf', num_digits=1, timeout=10, action='https://example.com/menu', method='POST')menu.say('Press 1 for sales, 2 for support.')print(str(resp))
from vobiz import vobizxmlresp = vobizxml.ResponseElement()menu = resp.add_gather(input_type='dtmf', num_digits=1, execution_timeout=10, action='https://example.com/menu', method='POST')menu.add_speak('Press 1 for sales, 2 for support.')print(resp.to_string())
const { twiml } = require('twilio');const resp = new twiml.VoiceResponse();const menu = resp.gather({ input: 'dtmf', numDigits: 1, timeout: 10, action: 'https://example.com/menu', method: 'POST' });menu.say('Press 1 for sales, 2 for support.');console.log(resp.toString());
import { vobizxml } from '@vobiz/sdk';const resp = new vobizxml.ResponseElement();const menu = resp.addGather({ inputType: 'dtmf', numDigits: 1, executionTimeout: 10, action: 'https://example.com/menu', method: 'POST' });menu.addSpeak('Press 1 for sales, 2 for support.');console.log(resp.toString());
Vobiz emits the same document shape your TwiML app already returns — see the full verb table in TwiML → VobizXML, and validate the incoming request on your answer URL as shown in Webhooks.
When Vobiz rings your number it fetches your answer_url over HTTP — return the VobizXML from any web framework, exactly as your Twilio voice URL returned TwiML. Just build the response with vobizxml instead of VoiceResponse, and send it as application/xml.
import express from 'express';import { vobizxml } from '@vobiz/sdk';const app = express();app.post('/answer', (req, res) => { const resp = new vobizxml.ResponseElement(); resp.addSpeak('Hello from Vobiz.'); res.type('application/xml').send(resp.toString());});
Building the XML is the only thing that changes — swap VoiceResponse()/str(resp) for vobizxml.ResponseElement()/resp.to_string() and keep the same route. The full verb map is in TwiML → VobizXML, and validating the signed request on this endpoint is covered in Webhooks.
This is the biggest shape change — and the one that makes Vobiz code clearer. Twilio funnels every mid-call action back through client.calls(sid).update(...) (redirecting to fresh TwiML or setting status) and starts recording with client.calls(sid).recordings.create(). Vobiz gives each action its own resource keyed by (auth_id, call_uuid), so the intent lives on the method name.
Each Vobiz method name states the action, so live-call logic reads top-to-bottom. For the full resource map, see Voice Call API; to convert the TwiML you inline via update(twiml=…), use the TwiML → VobizXML reference.
Twilio reads live and completed calls off one client.calls resource, filtered by status. Vobiz gives in-flight calls their own live_calls resource — pass status="live" to fetch or list what’s currently on the wire. Completed-call history lives in cdr.
one = client.calls('CA0123...').fetch()live = client.calls.list(status='in-progress')
one = client.live_calls.get_live_call(AUTH_ID, 'call-uuid-here', status='live')live = client.live_calls.list_live_calls(AUTH_ID, status='live')
Twilio searches the live carrier catalog with available_phone_numbers('US').local.list(...), reads .phone_number off a result, then provisions it with incoming_phone_numbers.create(phone_number=...). Vobiz browses ready-to-buy stock with list_inventory_numbers(...) and buys by E.164 with purchase_from_inventory(...) — a deterministic two-step flow where the E.164 number is the key.
available = client.available_phone_numbers('US').local.list(limit=1)number = available[0].phone_number # e.g. '+14155551234'incoming = client.incoming_phone_numbers.create(phone_number=number)
available = client.phone_numbers.list_inventory_numbers(AUTH_ID, country='US')number = available.numbers[0].e164 # e.g. '+14155551234'bought = client.phone_numbers.purchase_from_inventory(AUTH_ID, e164=number)
const available = await client.availablePhoneNumbers('US').local.list({ limit: 1 });const number = available[0].phoneNumber; // e.g. '+14155551234'const incoming = await client.incomingPhoneNumbers.create({ phoneNumber: number });
const available = await client.phoneNumbers.listInventoryNumbers(AUTH_ID, { country: 'US' });const number = available.numbers[0].e164; // e.g. '+14155551234'const bought = await client.phoneNumbers.purchaseFromInventory(AUTH_ID, { e164: number });
Twilio tracks a room by its CF… SID and each leg by its CallSid. Vobiz uses the room name as the join key and a member_id per participant — get_conference returns the room details and its member list in one call, and every control is its own verb.
conf = client.conferences("CFxxxxxxxx")# Who is in the room?for p in conf.participants.list(): print(p.call_sid, "muted:", p.muted)# Mute, hold, and remove one participantconf.participants("CAaaaa").update(muted=True)conf.participants("CAaaaa").update(hold=True, hold_url="https://example.com/hold.mp3")conf.participants("CAaaaa").delete()# End the whole conferenceconf.update(status="completed")
# Who is in the room? get_conference returns members + room details togetherroom = client.conferences.get_conference(AUTH_ID, conference_name="SalesRoom")for m in room["members"]: print(m["member_id"], "muted:", m["muted"])# Mute a member; hold = play audio to just that member, stop returns them to the roomclient.conference_members.mute_member(AUTH_ID, conference_name="SalesRoom", member_id="MEMBER_1")client.conference.play_audio_member(AUTH_ID, conference_name="SalesRoom", member_id="MEMBER_1", url="https://example.com/hold.mp3")client.conference.stop_audio_member(AUTH_ID, conference_name="SalesRoom", member_id="MEMBER_1")client.conference.kick_member(AUTH_ID, conference_name="SalesRoom", member_id="MEMBER_1")# End one room, or every active room at onceclient.conferences.delete_conference(AUTH_ID, conference_name="SalesRoom")client.conferences.delete_all_conferences(AUTH_ID)
Record the whole room on demand by name — start and stop whenever you like:
# Twilio sets recording on the <Conference> verb:# <Conference record="record-from-start"# recordingStatusCallback="https://example.com/rec">SalesRoom</Conference>
Completed-call history is a first-class cdr resource on Vobiz, with rich filters for reporting.
records = client.calls.list(status="completed", limit=20)for r in records: print(r.sid, r.to, r.duration, r.price)
records = client.cdr.list_cdrs( AUTH_ID, start_date="2026-06-01", end_date="2026-06-30", call_direction="outbound",)
Filter CDRs by from_number, to_number, call_direction, hangup_cause, bridge_uuid, or sip_call_id — purpose-built for reporting. See Voice Call API for the live-vs-history split (live_calls for in-flight legs, cdr for completed history).
Both platforms sign inbound webhooks so you can prove the request came from them. Twilio’s RequestValidator rebuilds the full URL plus every sorted POST field and HMAC-SHA1s it against X-Twilio-Signature. Vobiz signs a short, deterministic string — baseURL + "." + nonce (query stripped) — with HMAC-SHA256, and sends it as X-Vobiz-Signature-V3 with the random nonce in X-Vobiz-Signature-V3-Nonce. No param-sorting step, reproducible in any language with the standard library.
from flask import Flask, request, abortfrom twilio.request_validator import RequestValidatorapp = Flask(__name__)AUTH_TOKEN = "your_twilio_auth_token"@app.route("/voice", methods=["POST"])def voice(): validator = RequestValidator(AUTH_TOKEN) # HMAC-SHA1 over full URL + sorted POST params valid = validator.validate( request.url, request.form, request.headers.get("X-Twilio-Signature", ""), ) if not valid: abort(403) # ... return TwiML
On sub-account callbacks Vobiz also adds X-Vobiz-Signature-MA-V3, signed with the parent (main-account) token, so a parent can verify child traffic with the same validator. Full mapping and the Node version: webhooks & signatures.
Catch the Vobiz error types (all subclasses of ApiError) the same way you caught Twilio’s TwilioRestException — with the HTTP status and response body available on the exception.
from twilio.base.exceptions import TwilioRestExceptiontry: client.calls.create( to="+14165553434", from_="+14155551234", url="https://example.com/answer.xml", method="POST", )except TwilioRestException as e: print(e.status, e.code, e.msg)
from vobiz.core.api_error import ApiErrortry: client.calls.make_call( auth_id=AUTH_ID, from_="+14155551234", to="+14165553434", answer_url="https://example.com/answer.xml", answer_method="POST", )except ApiError as e: print(e.status_code, e.body)