Skip to main content

Extension identity: one user endpoint spanning voice, SMS, and appearance

An extension in Devotel Orbit is a user endpoint identity — one object that a person answers calls on, texts from, and appears on colleagues’ phones as. It is deliberately not any of the neighbouring concepts: a DID is a rentable number with its own lifecycle (Number lifecycle), an agent presence state is a routing eligibility flag (Agent presence lifecycle), and the UCaaS/CCaaS/CPaaS split is about which product tier you deploy (UCaaS, CCaaS, and CPaaS). The extension is the identity those constructs point at. Three guides each cover one facet of it — Extensions and desk phones, Extension SMS, Shared Line Appearance — and this page is the model that ties those facets together.

What an extension is

An extension is a SIP identity — a label, a derived SIP username, a password, and an optional short extension number — created under Voice → Extensions. It usually resolves to one DID as its caller-ID identity and inbound destination, and it is the identity that many devices register against: the browser softphone, a desktop or mobile app, a zero-touch-provisioned desk phone, or all three at once. One extension owns at most one DID as its caller-ID identity; inbound calls placed to that DID (or to the short extension number) fork onto every device the extension holds a registration on, and whichever device answers owns the call. The identity is stable even as its facets change. Rotating the password, re-provisioning the desk phone, or removing a member from a shared line does not create a second object — the extension survives, and every binding re-points at it. That is why the answer to “what rings when someone dials Mina?” is always the extension, never the device.

The lifecycle: register → DID assignment → in-service → release

The extension’s lifecycle has four states, and each transition is tenant-owned and auditable:
  1. Register — create the SIP credential under Voice → Extensions (or POST /sip-credentials); the plaintext password is returned exactly once. At this point the extension exists but owns no DID, so it can call internally and register devices but cannot present a caller-ID identity.
  2. DID assignment — bind a DID the organization owns to the extension (the default_caller_id_e164 field). The number’s own lifecycle is what makes it assignable in the first place — an un-purchased or released number cannot be bound (Number lifecycle).
  3. In-service — with a DID bound, the extension is fully in service: outbound calls and from_extension SMS sends present that DID, inbound calls to it fork onto the extension’s devices, and inbound SMS lands in the extension’s thread.
  4. Release / reassign — soft-delete the SIP credential (DELETE /sip-credentials/:id), which immediately disables REGISTER and marks the row deleted while keeping the audit trail; or unbind the DID so the number returns to the pool for reassignment to another extension or pool. Because the object is soft-deleted, a re-created extension that needs the same identity is a new credential with a fresh row, not a resurrection.
Every transition writes audit history; the SIP credential lifecycle page covers create / rotate / disable / delete of the identity itself in full (SIP credential lifecycle).

How sender resolution consumes it

On POST /api/v1/messages/sms, naming from_extension makes the extension the FIRST source the outbound chain checks — ahead of sender_pool_id, a bare from, and every pool and messaging-service default (How routing picks a sender; full chain: Sender resolution). Resolution is strict:
  • An unknown or disabled extension id → 404 EXTENSION_NOT_FOUND.
  • An extension whose DID binding (default_caller_id_e164) is unset → 422 EXTENSION_NO_SENDER_IDENTITY.
  • from_extension riding alongside sender_pool_id → 422, because an extension identity and a pool pick cannot both own the sender.
The extension only ever picks WHICH org-owned DID is stamped as the sender. The outbound SMS still terminates through the Devotel softswitch, and the number’s org-ownership is re-verified at send time (anti-spoof), the same way the SIP edge guards outbound caller-ID.

Where extension → DID resolution reads

The extension→DID mapping lives on the public cross-tenant catalog plane, not inside the per-tenant tenant_<id> schema. The resolver reads public.tenant_sip_credentials — one row per SIP credential, keyed on organization_id with extension_number + enabled + deleted_at — to fetch the bound default_caller_id_e164. Cross-tenant number ownership is separately tracked in public.phone_number_owners (number → owning org), which is the reverse map the anti-spoof re-check uses at send time. That split — catalog/routing on public, working data (messages, contacts, call bindings) in the tenant schema — is the tenancy model in Tenant isolation.

The bindings

One extension fans out to several bindings, and each binding is optional — the object is valid with any subset:
  • Desk-phone registrations — a Polycom, Yealink, Cisco, or Grandstream handset pairs to the extension and REGISTERs over its identity. The extension’s Live status card shows the current binding (Registered / Not registered / Disabled) with the last REGISTER’s timestamp and source IP, and can Force unregister it. End-to-end provisioning is in Extensions and desk phones.
  • SIP credentials — the username and password that authenticate each REGISTER. The plaintext is returned exactly once at creation; rotation replaces the password while the username — the identity everything else keys on — stays stable. Full lifecycle: SIP credential lifecycle; how registration proofs are verified: SIP digest verification.
  • The from_extension send-side role — on POST /api/v1/messages/sms, naming from_extension resolves the extension to its DID identity and sends from that DID. It is a sibling of from, not of a sender pool, and is mutually exclusive with sender_pool_id (both on one request → 422). Full precedence chain: Sender resolution.
  • An SMS thread identity — every SMS that names the extension’s DID as sender or recipient rolls into that extension’s per-peer inbox at Voice → Extension SMS, with unread badges and mark-read per peer.

Inbound SMS to an extension

Inbound (MO) messages addressed to the extension’s DID land in the extension’s own thread roll-up rather than in the organization’s shared inbox. The GET /threads roll-up groups inbound messages by peer, counts only the inbound direction as unread, and refuses any from number that is not an active DID the organization owns (403 on all four extension-SMS endpoints). Inbound MO rules — tenant-level matchers that route incoming messages to a webhook, queue, inbox, or SMS menu — still fire alongside the extension thread. How the inbound rule surface differs from sender resolution is covered in How routing picks a sender; the extension thread is an additional destination for messages that also matched a rule, not a replacement for rules.

Shared-line appearance

Shared Line Appearance (SLA) mirrors one extension onto a roster of devices: an inbound call to the line — via a DID routed to it or an internal dial to its primaryExtension — rings every member at once, the first to answer owns the call, and a colleague with grab permission can move it onto their own device. A member’s private or outbound call never lights the shared busy indicator, so the lamp is accurate. This is the deskphone-replacement pattern for reception and executive-assistant deployments — not a queue and not a ring group. Field-by-field semantics (roster caps, canGrab, grab dedup, DID routing to the line) stay in Shared Line Appearance (SLA); the concept-level rule here is that the line’s identity is an extension, and every member of the roster is an extension or a user id bound to one.

Presence interplay

Extension registration state and agent presence are two separate facts that interact at routing time:
  • Registration state — whether a device currently holds a binding on the extension (Registered / Not registered / Disabled). Governs whether a SIP fork would reach a live endpoint at all.
  • Agent presence — the available / busy / wrapup / paused / offline eligibility flag that ACD dispatch and the omnichannel capacity ledger check before any ring (Agent presence lifecycle). Mediated per queue membership, with audit history.
An available agent with no registered device still does not ring — presence qualifies the person for eligibility, registration makes a device answerable. The softphone toggle that flips presence (login, pause codes, logout) never alters the SIP binding, and re-REGISTERing a desk phone never touches the agent’s queue memberships. Keep both healthy, and check presence first: dispatch consults it before it ever asks the SIP edge about registrations.

Worked example: a 3-user UCaaS tenant

A 3-person UCaaS tenant provisions three extensions end to end:
  1. Register three extensions. Under Voice → Extensions, create reception-desk-1 with short extension 201, plus two more for the colleagues (202, 203). Each creation returns the one-time plaintext password once — store it before closing the dialog.
  2. Assign a DID each. Bind one org-owned DID to each extension’s default_caller_id_e164. 201 takes the main published number; 202/203 take direct-dial numbers. Each DID’s own lifecycle state is what makes it bindable (Number lifecycle).
  3. Register the desk phones. Under Voice → Desk phones, bulk-import the three Polycom handsets (CSV or single dialog) and pair each to its extension. Each phone REGISTERs over its extension’s identity; the Live status card flips Registered.
  4. Send from each identity. The colleagues send customer SMS with from_extension: "202" / "203" — each message leaves stamped with that extension’s DID. from_extension wins over any pool or default sender on the same body; sending both it and sender_pool_id is a 422.
  5. Inbound delivery. Inbound SMS to 201’s DID lands in reception-desk-1’s Voice → Extension SMS thread list, with unread badges on inbound only; MO keyword rules still fire in parallel and can also route the message to a queue or webhook. Inbound calls to each DID fork onto that extension’s registered phones.
  6. Layer the shared line. Create a shared line with primaryExtension: "201" and add 202/203 as members with canGrab: true. The main inbound DID routes to the line, so both colleagues see the reception busy lamp and can grab an answered call (Shared Line Appearance).
Three extension objects carried the UCaaS story: SIP identity, desk-phone bindings, DID caller-ID, per-extension SMS threads, and the shared line.

Observability: where extension state is readable

Extension state is readable on both the API and the dashboard:
  • GET /sip-credentials lists every non-deleted SIP credential for the organization; GET /sip-credentials/:id reads one row — label, extension_number, enabled, outbound_enabled, default_caller_id_e164, registration ceiling.
  • The extension’s Live status card under Voice → Extensions shows the current REGISTER binding (Registered / Not registered / Disabled) with the last registration timestamp and source IP, and can force-unregister.
  • DELETE /sip-credentials/:id/registrations evicts active REGISTER bindings for a compromised or NAT-stuck device.
  • Voice → Extension SMS renders the per-peer thread roll-up with unread counts; the UCaaS dashboards surface registration, DID binding, and message-thread health for every extension.
Every create / rotate / patch / delete writes an audit row on the credential.

See also