> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Link a SIP extension to a user: per-user inbound VoIP ring

> Bind a SIP credential to a specific user so inbound calls fire a native PushKit (iOS) or high-priority FCM (Android) call push directly to that user's phone, in addition to the organization-wide ring alert.

# Link a SIP extension to a user

Set the **Linked user** field on a SIP credential and inbound calls to that
extension fire a native incoming-call push directly to that user's phone —
the PushKit path on iOS or a high-priority FCM payload on Android. The push
wakes the OS-level incoming-call UI even when the app is locked or
force-quit, something the organization-wide ring alert cannot guarantee.

It is additive, not a replacement: the existing tenant-wide alert still
fires on every inbound ring, and the per-user push lands on top of it.
You use the per-user link when a specific phone must ring natively.

## 1. Link the user to the credential

Edit the extension. In the dashboard open **Voice → Extensions**, click the
credential, and set **Linked user** on it. Over the API send `userId` on the
credential's create or update request:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.orbit.devotel.io/api/v1/sip-credentials \
    -H "X-API-Key: $ORBIT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "label": "j-smith-desk",
      "extensionNumber": "107",
      "userId": "user_abc1234"
    }'
  ```

  ```typescript Node.js SDK theme={null}
  await orbit.request('POST', '/sip-credentials', {
    label: 'j-smith-desk',
    extensionNumber: '107',
    userId: 'user_abc1234',
  });
  ```
</CodeGroup>

The link targets any user in your organization, and the check runs server-side:
a `userId` that is not a live member of this organization is rejected with
`403 FORBIDDEN`, so a link never rings a stranger's phone. Re-setting `userId`
on a `PATCH` requires the same membership check; clearing it (`userId: null`)
is always allowed and returns the credential to the alert-only behaviour.

To unlink, send `userId: null`:

```bash theme={null}
curl -X PATCH https://api.orbit.devotel.io/api/v1/sip-credentials/sipcred_abc123 \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "userId": null }'
```

## 2. Register the user's VoIP token

A linked user only rings if that user has a row in the **VoIP devices**
registry — this is a separate table from the alert-push device tokens and it
carries the PushKit / high-priority-FCM call token. Have the target user
register from their device:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.orbit.devotel.io/api/v1/push/voip-devices \
    -H "X-API-Key: $ORBIT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "platform": "ios",
      "voip_token": "<PushKit token>",
      "bundle_id": "com.yourapp.phone",
      "user_id": "user_abc1234"
    }'
  ```

  ```typescript Node.js SDK theme={null}
  await orbit.request('POST', '/push/voip-devices', {
    platform: 'ios',
    voip_token: '<PushKit token>',
    bundle_id: 'com.yourapp.phone',
    user_id: 'user_abc1234',
  });
  ```
</CodeGroup>

Call it from the SDK's token-refresh callback — it is an idempotent upsert on
the token string, so re-registration refreshes without duplicating. On
Android pass the FCM registration token; the call push then ships at FCM
`priority: high` with a call-only payload. Detail:
[Push device tokens and scheduled sends](/guides/push-device-tokens-and-scheduled-sends).

## 3. Ring a linked extension

Route an inbound number to the extension's SIP username — a ring group,
softphone-register route, or an IVR dial verb. On each inbound ring Orbit
resolves the surviving (non-DND) SIP usernames on the route, looks up their
linked users, and fires one per-user Call push per distinct user, in
addition to the tenant-wide alert. Disabled or deleted credentials are
excluded from the lookup, so a disabled device never rings the user's phone.

The resolution log on each ring confirms both surfaces fired:

```text theme={null}
Inbound push: tenant-wide fan-out complete
  orgId=org_x12 callSid=call_abc routeType=softphone_register
  sent=3 failed=0 dndSkipped=0 voipPushedUsers=1
```

`voipPushedUsers` reports how many distinct linked users the ring pushed —
`0` means the tenant-wide alert still fired but no linked user was in the
destination set (or none had a VoIP token).

## 4. Behaviour contract

| Condition | Behaviour |
| - | - |
| Credential with no linked user | Tenant-wide ring alert only — identical to the pre-link behaviour. |
| Linked user with no `voip_devices` row | Tenant-wide alert only — fail-open, nothing blocks the ring. |
| Linked user who is in Do-Not-Disturb | Skipped from the per-user push set; other linked users on the same ring still fire. |
| Two matched credentials link the same user | One per-user push total — the lookup is de-duplicated by user id. |
| Disabled or deleted credential | Excluded from the resolution — its user is never pushed. |
| `userId` not a live member of the organization | `403 FORBIDDEN` at write time — the link is never persisted. |

## 5. Guardrails

* **Membership-checked on write.** The linked user must be a live member
  of your organization; the check runs on both create and update, and a
  rejected `userId` surfaces as `403 FORBIDDEN`. Clearing (`null`) is
  always permitted.
* **The ring never blocks.** Per-user push resolution and send are
  fail-open end to end — a lookup or send error degrades to "tenant-wide
  alert only" for that ring instead of surfacing into the ring path.
  The per-user push send is documented as never-throw.
* **The tenant-wide alert always fires.** The linked user is additive —
  alert recipients who are not the linked user continue to receive the
  organization's ring alert exactly as before.

## 6. Troubleshooting

| Symptom | Likely cause and fix |
| - | - |
| Push never arrives | Confirm the linked user has a row under `GET /api/v1/push/voip-devices` with a live (non-revoked) token. No row — no per-user push. |
| Alert fires but the native call UI never wakes | The device carries only an alert push token, or the token was registered against the wrong registry. iOS must register a PushKit token via `/push/voip-devices`; Android must register the FCM token there. |
| Push goes to a user who should not receive it | Unset the link: `PATCH /api/v1/sip-credentials/sipcred_abc123` with `{ "userId": null }`. |
| `403 FORBIDDEN` on save | The `userId` sent is not a live member of this organization — pick a different user or clear the field. |

## Related docs

* [Extensions and desk phones](/guides/extensions-and-desk-phones) — create and
  provision the SIP credential itself.
* [Voice extensions: reference](/voice/extensions) — full field set and
  lifecycle.
* [Push device tokens and scheduled sends](/guides/push-device-tokens-and-scheduled-sends) —
  the VoIP device registry.
* [Mobile app](/guides/mobile-app) — inbound-call push on the device side.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.