Skip to content

docs(voice): add inbound calling guide - #697

Open
Devon-White wants to merge 20 commits into
mainfrom
devon/inbound-calling-guide
Open

Devon-White wants to merge 20 commits into
mainfrom
devon/inbound-calling-guide

Conversation

@Devon-White

Copy link
Copy Markdown
Collaborator

Summary

  • New guide at /docs/platform/voice/inbound-calling, a companion to the outbound calling guide with the same shape: prepare, answer a first call, follow the call, examples, next steps.
  • Opens with how a call reaches a Resource through its addresses (phone number, SIP address, alias), based on the live Dashboard: Addresses & Phone Numbers tab, Inbound Call Settings → Assign Resource, Relay Application Topic, SWML Script External URL / Hosted Script.
  • First call in three tabs: SWML served by the Server SDKs (default), WebSocket (Relay), Browser SDK. Number assignment shown in the Dashboard and with REST plus the Python and TypeScript wrappers. Hosted-script REST flow is in an Accordion.
  • Examples: AI agent, phone menu, forward with whisper, voicemail, background recording, audio streaming, answer in the browser.
  • Two new themed SVG lifecycle diagrams with <llms-only> Mermaid equivalents.
  • Adds an "Answer an inbound call" card to the Voice overview and a link in the root llms.txt.

Review notes

  • REST assignment uses PUT /api/relay/rest/phone_numbers/{id} with relay_script and relay_topic. The TypeSpec core model marks the Relay topic status callback URL as required, but the request model and both SDK wrappers make it optional; the guide follows the SDKs.
  • Subscriber assignment via REST uses the beta phone-number-addresses endpoint and is labeled beta.
  • SIP address format sip:<user>@<space>-<context>.dapp.signalwire.com is taken from the Dashboard form's fixed prefix and suffix; confirm it's stable.
  • Relay samples omit host, per the RelayClient reference; the outbound guide passes the Space host.

Verification

yarn fern-md-check: all 2749 MDX files valid.

Companion to the outbound calling guide. Covers how a call reaches a
Resource through its addresses (phone number, SIP address, alias), then
answers a first call with SDK-served SWML, Relay, or the Browser SDK,
assigns the number in the Dashboard or with REST, follows the call, and
adds AI agent, phone menu, whisper, voicemail, recording, streaming, and
browser-answering examples. Adds two themed lifecycle diagrams, a card on
the Voice overview, and a root llms.txt link.
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Turn the Addresses page from a one-snippet stub into the reference for
how a Resource is reached: phone numbers, SIP addresses, and aliases;
contexts and channels; how SignalWire resolves an address; one Resource
with many addresses; and how to manage addresses in the Dashboard, the
REST API, and the Browser SDK. Add a shared snippet for assigning a
Resource to a phone number, use it from Resources, Addresses, and the
inbound calling guide, and trim the guide's addressing section to a
summary that links here.
Frame the guide around where the logic runs and how SignalWire reaches
it: SWML over HTTP (Server SDK), serverless SWML (hosted in the Space),
WebSocket from your server (Relay), and WebSocket from the browser.
Give serverless SWML its own tab in the handler and assignment steps
instead of an aside and accordion, compare all four in one table, and
state that only Subscriber tokens receive calls in the browser while
guest and embed tokens are outbound-only.
Add a step that chooses how callers reach the handler: phone number,
SIP address, or alias, independent of the handler choice. Split the old
number-assignment step into creating the Resource (per handler) and
giving it an address (per address type), with Dashboard and REST paths
for each, and make the test-call step follow the chosen address.
…er SDK

Name the four approaches by SDK and transport: Server SDK over HTTP
(SWML), Server SDK over WebSocket (Relay), Browser SDK over WebSocket,
and serverless SWML. Say outright that SWML is what the Server SDK
serves and that the Browser SDK is Relay in the browser, so the
comparison no longer sets SWML against a WebSocket it also uses.
@Manny-r31

Copy link
Copy Markdown
Collaborator

Errors

  • Default alias context for AI Agents is private, not public. The addresses page says an AI Agent named Support Agent lands at /public/support-agent. In prime-rails the RelayBin adapter, which covers AI Agents, SWML Scripts, and Call Flows, sets the auto-created alias to the private context. Relay Applications are private too. Only Video Rooms, SIP Credentials, SIP Gateways, cXML Scripts, FreeSWITCH Connectors, and the external webhook handlers default to public. Line 84 to 87 of addresses.mdx.
  • The Dashboard cannot re-point an alias to another Resource. The page says to "open the alias and change its assigned Resource" in the Dashboard. Neither alias edit form (the Resource's Addresses tab or the Space-wide Addresses page) has a Resource field, and both update contracts accept only name, display name, display type, context, channels, codecs, and Call Flow version. The Dashboard's own helper text promises the Resource "can be changed later," but the forms don't deliver it. Re-pointing is delete and recreate in both the Dashboard and the REST API. Line 98 to 101.
  • The picker offers "Video Room", not "Conference Room". The Resource picker view lists Script, AI Agent, Video Room, Relay Application, Subscriber, FreeSWITCH Connector, Call Flow, SIP, and Queue behind a feature flag. Script, AI Agent, and SIP open sub-choices (SWML or cXML Script, Custom or Google DialogFlow Agent, SIP Credential or SIP Gateway). resources.mdx line 124.
  • Wrong endpoint linked for listing a Resource's addresses. The Resources page links "list a Resource's addresses" to the client endpoint that requires a Subscriber token and lists what that Subscriber can reach. The admin endpoint is List Resource Addresses at /api/fabric/resources/{id}/addresses, slug /docs/apis/rest/addresses/list-resource-addresses. resources.mdx line 75.

Minor issues

  • The phone number context is stored and returned by the API as lowercase external. The Dashboard title-cases it to "External". The page writes External in code font on lines 51 and 115, which matches neither exactly. Use lowercase in code font or plain text without code font.
  • The call handler list on line 57 reads as complete but omits cXML Scripts and Applications, Video Rooms, SIP Gateways, FreeSWITCH Connectors, and DialogFlow Agents, all of which the phone route allows. Adding "such as" fixes it.
  • The Resources page calls the type "SIP Endpoints" while the Dashboard picker and the addresses page say "SIP Credential". Pick one across both pages.
  • The Browser SDK section names client.directory. Our own Browser SDK address book page documents it as the observable client.directory$. I could not check the SDK source since it is not in the local tree.
  • I could not confirm the "Assign Resource" button label under Inbound Call Settings from the phone route form source. The section headings "Inbound Call Settings", "Inbound Message Settings", and "Edit Settings" do match.

Verified correct

Two independent handlers per number. Message handlers restricted to messaging-capable types. SIP host format <space>-<context>.dapp.signalwire.com with user defaulting to *, domain defaulting to the public context, and password, IP allowlist, encryption, codec, and cipher options. Alias auto-creation from the Resource name via parameterize, so john.doe becomes john-doe in the private context and names allow lowercase letters, digits, underscores, and dashes. Channels audio, video, messaging. Display As app, room, call, subscriber. REST alias update has no resource_id. Context omission works for Subscriber-originated dials. WhatsApp appears in the Resource's Add an Address menu. Sidebar groups, My Resources columns, and Addresses page columns all match the views.

Devon-White and others added 2 commits September 17, 2026 13:21
- Default alias context for AI Agents, scripts, and applications is
  private, not public; note which types get a public alias instead
- Aliases can't be re-pointed in the Dashboard or REST: delete and
  re-create
- Resource picker offers Video Room, not Conference Room, with its
  sub-choices
- Link the admin List Resource Addresses endpoint, not the client one
- Lowercase external context; 'such as' on the call handler list;
  SIP Credentials card; client.directory$ observable
@Manny-r31

Manny-r31 commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Review: Resources and Addresses pages

Gaps in the model

  • Per-type defaults. Nothing says which context/channels a new Resource's alias gets. Customers need this to know a new AI Agent is unreachable from a public widget until they add a public alias. Suggest a small table: type, default context, default channels, Display As.
  • Relationship rules never stated. One address → one Resource; one Resource → many addresses; names unique per context so /public/support and /private/support coexist; a phone number is two addresses (calling, messaging) in external, each independently assignable. A Context → Address → Resource diagram next to the resolution flowchart would carry it.
  • Deletion. Neither page says what happens when you delete a Resource with addresses or delete an address. Dashboard copy confirms: deleting an address leaves the Resource and in-progress calls intact; removing a handler leaves the number in the account unassigned.
  • WhatsApp It behaves like a phone number: two handlers (calling, messaging) assigned to Resources. Move the mention from the Aliases section to the end of Phone numbers, and keep it light since the Add an Address option is feature-flagged and the public REST API exposes it only via the WhatsApp Numbers endpoints.
  • Contexts. Explain that a context has an access type (public/private) that governs reachability, that public/private are the built-in read-only ones, and that SIP domains are contexts. guest and custom contexts can stay light (feature-flagged).
  • Omitting the context (line ~118) only works for calls placed by an authenticated Subscriber. REST dials and SWML connect need the full /context/name. Current wording will trip server-side devs.
  • Address properties are scattered: display name vs URL-safe name, Display As, channels, codecs (aliases too, not just SIP), Call Flow version pinning. A short property list would match the REST response.
  • "Handler" is used ~15 times and never defined. One clause at first use.

Structure

  • Resources page duplicates the addresses page (Resource addresses section, Give it an address, the same snippet) and includes the "What's a Resource?" admonition that links back to itself. Suggest: Resources owns what/types/create/manage; Addresses owns reaching one; Resources keeps a short handoff.

Already strong

Two-handler phone number explanation, SIP address vs Credential vs gateway, resolution flowchart, swap-a-handler example, REST task table.

Devon-White and others added 11 commits September 17, 2026 15:07
- Define handler at first use; state address/Resource relationship rules
- Per-type table for the first alias's context; External URL scripts are public
- Alias property list matching the REST object; display_type is derived
- WhatsApp number as its own address type; deletion behavior
- Contexts: public/private built in; only Subscriber dials may omit the context
- Resources page hands off to Addresses instead of duplicating it; drop the
  self-referencing admonition; snippet says SIP Credentials
- Browser SDK: register() resolves on guest and embed tokens (they never
  ring); answer() with no options mirrors the caller's offer; transfer()
  needs a destination; pin @signalwire/js@4.0.0-rc.2 rxjs@7.8.2 in both
  voice guides (@latest resolves to 3.x)
- Drop unsourced claims: a token with a new reference creating the
  Subscriber, ringback during connect.confirm, record_call posting at
  call end
- Phone menu: Relay sample dials phones only, so prose says E.164; type
  the TypeScript lookup table so it passes tsc --strict
- Create the Resource: Dashboard plus one cURL per tab; the TypeScript
  swmlWebhooks.create is deprecated in favor of setSwmlWebhook, which the
  shortcut Accordion already shows
- Move the approach choice out of Steps into "Pick the right product",
  fold the Resource/address model into it, add per-surface bullets
- Remove duplicated alias, guest-token, and browser status prose; add
  missing placeholders and sample headers; highlight the lines the Relay
  tracking sample adds; drop the Next steps section
- Bare slug; alias form field is Name, matching the Addresses guide
- Outbound: attach media before the capture-failure subsection
…aims

Follow the outbound guide's flow — prepare, choose, set up, test, track,
examples — and keep each handler's code and Dashboard setup together.

Correct the capability claims the old matrix got wrong:

- SWML and Relay do reach a person, via `connect` to a phone number, SIP
  address, or queue. The old table's single "a person answers" row conflated
  how a call is routed with where a person picks it up.
- Serverless SWML can look a caller up with `request` and branch on the
  result with `switch`. What it can't do is vary the document per call.
- Drop wording implying a company number must be assigned directly to a
  Subscriber for a browser user to answer; a routing handler can forward.

Replace the check/cross scorecards with descriptive comparisons of purpose
and control model, so a missing direct SDK method isn't read as the whole
surface being underpowered.

Build SWML through `SWMLService` and `SwmlBuilder` wherever the pinned SDK
versions support the instruction, and order examples Python, TypeScript,
then cURL. The raw `stream` payload stays, with a comment naming the
version gap that requires it.
…risons

Codify two conventions the calling guides now follow.

Examples lead with the supported SDK: Python, then TypeScript, then cURL as
the direct HTTP alternative. SWML instructions go through the builders, with
YAML and JSON after. Any SDK or version gap that forces a raw request gets
explained where it appears.

Compare approaches by purpose and control model rather than by check/cross
scorecard. A missing direct SDK method is not the same as something the
whole flow can't do, since a browser client can reach a Resource that does
it. State real restrictions at the scope the evidence supports.

Link the capability-guides rule from CONTRIBUTING so the template is
discoverable alongside the other authoring rules.
Flatten the Architecture section's pages directly into Platform > Core, and
lead Core with Projects, Resources, Addresses, Subscribers, and Webhooks —
the concepts the calling guides assume a reader already has. All 11 pages
are preserved, each appearing exactly once.
…uides

The combined SWML walkthrough is now covered by the platform inbound and
outbound calling guides, which carry the same setup in more depth.

Delete the page, redirect its URL to the inbound guide, and resolve all
seven references: the SWML recipes and the phone numbers overview point at
the inbound sections that replace them, and the voice overview drops the
duplicate card, since both new guides are already listed there.
@Manny-r31

Copy link
Copy Markdown
Collaborator

Inbound calling vs. Receive WhatsApp calls: keep separate, cross-link

Recommendation: keep the two guides separate. They share a skeleton (prepare → credentials → hosted SWML → attach address → test → troubleshoot → read caller → follow the call), and the WhatsApp page duplicates the hosted-script creation and the Relay client almost verbatim. But about half of the WhatsApp page has no home in the inbound guide:

  • Different address and assign endpoint (WhatsApp number, POST /api/fabric/resources/{id}/whatsapp_numbers)
  • Meta-side prerequisites and troubleshooting (Registered status, voice enabled, voice_enabled / voice_capable, renewal refusal)
  • Constraints: inbound only, audio only, no hold, no Browser SDK answering, one Address per number
  • whatsapp: prefix on to

It's also step 3 of August's four-page WhatsApp calling cluster (overview → onboarding → enable voice → receive calls), and the inbound guide is already ~2,300 lines. Merging would either bury the WhatsApp-specific content or grow a page that needs splitting.

The actual gap: the two pages don't link to each other at all.

Suggested changes

  1. Inbound calling: add "WhatsApp number" as a fourth row in Choose how callers reach you and the test-call table, linking to Receive WhatsApp calls. The Addresses page now lists WhatsApp as an address type, so this should match.
  2. Receive WhatsApp calls: keep the self-contained first call, but replace the duplicated Relay section with a short paragraph + link to the inbound guide's Relay handler, and turn "past that point the call is an ordinary call" into a link to the inbound guide's Examples table.
  3. Both: the credentials table and the signalwire-sdk==3.4.1 pin appear ~30 times across the two pages. A shared snippet would stop them drifting.

Worth borrowing the other way: the WhatsApp page's "Pick the right product" matrix (SWML / Relay / REST / Call Flow Builder by function) is stronger than the inbound guide's approach table.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants