> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migration from Twilio

> This document will help in switching from Twilio to Telnyx. Start building on Telnyx today.

Are you thinking of switching from Twilio to Telnyx? This document describes some key differences between the platforms as well as implementation details for standard SDK features to make your experience as smooth as possible.

## How Twilio’s Voice SDK works

With Twilio, a lot of setup is required before you can get started making calls. because users are required to have their own backend in place, **with Telnyx this is not needed**.

TwiML is similar to Call Control that Telnyx provides - but it isn’t a requirement when using our Voice SDK.

In short, Twilio’s Voice SDK flow is as follows:

1. Your browser / mobile device connects to Twilio
2. Twilio connects to your pre-deployed server node application which can generate a token and receive voice webhooks
3. Twilio sends you a webhook to get TwiML instructions
4. Your backend server node responds with a set of TwiML instructions that you have defined for certain use cases (eg. call a number, connect to a conference)
5. Twilio receives your TwiML instructions and executes them on your behalf. (eg. Dial a number contained in your TwiML instructions)
6. Twilio creates a VoIP connection between your callee and your application.

Flow looks like this:

Caller > Twillio Voice SDK > Twillio Servers > customer backend (Webhook receiver) > Twillio Servers > Callee

## It's much easier with the Telnyx Voice SDK

Telnyx’s Voice SDK has been designed to be as simple as possible to make/receive calls in a matter of minutes. Unlike Twilio, there is no backend setup required.

Simply, implement the Voice SDK library on the platform of your choice, and log in with your Telnyx Connection and you’re all set.

To summarize:

1. Your browser / mobile device connects to Telnyx
2. You send an invitation from the client
3. If accepted, Telnyx creates a VoIP connection between your callee and your application.

Flow looks like this:

Caller > Telnyx WebRTC SDK > Telnyx servers > Callee

Optionally, if you would like to control the call, like you do with TwiML, you can use Call Control.

## Pricing

Below is a comparison table for the United States region. (Note regional prices may vary).

<table class="table">
  <tbody>
    <tr>
      <td>SERVICE </td>
      <td>TELNYX ORIGINATION</td>
      <td>TWILIO ORIGINATION</td>
      <td>TELNYX TERMINATION</td>
      <td>TWILIO TERMINATION</td>
    </tr>

    <tr>
      <td>Local Calls </td>
      <td>0.0070/ min	</td>
      <td>0.0140/ min	</td>
      <td>0.0055/ min	</td>
      <td>0.0085/ min </td>
    </tr>

    <tr>
      <td>Toll-Free Call </td>
      <td>0.0020/ min	</td>
      <td>0.0140/ min	</td>
      <td>0.0170/ min	</td>
      <td>0.0220/ min </td>
    </tr>

    <tr>
      <td>Browser / App Calling </td>
      <td>0.0020/ min	</td>
      <td>0.0040/ min	</td>
      <td>0.0020/ min	</td>
      <td>0.0040/ min </td>
    </tr>

    <tr>
      <td>SIP Interface </td>
      <td>0.0020/ min	</td>
      <td>0.0040/ min	</td>
      <td>0.0020/ min	</td>
      <td>0.0040/ min </td>
    </tr>

    <tr>
      <td>Secure Media </td>
      <td>included </td>
      <td>included </td>
      <td>included </td>
      <td>included </td>
    </tr>
  </tbody>
</table>

Sources: [https://telnyx.com/pricing/call-control](https://telnyx.com/pricing/call-control) [https://www.twilio.com/en-us/voice/pricing/us](https://www.twilio.com/en-us/voice/pricing/us)

\| [Web](#web) | [Android](#android) | [iOS](#ios) |

***

## Web

### Comparative Table (Web SDK)

<table class="table">
  <tbody>
    <tr>
      <td />

      <td>Telnyx RTC</td>
      <td>Twilio Voice SDK</td>
    </tr>

    <tr>
      <td>Portal and server initial configuration</td>
      <td>No server setup is required.
      Create SIP connections
      Buy a phone number and assign it to a SIP connection.</td>
      <td>Create a TwiML App and find the TwiML App SID and configure a webhook endpoint to make outbound calls
      Buy a phone number to make outbound and receive inbound calls and configure a webhook to be able to receive incoming calls.
      Create an API KEY to be able to generate an Access Token
      Run a server node application to generate token and to receive the voice webhook to exec dial command.</td>
    </tr>

    <tr>
      <td>SDK Installation</td>
      <td>npm i @telnyx/webrtc</td>
      <td>npm i twillio (backend)
      npm i @twilio/voice-sdk (front-end)</td>
    </tr>

    <tr>
      <td>To make calls</td>
      <td>The SDK is connected using a SIP username or JWT Token
      It calls directly between the browsers with the command `call.newCall({...})`</td>
      <td>The SDK is connected using a Token. When connecting it will return a Call object that will send a POST in Twillio Sever to /voice endpoint and in the local backend API it will exec the dial command</td>
    </tr>
  </tbody>
</table>

### Connect

#### Telnyx

```javascript theme={null}
// Initialize the client
   const client = new TelnyxRTC({
     /* Use a JWT to authenticate (recommended) */
     login_token: login_token,
     /* or use your Connection credentials */
     // login: username,
     // password: password,
   });
   // Connect and login
   client.connect();
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

#### Twilio

```javascript theme={null}
import twilio from 'twilio';

// Download the helper library from https://www.twilio.com/docs/node/install
// Find your Account SID and Auth Token at twilio.com/console
// and set the environment variables. See http://twil.io/secure
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const client = twilio(accountSid, authToken);
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

### Make a Call

#### Telnyx

```javascript theme={null}
const call = client.newCall({
     // Destination is required and can be a phone number or SIP URI
     destinationNumber: '18004377950',
     callerNumber: '155531234567',
   });
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

#### Twilio

```javascript theme={null}
client.calls
  .create({
     url: 'https://example.com',
     to: '+15558675310',
     from: '+15017122661'
   })
  .then(call => console.log(call.sid));
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

### Answer an incoming Call

**Note**: in Twilio’s case, a backend server needs to be setup. We have included a Python flask example as well as the client implementation

#### Telnyx

```javascript theme={null}
client.on('telnyx.notification', (notification) => {
   const call = notification.call;
   if (notification.type === 'callUpdate' && call.state === 'ringing') {
     call.answer();
   }
 });
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

#### Twilio

```javascript theme={null}
// Backend Server, in this case Python with flask
   @app.route('/handle_calls', methods=['POST'])
   def call():
     p.pprint(request.form)
     response = VoiceResponse()
     dial = Dial(callerId=twilio_number)
     if 'To' in request.form and request.form['To'] != twilio_number:
       print('outbound call')
       dial.number(request.form['To'])
     else:
       print('incoming call')
       caller = request.form['Caller']
       dial = Dial(callerId=caller)
       dial.client(twilio_number)
     return str(response.append(dial))
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

```javascript theme={null}
// Client
device.on("incoming", function (conn) {
conn.accept();
});
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

## Android

### Comparative Table (Android SDK)

<table class="table">
  <tbody>
    <tr>
      <td />

      <td>Telnyx RTC</td>
      <td>Twilio Voice SDK</td>
    </tr>

    <tr>
      <td>Portal and server initial configuration</td>
      <td>No server setup is required.
      Create SIP connections
      Buy a phone number and assign it to a SIP connection.</td>
      <td>Deployment of a **TwiML**. This will generate an **Application SID**.
      Create a token using the Application SID.
      Buy a phone number to make PSTN calls</td>
    </tr>

    <tr>
      <td>SDK Installation</td>
      <td>JitPack</td>
      <td>Maven Central</td>
    </tr>

    <tr>
      <td>Android Minimum API Level</td>
      <td>Android 23 (6) and higher</td>
      <td>Android 16 (4.1) and higher</td>
    </tr>

    <tr>
      <td>Java Compatibility</td>
      <td>sourceCompatibility 1.8
      targetCompatibility 1.8</td>
      <td>sourceCompatibility 1.8
      targetCompatibility 1.8</td>
    </tr>

    <tr>
      <td>Language</td>
      <td>Kotlin SDK Kotlin Sample app Compose Android Sample App</td>
      <td>Java SDK
      Java Sample app</td>
    </tr>

    <tr>
      <td>To make calls</td>
      <td>The client is connected while the app is opened, creating an instance of TelnyxClient.
      With this instance of TelnyxClient you can create and receive multiple calls</td>
      <td>The SDK is connected using a Token. When connecting it will return a Call object
      The SDK creates a call object once you have connected. Meaning you authenticate per call. (Or at least include a valid access token per call, tokens live for an hour)</td>
    </tr>

    <tr>
      <td>Receiving calls</td>
      <td>Incoming calls when logged in are handled as socket messages which the SDK is listening for. When receiving an invite socket message, a Call object is made which can be answered or declined.
      Incoming calls when logged out are handled via FCM.</td>
      <td>All incoming calls are delivered via FCM</td>
    </tr>

    <tr>
      <td>Push notifications setup (CLI)</td>
      <td>Create a firebase server key
      Create push credential in portal with firebase server key
      Attach push credential to a SIP connection.</td>
      <td>Create a firebase server key
      Use Twillio CLI to create Push Credential
      Twilio CLI returns a Push Credential ID
      You can now include Credential ID with access token request via a Voice Grant.</td>
    </tr>

    <tr>
      <td>Push notification setup on the client application</td>
      <td>Include firebase in your project with google-services.json that includes server key
      Generate a FCM token at launch via standard getInstance() method.
      Include FCM token with either credntialLogin or tokenLogin method.
      The device can now receive notifications when being called</td>
      <td>Include firebase in your project with google-services.json that includes server key
      This access token mentioned above is then used with a Voice.register() method which will register your mobile application with the FCM device token as well as the access token.
      The device can now receive notifications when being called</td>
    </tr>
  </tbody>
</table>

### Connect

**Note**: in Twilio’s case, for their mobile SDKs, they connect and authenticate per call rather than one initial connection

#### Telnyx

```java theme={null}
val telnyxClient = TelnyxClient(context)
     telnyxClient.connect()
     telnyxClient.credentialLogin(credentialConfig)
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

#### Twilio

```java theme={null}
val contact = (dialog as AlertDialog).findViewById<EditText>(R.id.contact)
   params.put("to", contact.text.toString())
   val connectOptions: ConnectOptions = Builder(accessToken)
       .params(params)
       .build()
   activeCall = Voice.connect(this@VoiceActivity, connectOptions, callListener)
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

### Make a Call

#### Telnyx

```java theme={null}
telnyxClient.call.newInvite(callerName, callerNumber, destinationNumber, clientState)
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

#### Twilio

```java theme={null}
 val contact = (dialog as AlertDialog).findViewById&lt;EditText>(R.id.contact)
    params.put("to", contact.text.toString())
    val connectOptions: ConnectOptions = Builder(accessToken)
        .params(params)
        .build()
    activeCall = Voice.connect(this@VoiceActivity, connectOptions, callListener)
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

### Answer an incoming Call

#### Telnyx

```java theme={null}
 mainViewModel.getSocketResponse()
        ?.observe(this, object : SocketObserver<ReceivedMessageBody>() {
            SocketMethod.INVITE.methodName -> {
    val inviteResponse = data.result as InviteResponse
    telnyxClient.call.acceptCall(inviteResponse.callId,  inviteResponse.callerIdNumber)
      }
    })
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

#### Twilio

```java theme={null}
 // Receive Intent from notification:
 private fun handleIncomingCallIntent(intent: Intent?) {
   if (intent != null && intent.action != null) {
       val action = intent.action
       activeCallInvite = intent.getParcelableExtra<Parcelable>(Constants.INCOMING_CALL_INVITE)
       activeCallNotificationId =  intent.getIntExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, 0)
       when (action) {
           Constants.ACTION_ACCEPT -> answer()
           else -> {}
       }
   }
}
  // answer the call
  private fun answer() {
   activeCallInvite.accept(this, callListener)
 }
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

## iOS

### Comparative Table (iOS SDK)

<table class="table">
  <tbody>
    <tr>
      <td />

      <td>Telnyx RTC</td>
      <td>Twilio Voice SDK</td>
    </tr>

    <tr>
      <td>Portal and server initial configuration</td>
      <td>No server setup is required.
      Create SIP connections
      Buy a phone number and assign it to a SIP connection.</td>
      <td>Deployment of a **TwiML**. This will generate an **Application SID**.
      Create a **token** using the Application SID.
      Buy a phone number to make PSTN calls</td>
    </tr>

    <tr>
      <td>SDK Installation</td>
      <td>Cocoapods</td>
      <td>SPM: Recommended.
      Cocoapods
      Carthage
      Framework</td>
    </tr>

    <tr>
      <td>To make calls</td>
      <td>The client is connected while the app is opened.
      While connected you can make calls.</td>
      <td>The SDK is connected using a Token. When connecting it will return a Call object</td>
    </tr>

    <tr>
      <td>Push notifications setup (Portal)</td>
      <td>Create an APNS certificate
      Upload the APNS certificate
      Assign the APNS certificate to a SIP connection.</td>
      <td>Create an APNS certificate
      Upload the APNS certificate: This will create a CR\_ID.
      Assign the CR\_ID to the APPLICATION\_SID</td>
    </tr>

    <tr>
      <td>Push notification setup on the client APP</td>
      <td>To register a device for PN: We need to wait until APNS assign a new token and then connect to the client to send the PN parameters over the login message.
      Unregister a device from the PN is not supported
      The last registered device for a SIP connection is the one that will receive the push notification.</td>
      <td>Register a device for PN: Requires a deviceToken and the PushToken. The device is a relation between the TwiML app, the user and the device.
      You can unregister to stop getting PN
      The same user can be registered on multiple devices</td>
    </tr>
  </tbody>
</table>

### Connect

**Note**: in Twilio’s case, for their mobile SDKs, they connect and authenticate per call rather than one initial connection

#### Telnyx

```c theme={null}
let telnyxClient = TxClient()
   do {
      try telnyxClient.connect(txConfig: txConfigToken)
   } catch let error {
      print("ViewController:: connect Error \(error)")
   }
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

#### Twilio

```c theme={null}
let connectOptions = ConnectOptions(accessToken: accessToken) { builder in
       builder.params = [twimlParamTo: self.outgoingValue.text ?? ""]
       builder.uuid = uuid
   }
   let call = TwilioVoiceSDK.connect(options: connectOptions, delegate: self)
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

### Make a Call

#### Telnyx

```c theme={null}
self.currentCall = try self.telnyxClient?.newCall(callerName: "Caller name",                                           callerNumber: "155531234567",
    // Destination is required and can be a phone number or SIP URI
   destinationNumber: "18004377950",
   callId: UUID.init())
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

#### Twilio

```c theme={null}
  let connectOptions = ConnectOptions(accessToken: accessToken) { builder in
       builder.params = [twimlParamTo: self.outgoingValue.text ?? ""]
       builder.uuid = uuid
   }
   let call = TwilioVoiceSDK.connect(options: connectOptions, delegate: self)
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

### Answer an incoming Call

#### Telnyx

```c theme={null}
 extension ViewController: TxClientDelegate {
 //....
 func onIncomingCall(call: Call) {
   // We are automatically answering any incoming call as an example, but
   // maybe you want to store a reference of the call, and answer the call after a button press.
   self.myCall = call.answer()
   }
}
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>

#### Twilio

```c theme={null}
 // Listen for telephony notification (after prior setup)
   extension ViewController: CXProviderDelegate {
   func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
   NSLog("provider:performAnswerCallAction:")
   performAnswerVoiceCall(uuid: action.callUUID) { success in
       if success {
           NSLog("performAnswerVoiceCall() successful")
       } else {
           NSLog("performAnswerVoiceCall() failed")
       }
   }
   action.fulfill()
   }
  }
   // answer the call
   func performAnswerVoiceCall(uuid: UUID, completionHandler: @escaping (Bool) -> Void) {
   guard let callInvite = activeCallInvites[uuid.uuidString] else {
       NSLog("No CallInvite matches the UUID")
       return
   }
   let acceptOptions = AcceptOptions(callInvite: callInvite) { builder in
       builder.uuid = callInvite.uuid
   }
   let call = callInvite.accept(options: acceptOptions, delegate: self)
 }
```

<Callout type="info">
  After pasting the above content, Kindly check and remove any new line added
</Callout>
