Skip to main content

Worked SIP hook chains

Jambonz calls these hook URLs over HTTP; every body below is the actual POST payload the Devotel softswitch sends to Orbit, not a payload you send to Orbit. The samples exist so a SIP-trunking operator can see which hook fires at which stage and what shape to expect on the wire when chaining to an external softswitch. Use raw cURL (or any HTTP client) to script these — the Node/Python/Go SDKs do not ship a typed helper for the Jambonz hook surface.

Chain 1 — SIP trunk auth: Register auth → Customer trunk auth → Lookup

The softswitch challenges every new REGISTER, then every inbound INVITE, then routes the call.

1a. REGISTER auth (softphone or BYO-PBX binding)

POST /api/v1/jambonz/auth/register
Fired once per REGISTER the softswitch challenges. Orbit answers with the A1 digest hash and algorithm so the SBC can verify the UA locally — never a 5xx, always a clean ok/fail.
cURL
Response: 200 { "status": "ok", "a1_hash": "1ad1c4…", "algorithm": "MD5", "expires": 60 } — the expires clause only appears when the PBX asked for an interval below the healthy floor; otherwise it is omitted and the PBX’s own value stands. Wrong digest → 401 { "status": "fail" }.

1b. Customer trunk auth (INVITE/REGISTER digest on the shared carrier)

POST /api/v1/jambonz/customer-trunk-auth
The account-scoped auth hook for the customer-trunks carrier — it sees BOTH the INVITE (incoming call) and REGISTER (binding) digest claims of every BYO-PBX trunk.
cURL
Response: returns { "status": "ok", "password": "<trunk-digest-secret>" } so the SBC can verify the PBX’s digest locally; when the PBX asked for an interval below the healthy floor the same expires floor clause rides along. Unknown digest username → 401 { "status": "fail" } and the source IP is probe-blacklisted at the edge. Trunk with no digest password configured → 403 { "status": "fail", "message": "no_password_configured" }. Non-INVITE/non-REGISTER methods (OPTIONS keepalives) short-circuit to a bare { "status": "ok" }.

1c. Lookup — resolve the dialed party to a route

POST /api/v1/jambonz/lookup
Fired on every inbound INVITE once auth clears. to is the dialed party (E.164, SIP URI, or carrier-prefixed digit run); originating_sip_trunk_name discriminates inbound-PSTN vs browser-softphone outbound; sip.headers carries any custom SIP headers the originating UA sent (e.g. X-Tenant-ID, X-Devotel-Call-Id set at softphone-initiation).
cURL
Response: a Jambonz verb array for the resolved route — e.g. a dial verb fanned out across the matched inbound-route’s SIP usernames, or the business-hours fallback. Lookup-next (POST /api/v1/jambonz/lookup-next) is the sequential-ring continuation: it advances to the next username in the route’s fan-out after a no-answer timeout, carrying the same body shape plus the attempt state.

Chain 2 — inbound routing: Dial-confirm → Callback-answer → Feature-code → Queue-wait

Once the route resolves, the softswitch drives the call through these verb hooks.

2a. Dial-confirm — screen the outbound arm before connecting

POST /api/v1/jambonz/dial-confirm
Fires when a dial arm is about to answer so Orbit can last-moment veto or confirm destination eligibility (TCPA/time-window guard) before the caller hears the arm ring.
cURL
Response: a verb array — typically a one-word confirm or the polite-decline verbs. On any transient failure Orbit fails CLOSED with a 503 so the leg is never connected without confirmation.

2b. Callback-answer — outbound callback leg answered

POST /api/v1/jambonz/callback-answer
Fires when a queued callback (requested by the caller during queue-wait) reaches the customer’s phone and goes live — Orbit emits the verbs that bridge the customer to the reserved agent.
cURL
Response: the bridge verb array; the sibling Pause-elapsed hook (chain 4) is what fires instead when the caller held in-queue and the pause timer expired without an agent.

2c. Feature-code — star-code interception mid-call

POST /api/v1/jambonz/feature-code
Fires when the caller dials a configured star code (e.g. *97 mailbox, *72 DND toggle) — the softswitch sends the code plus the in-call username binding it to a SIP credential.
cURL
Response: the verb array implementing the code (park the call, open the mailbox TUI, toggle DND), or a “feature not enabled” announcement when the credential has no such code.

2d. Queue-wait — caller holding in queue

POST /api/v1/jambonz/queue-wait
Fires per queue-duration tick while the caller holds; Orbit replies with the announcement/mus verbs plus position-in-queue, and may offer the callback escape. The siblings Queue-action and Queue-overflow fire on the caller’s menu choice and on wait-timeout overflow respectively; Conference-wait-music feeds the hold stream.
cURL
Response: the next wait-cycle verb array (say position in queue, play hold music, gather for the callback opt-in).

Chain 3 — AI voice agent: Bridge-to-lk / Supervisor-bridge → Quality-ingest

The AI-agent pillars bridge the live PSTN call into a LiveKit room; supervisors join the same room in listen/whisper/barge/conference modes; the media layer pushes call quality telemetry into the ingest.

3a. Bridge-to-lk — drop the PSTN call into the LiveKit room

POST /api/v1/jambonz/bridge-to-lk
cURL
Response: the dial verb toward the LiveKit SIP URI (X-LK-Room header) — the AI agent joins the room as a conference participant and converses with the caller in real time.

3b. Supervisor-bridge — join a supervisor to the live call

POST /api/v1/jambonz/supervisor-bridge?callId=…&mode=listen|whisper|barge|conference
Verb hook landing the supervisor’s leg into the conf_<callId> room. The mode drives the audio path: listen (muted supervisor), whisper (audible to the agent only), barge and conference (audible to all — conference is the consult-and-conference 3-way mode).
cURL
Response: a single conference verb — { "verb": "conference", "name": "conf_call_01HWKJF…", "muted": false, "coachCallSid": "call_01HWKAG…" } for whisper. Invalid query → hangup.

3c. Quality-ingest — RTCP-XR telemetry for the call legs

POST /api/v1/jambonz/quality-ingest
Separate from the call-control hooks: the orbit-media layer pushes per-leg quality samples (HMAC-signed) into this ingest; the voice-quality surfaces render from it.
cURL
Response: 200 with the stored sample ack; a missing platform HMAC key fail-closes every POST with a 503 (the quality surface then reports collection as not enabled, not as empty).

Chain 4 — Mailbox fallback: Voicemail pin/store, Pause-elapsed fallback

When the route ends in a mailbox rather than an agent, the TUI challenges for the PIN and stores the message; the pause/callback fallback covers callers who held past the timer.

4a. Voicemail-pin — mailbox TUI PIN challenge

POST /api/v1/jambonz/voicemail-pin
cURL
Response: the TUI verb array — the next prompt (say “enter your PIN again” / “you have N messages”) or the polite-decline verbs after the attempt ceiling. The PIN itself is a tenant-owned mailbox control stored as a salted hash; it never leaves the tenant boundary.

4b. Voicemail-store — persist the recorded message

POST /api/v1/jambonz/voicemail-store
Fires when the mailbox recording completes; Orbit persists the message rows so the mailbox surfaces (envelope/unread counters, playback) see the recording immediately.
cURL
Response: 200 ack; the message appears in the mailbox for the matched username/DID.

4c. Pause-elapsed — held caller’s pause timer fired

POST /api/v1/jambonz/pause-elapsed
Fires when a caller who chose “hold for an agent” exhausts the pause window without an agent reserving them — the fallback that decides between callback-offer, mailbox, or a polite hangup (paired with Callback-answer in chain 2 and Mid-queue-voicemail for an in-queue mailbox offer).
cURL
Response: the fallback verb array (callback offer → mailbox → polite good-bye).
Webhook authentication. Every hook above is HMAC-signed: the softswitch sends jambonz-signature: t=<unix>,v1=<hex> (quality-ingest uses X-Orbit-Signature with the same scheme) over the raw body. Rejects return a clean 4xx envelope ({ "status": "fail", "message": "…" }), never a 5xx — that keeps the SIP transaction clean at the edge. See the Jambonz softswitch concept page for the account/registration model. Idempotency. Every hook accepts the standard Idempotency-Key header and replays identically on retry — use the response’s envelope exactly as computed (a 409 means the same key arrived twice mid-flight).