Programmable Voice DSL Reference
When you point one of your Devotel phone numbers (DIDs) at an HTTPS answer URL, Orbit turns that DID into a fully programmable inbound call — the TwiliovoiceUrl / Vonage answer_url model. On
every inbound call Orbit performs a server-side HTTP request to your
URL with the call metadata and expects a JSON response describing what
to do with the call. That JSON is a list of verbs — the
programmable-voice DSL documented here.
The verbs your answer_url returns are parsed, validated, and sanitised by
sanitizeDslVerbs (apps/api/src/routes/jambonz/voice-dsl-executor.ts) —
that function is the source of truth for this contract. The webhook
inbound-route handler (buildWebhookVerbs in
apps/api/src/routes/jambonz/verb-builder.ts) performs the server-side fetch
to your URL and then hands the response body to sanitizeDslVerbs; the other
build*Verbs handlers in that file build the platform-managed verb sequences
for the non-webhook route types (agent, queue, voicemail, …). The verb shape
is pinned as the VoiceDslVerb OpenAPI component
(apps/api/src/lib/openapi-schemas.ts).
Outbound calls always terminate through the Devotel softswitch.
Every
dial verb is routed through Devotel’s wholesale softswitch trunk.
You cannot select an arbitrary carrier, Telnyx Call Control, or DIDWW for
the outbound leg — any carrier-selection field you return is ignored.
Quality, STIR/SHAKEN signing, and PSAP routing are owned by Devotel.The request Orbit POSTs to your answer_url
On each inbound call, Orbit sends an HTTP request to your configured URL. The default method isPOST with a JSON body; if you configured GET,
the same fields arrive as query-string parameters.
Verifying the request came from Orbit
When your inbound route has a signing secret configured, Orbit signs every request to your answer_url with HMAC-SHA256 so you can confirm it genuinely came from Orbit and was not forged by someone who learned your URL — the TwilioX-Twilio-Signature / Vonage answer_url JWT parity.
Two headers carry the signature:
Routes provisioned without a signing secret receive unsigned requests
(neither header is present), so signing is opt-in and adding a secret never
breaks an existing integration.
The signed content is the timestamp, a literal
., and the exact request
payload Orbit put on the wire:
- POST —
<X-Devotel-Timestamp>.<raw_request_body>, where the raw body is the exact JSON byte-for-byte as received. Hash the raw bytes; do not re-serialize the parsed JSON, or key-order and whitespace differences will make the signature mismatch. - GET —
<X-Devotel-Timestamp>.<raw_query_string>, where the query string is exactly what Orbit appended to your URL (everything after?, without the leading?).
- Read
X-Devotel-Timestampand reject the request if it is older than a few minutes (for example, 5 minutes) to bound replay. - Compute
expected = "sha256=" + HMAC_SHA256(secret, "<timestamp>.<payload>")as hex, using the raw body (POST) or raw query string (GET) aspayload. - Compare
expectedagainstX-Devotel-Signaturewith a timing-safe comparison and reject on mismatch.
This answer_url signature is a separate scheme from the customer-webhook
HMAC documented in Webhook Security. Both use
HMAC-SHA256 keyed on a per-resource secret, but the answer_url request
carries the timestamp in its own
X-Devotel-Timestamp header and a
sha256=<hex> value in X-Devotel-Signature, rather than the combined
t=<unix>,v1=<hex> encoding used for webhook deliveries. Verify each with
the scheme documented for it.The response your answer_url returns
Respond withContent-Type: application/json and either an envelope or a
bare array of verbs:
Verbs
Each verb object is discriminated by itsverb field.
say
Text-to-speech playback to the caller.
play
Play a pre-recorded audio file. The URL must be https:// and is
SSRF-validated at configuration time.
gather
Collect DTMF and/or speech input from the caller. This is also the IVR-menu
primitive: play your prompt with a preceding say (or play), then branch on
the collected digit in the verb list you return from actionHook.
dial
Bridge the caller to an outbound leg. Routed exclusively through the
Devotel softswitch trunk (invariant #45) — you cannot select a carrier.
record
Capture the caller’s audio to a single recording file — the file-capture
primitive (e.g. for a voicemail, a spoken message, or a survey answer). It
records the current leg until the caller presses finishOnKey or
maxLength seconds elapse, then POSTs the recording metadata to your
actionHook. You are responsible for the consent this capture requires —
see the consent note below.
Recording the whole call is a route setting, not a DSL toggle. To record
an entire bridged call, set the inbound route’s
recording_mode (all or
inbound) — Orbit then records both/inbound legs through the Devotel SIPREC
server for the life of the call. There is no boolean record start/stop
switch in the DSL: a record field set to true or false is not consumed
by the engine and is ignored. Use the record verb above only to capture a
discrete clip to a file.enqueue
Park the caller in a named queue (e.g. for ACD / contact-center routing).
transfer
Hand the call off to another inbound route or destination.
Like
dial, a transfer to a PSTN number terminates through the Devotel
softswitch trunk — emergency short codes and blocked-prefix destinations are
rejected, and the caller hears your fallback instead.
hangup
End the call. Optionally supply a SIP reason surfaced in carrier CDRs.
listen
Fork the live, bidirectional call audio to your own WebSocket endpoint for
custom transcription, voice-AI, or analytics — the Twilio Media Streams /
Telnyx fork model. Orbit opens a secure WebSocket (wss://) to your url
and streams raw audio frames for the duration of the call. The endpoint must
be a public host: it is SSRF-validated at admission, so a private or
internal address is rejected and the verb is dropped.
listen forks media on the existing call leg — it never originates or
terminates a call. Bidirectional audio is injected back into the same leg,
so it does not create an outbound carrier leg and is unaffected by the
softswitch-termination rule that governs dial.Reserved verbs
Two verbs are managed by Orbit and cannot be returned from youranswer_url:
conference— joining a caller into a named conference room. Rooms are created and named by the platform (conference routes, call park, supervisor monitoring), so accepting a room name from a webhook would let one call bridge into another tenant’s or a supervisor’s room. Configure conference routes in the dashboard instead.config— changing in-call engine settings such as pausing or resuming recording. Recording is governed by your route’s recording and consent settings, not by the verb list.
answer_url returns either verb, Orbit drops it and continues with the
rest of your verb list. Everything you need for a normal call flow is covered by
the verbs above.
Failure handling
Orbit fails soft. If youranswer_url is slow, unreachable, returns a
non-2xx, or returns a malformed body, the configured fallback runs
instead — one of:
- safe-default — a short apology prompt then hangup.
- voicemail — defer to the route’s voicemail configuration.
- decline — reject the call with a SIP
603 Decline.