> ## 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.

# Migrate from Telnyx to Orbit: SMS, MMS, voice, fax, and webhooks

> Migrate your Telnyx integration to Orbit step by step, mapping API keys, messaging profiles, phone numbers, 10DLC campaigns, SIP, and webhooks to their Orbit equivalents.

# Migration from Telnyx to Orbit

This guide walks you through migrating your SMS/MMS, voice, SIP, and fax
integration from Telnyx to Orbit with a concept-by-concept code map.

<Note>
  Only porting your account *configuration* — numbers, messaging profiles,
  10DLC registrations, contacts? The assisted import wizard does that without
  any code changes: see [Assisted import wizard](/guides/assisted-import-wizard).
  This guide is the manual, code-level path for teams rewriting the integration
  itself, and you can run both in parallel.
</Note>

## Concept Mapping

| Telnyx Concept | Orbit Equivalent | Notes |
| - | - | - |
| API key (`KEY…`) | API key (`dv_live_sk_xxxx`) | Single key, simpler auth |
| Messaging profile | Messaging Service | Bundles senders + routing; a Telnyx messaging profile maps one-to-one to an Orbit messaging service. Pass the service id as `messaging_service_id` when you send. |
| Phone numbers | Numbers API | Same capabilities, lower cost |
| SMS + MMS send/receive | Messages API (`channel: "sms"` / `channel: "mms"`) | One endpoint, explicit channel |
| 10DLC campaign registration | 10DLC wizard | Guided brand + campaign registration; the import wizard carries your Telnyx campaign registrations across as reference config. |
| Programmable voice calls | Voice API | Calls, conference, AI agents |
| SIP trunk | SIP trunk (VoIP & SIP) | Connect your own SIP network; inbound and outbound on the Devotel softswitch. |
| Fax | Fax workflow (Telnyx as inbound T.38) | Send/receive fax through your existing Telnyx number or a new Orbit fax number. |
| Webhooks | Webhooks | Same pattern, different headers |
| Delivery receipts | Webhook events | `message.sent`, `message.delivered`, `message.failed` |

***

## Prefer not to do it by hand?

Two options port your Telnyx account's *configuration* (numbers, messaging
profiles, content templates, and contacts with recent history) onto Orbit for
you — neither touches your live traffic, so your existing Telnyx integration
keeps running until you decide to cut over:

* **Dashboard wizard** — **Settings → Import → Telnyx** walks you through
  pasting a Telnyx V2 API key (starts with `KEY`), a dry-run preview of what
  carries over, and a one-click commit with rollback.
* **`devotel migrate telnyx` CLI** — the same wizard, scriptable from your
  terminal or a CI pipeline:

  ```bash theme={null}
  # Preview first — always a dry run, nothing is written yet
  devotel migrate telnyx --api-key KEYxxxxxxxx

  # Commit
  devotel migrate telnyx --api-key KEYxxxxxxxx --run

  # Check progress
  devotel migrate status imp_xxxxxxxx
  ```

  Install with `npm install -g @devotel-orbit/cli`, then `devotel auth login`
  first. See the [CLI README](https://github.com/devotel/orbit/tree/main/packages/cli#migrating-from-telnyx)
  for the full flag reference.

The rest of this guide covers the manual, code-level migration for teams who
want full control over each step.

***

## Step 1: Create Your Orbit Account

1. Sign up at [orbit.devotel.io/signup](https://orbit.devotel.io/signup)
2. Generate an API key at **Settings > API Keys**
3. Note your key prefix: `dv_live_sk_xxxx`

***

## Step 2: Port Your Numbers

Porting moves your Telnyx numbers onto Orbit; the process takes 7–14 business
days. Telnyx stays live until you cut over.

A port request must carry a Letter of Authorization (LoA). Orbit only accepts
an LoA URL it issued itself, so you upload the signed PDF to Orbit first and
submit the returned URL — a link to your own bucket is rejected with
`LOA_URL_INVALID_ORIGIN`.

```bash theme={null}
# 1. Upload the signed LoA PDF. The response `data.url` is a short-lived signed
#    URL (1-hour default). Pass `ttl_ms` to keep it valid long enough for the
#    carrier to fetch it during submission — here, 24 hours.
curl -X POST "https://api.orbit.devotel.io/api/v1/files/upload?ttl_ms=86400000" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -F "file=@./loa.pdf;type=application/pdf"
# => { "data": { "id": "file_...", "url": "https://storage.googleapis.com/...", ... } }
```

```bash theme={null}
# 2. Submit the port request, passing the uploaded `data.url` as loaFileUrl.
curl -X POST https://api.orbit.devotel.io/api/v1/numbers/porting \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "numbers": ["+14155551234", "+14155551235"],
    "currentCarrier": "Telnyx",
    "country": "US",
    "authorizedSigner": "Your Name",
    "accountNumber": "123456789",
    "accountPin": "1234",
    "loaFileUrl": "https://storage.googleapis.com/.../loa.pdf?X-Goog-Signature=..."
  }'
```

**Alternative:** Buy new numbers from Orbit and update your systems gradually.

***

## Step 3: Port US 10DLC Registration

US carrier messaging requires a 10DLC brand and campaign before high-volume
SMS goes through. The import wizard carries your Telnyx campaign registrations
across as mapped reference config; finish the registration itself in Orbit's
guided [10DLC wizard](/guides/10dlc-wizard), which walks brand details, EIN,
sample messages, and the compliance attestation and submits to the carriers.

***

## Step 4: Replace Send + Receive

### Telnyx (Before)

```javascript theme={null}
const telnyx = require('telnyx')(process.env.TELNYX_API_KEY)

await telnyx.messages.send({
  from: '+18005551234',
  to: '+14155552671',
  text: 'Hello from Telnyx!',
  webhook_url: 'https://yourapp.com/hooks/telnyx',
})
```

### Orbit (After)

```javascript theme={null}
import { Devotel } from '@devotel-orbit/node'

const orbit = new Devotel({ apiKey: 'dv_live_sk_xxxx' })

await orbit.messages.send({
  channel: 'sms',
  to: '+14155552671',
  body: 'Hello from Orbit!',
  webhook_url: 'https://yourapp.com/hooks/orbit',
})
```

### SMS Payload Swap (same message, both APIs)

**Telnyx:**

```json theme={null}
{
  "from": "+18005551234",
  "to": "+14155552671",
  "text": "Your order shipped.",
  "messaging_profile_id": "40017"
}
```

**Orbit:**

```json theme={null}
{
  "to": "+14155552671",
  "body": "Your order shipped.",
  "channel": "sms",
  "messaging_service_id": "msgsvc_01H8RNEWG5"
}
```

**MMS payload swap:**

**Telnyx:**

```json theme={null}
{
  "from": "+18005551234",
  "to": "+14155552671",
  "text": "Your boarding pass",
  "media_urls": ["https://cdn.example.com/pass.png"],
  "messaging_profile_id": "40017"
}
```

**Orbit:**

```json theme={null}
{
  "to": "+14155552671",
  "body": "Your boarding pass",
  "channel": "mms",
  "media_urls": ["https://cdn.example.com/pass.png"],
  "messaging_service_id": "msgsvc_01H8RNEWG5"
}
```

### Key Differences

| Feature | Telnyx | Orbit |
| - | - | - |
| Auth | Bearer `Authorization: <KEY…>` | Single API key in `X-API-Key` |
| Send endpoint | `POST /v2/messages` | `orbit.messages.send()` / `POST /api/v1/messages/send` |
| Sender grouping | `messaging_profile_id` | `messaging_service_id` |
| Status webhook | `webhook_url` per message | `webhook_url` or global webhooks |
| Receipts | `webhook_url` / account-level webhook | `message.sent` / `message.delivered` events |

***

## Step 5: Map Webhooks

Telnyx POSTs delivery events as v2 webhook payloads with `data.type` set to
`message.finalized`; Orbit emits one JSON event per lifecycle transition,
with the event type in the envelope:

### Webhook Header Changes

| Telnyx | Orbit |
| - | - |
| `telnyx-signature-ed25519` | `X-Orbit-Signature` (HMAC-SHA256) |

Verify against `X-Orbit-Signature` — it is the canonical header Orbit sends on
every webhook delivery. `X-Devotel-Signature` is still emitted for backward
compatibility only; treat it as legacy and do not build new verifiers against
it.

### Payload Format Changes

**Telnyx (v2 webhook):**

```json theme={null}
{
  "data": {
    "event_type": "message.finalized",
    "id": "cde1cd67-0a07-4423-82d5-9159ba0ede34",
    "occurred_at": "2026-03-08T12:00:00Z",
    "payload": {
      "direction": "outbound",
      "from": { "phone_number": "+18005551234" },
      "to": { "phone_number": "+14155552671" },
      "received_at": "2026-03-08T12:00:00Z",
      "status": "delivered"
    }
  },
  "meta": { "attempt": 1 }
}
```

**Orbit (same delivery, one event per transition):**

```json theme={null}
{
  "id": "evt_msg_001",
  "type": "message.delivered",
  "created_at": "2026-03-08T12:00:00Z",
  "data": {
    "message_id": "msg_abc123",
    "channel": "sms",
    "from": "+18005551234",
    "to": "+14155552671",
    "status": "delivered"
  }
}
```

### Update Signature Verification

```javascript theme={null}
// Telnyx (before)
const valid = telnyx.verifySignature({
  publicKey,
  signature: headers['telnyx-signature-ed25519'],
  timestamp: headers['telnyx-timestamp'],
  payload: rawBody,
})

// Orbit (after)
import { verifyWebhookSignature } from '@devotel-orbit/node'
const valid = verifyWebhookSignature(rawBody, signature, 'whsec_your_secret')
```

### Delivery Receipts Per Channel

Orbit emits `message.sent`, `message.delivered`, and `message.failed` events
on SMS, MMS, and RCS with the same envelope shape and payload contract. Point
[each channel's wire-up at the DLR webhook guide](/guides/wire-dlr-webhooks-per-channel)
if your Telnyx logic branched per channel.

***

## Step 6: Replace Voice Calls and SIP Trunks

### Voice Calls

**Telnyx Call Control (before):**

```javascript theme={null}
await telnyx.calls.dial({
  connection_id: process.env.TELNYX_CONNECTION_ID,
  to: '+14155552671',
  from: '+18005551234',
})
```

**Orbit (after):**

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/voice/calls \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155552671",
    "from": "+18005551234",
    "webhook_url": "https://yourapp.com/hooks/voice"
  }'
```

**Call payload swap:**

**Telnyx:**

```json theme={null}
{
  "connection_id": "142308809044",
  "to": "+14155552671",
  "from": "+18005551234"
}
```

**Orbit:**

```json theme={null}
{
  "to": "+14155552671",
  "from": "+18005551234",
  "webhook_url": "https://yourapp.com/hooks/voice"
}
```

Outbound voice on Orbit terminates through the Devotel wholesale softswitch,
never through Telnyx — the telnyx connection/Call Control account you are
migrating from is inbound-only once cut over.

### IVR Trees

Replace Call Control flows with Orbit's IVR flow graph (`{ nodes, edges }`,
the same shape the visual [IVR builder](https://orbit.devotel.io/voice/ivr-builder)
saves). See the [Migrate from Twilio guide](/guides/migration-from-twilio#step-5-migrate-voice-if-applicable)
for a worked graph example — the same API surface applies to Telnyx
migrations, with `transfer` targets limited to phone numbers or on-net
extensions (outbound calls exit only via the Devotel softswitch).

### SIP Trunks

If you terminate SIP trunk traffic into Telnyx today, re-point inbound at
your Orbit SIP trunk connection (see [Connect a SIP trunk](/guides/sip-trunk-connection))
and register the same numbers in Orbit. Outbound SIP routes onto the Devotel
softswitch.

***

## Step 7: Fax

Telnyx fax migrates to Orbit's fax workflow; inbound T.38 faxes land as PDFs
and outbound faxes go through the [fax send workflow](/guides/fax-send-workflow).
Port your fax-capable numbers in Step 2, and your last inbound fax webhook
wires up like any other message webhook (Step 5).

***

## Migration Checklist

* [ ] Create Orbit account and generate API keys
* [ ] Port numbers or purchase new ones
* [ ] Complete US 10DLC registration (if applicable)
* [ ] Install Orbit SDK (`npm install @devotel-orbit/node`)
* [ ] Replace SMS/MMS send + receive calls
* [ ] Replace webhook endpoint handlers
* [ ] Update webhook signature verification
* [ ] Rewire delivery receipts per channel with the [wire-DLR guide](/guides/wire-dlr-webhooks-per-channel)
* [ ] Replace Call Control voice calls and IVR trees
* [ ] Re-point SIP trunks (if applicable)
* [ ] Migrate fax workflow (if applicable)
* [ ] Run parallel testing (send via both Telnyx and Orbit)
* [ ] Decommission the Telnyx integration
* [ ] Cancel the Telnyx account

***

## Parallel Running and Fallback

Run Telnyx and Orbit in parallel during cut-over:

1. **Phase 1 (Week 1–2):** Send 10% of traffic through Orbit, 90% through
   Telnyx. Compare delivery receipt latency and per-carrier DLR rates.
2. **Phase 2 (Week 3–4):** Split 50/50. Watch
   [delivery receipts](/guides/dlr-outcomes-monitoring) for regressions per
   carrier.
3. **Phase 3 (Week 5):** Route 100% through Orbit, keep Telnyx as fallback.

**Rollback:** The import wizard's rollback (Settings → Migrations) removes the
contacts the imported job created. For live traffic, keep Telnyx credentials
in your secret store until parallel checks finish green — fall back by
switching your `orbit.messages.send` client back to `telnyx.messages.send`
with the original `messaging_profile_id`; no re-import is needed because the
numbered configuration and messaging services are idempotent records.
Decommission **only after** the rollback path has been exercised at least
once during the parallel window.

<Tip>
  Need help with your migration? Our solutions team offers free migration
  support for customers moving from Telnyx. Contact
  [migrate@devotel.io](mailto:migrate@devotel.io).
</Tip>
