| name | telnyx-webrtc-client-ios |
| description | Build VoIP calling apps on iOS using Telnyx WebRTC SDK. Covers authentication, making/receiving calls, CallKit integration, PushKit/APNS push notifications, call quality metrics, and AI Agent integration. Use when implementing real-time voice communication on iOS. |
| metadata | {"author":"telnyx","product":"webrtc","language":"swift","platform":"ios"} |
Telnyx WebRTC - iOS SDK
Build real-time voice communication into iOS applications using Telnyx WebRTC.
Prerequisites: Create WebRTC credentials and generate a login token using the Telnyx server-side SDK. See the telnyx-webrtc-* skill in your server language plugin (e.g., telnyx-python, telnyx-javascript).
Installation
CocoaPods
pod 'TelnyxRTC', '~> 0.1.0'
Then run:
pod install --repo-update
Swift Package Manager
- In Xcode: File → Add Packages
- Enter:
https://github.com/team-telnyx/telnyx-webrtc-ios.git
- Select the
main branch
Project Configuration
-
Disable Bitcode: Build Settings → "Bitcode" → Set to "NO"
-
Enable Background Modes: Signing & Capabilities → +Capability → Background Modes:
- Voice over IP
- Audio, AirPlay, and Picture in Picture
-
Microphone Permission: Add to Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access required for VoIP calls</string>
Authentication
Option 1: Credential-Based Login
import TelnyxRTC
let telnyxClient = TxClient()
telnyxClient.delegate = self
let txConfig = TxConfig(
sipUser: "your_sip_username",
password: "your_sip_password",
pushDeviceToken: "DEVICE_APNS_TOKEN",
ringtone: "incoming_call.mp3",
ringBackTone: "ringback_tone.mp3",
logLevel: .all
)
do {
try telnyxClient.connect(txConfig: txConfig)
} catch {
print("Connection error: \(error)")
}
Option 2: Token-Based Login (JWT)
let txConfig = TxConfig(
token: "your_jwt_token",
pushDeviceToken: "DEVICE_APNS_TOKEN",
ringtone: "incoming_call.mp3",
ringBackTone: "ringback_tone.mp3",
logLevel: .all
)
try telnyxClient.connect(txConfig: txConfig)
Configuration Options
| Parameter | Type | Description |
|---|
sipUser / token | String | Credentials from Telnyx Portal |
password | String | SIP password (credential auth) |
pushDeviceToken | String? | APNS VoIP push token |
ringtone | String? | Audio file for incoming calls |
ringBackTone | String? | Audio file for ringback |
logLevel | LogLevel | .none, .error, .warning, .debug, .info, .all |
forceRelayCandidate | Bool | Force TURN relay (avoid local network) |
Region Selection
let serverConfig = TxServerConfiguration(
environment: .production,
region: .usEast
)
try telnyxClient.connect(txConfig: txConfig, serverConfiguration: serverConfig)
Client Delegate
Implement TxClientDelegate to receive events:
extension ViewController: TxClientDelegate {
func onSocketConnected() {
}
func onSocketDisconnected() {
}
func onClientReady() {
}
func onClientError(error: Error) {
}
func onIncomingCall(call: Call) {
self.currentCall = call
}
func onPushCall(call: Call) {
self.currentCall = call
}
func onCallStateUpdated(callState: CallState, callId: UUID) {
switch callState {
case .CONNECTING:
break
case .RINGING:
break
case .ACTIVE:
break
.:
.( reason):
reason reason {
()
()
}
.( reason):
()
.( reason):
()
}
}
}
Making Outbound Calls
let call = try telnyxClient.newCall(
callerName: "John Doe",
callerNumber: "+15551234567",
destinationNumber: "+18004377950",
callId: UUID()
)
Receiving Inbound Calls
func onIncomingCall(call: Call) {
self.currentCall = call
call.answer()
}
Call Controls
call.hangup()
call.muteAudio()
call.unmuteAudio()
call.hold()
call.unhold()
call.dtmf(digit: "1")
Push Notifications (PushKit + CallKit)
1. Configure PushKit
import PushKit
class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate {
private var pushRegistry = PKPushRegistry(queue: .main)
func initPushKit() {
pushRegistry.delegate = self
pushRegistry.desiredPushTypes = [.voIP]
}
func pushRegistry(_ registry: PKPushRegistry,
didUpdate credentials: PKPushCredentials,
for type: PKPushType) {
if type == .voIP {
let token = credentials.token.map { String(format: "%02X", $0) }.joined()
}
}
func pushRegistry(_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType,
completion: @escaping () -> ) {
type .voIP {
handleVoIPPush(payload: payload)
}
completion()
}
}
2. Handle VoIP Push
func handleVoIPPush(payload: PKPushPayload) {
guard let metadata = payload.dictionaryPayload["metadata"] as? [String: Any] else { return }
let callId = metadata["call_id"] as? String ?? UUID().uuidString
let callerName = (metadata["caller_name"] as? String) ?? ""
let callerNumber = (metadata["caller_number"] as? String) ?? ""
let txConfig = TxConfig(sipUser: sipUser, password: password, pushDeviceToken: token)
try? telnyxClient.processVoIPNotification(
txConfig: txConfig,
serverConfiguration: serverConfig,
pushMetaData: metadata
)
let callHandle = CXHandle(type: .generic, value: callerNumber)
let callUpdate = CXCallUpdate()
callUpdate.remoteHandle = callHandle
provider.reportNewIncomingCall(with: UUID(uuidString: callId), update: callUpdate) { error
error error {
()
}
}
}
3. CallKit Integration
import CallKit
class AppDelegate: CXProviderDelegate {
var callKitProvider: CXProvider!
func initCallKit() {
let config = CXProviderConfiguration(localizedName: "TelnyxRTC")
config.maximumCallGroups = 1
config.maximumCallsPerCallGroup = 1
callKitProvider = CXProvider(configuration: config)
callKitProvider.setDelegate(self, queue: nil)
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
telnyxClient.enableAudioSession(audioSession: audioSession)
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
telnyxClient.disableAudioSession(audioSession: audioSession)
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
telnyxClient.answerFromCallkit(answerAction: action)
}
( : , : ) {
telnyxClient.endCallFromCallkit(endAction: action)
}
}
Call Quality Metrics
Enable with debug: true:
let call = try telnyxClient.newCall(
callerName: "John",
callerNumber: "+15551234567",
destinationNumber: "+18004377950",
callId: UUID(),
debug: true
)
call.onCallQualityChange = { metrics in
print("MOS: \(metrics.mos)")
print("Jitter: \(metrics.jitter * 1000) ms")
print("RTT: \(metrics.rtt * 1000) ms")
print("Quality: \(metrics.quality.rawValue)")
switch metrics.quality {
case .excellent, .good:
case .fair:
case .poor, .bad:
case .unknown:
}
}
| Quality Level | MOS Range |
|---|
| .excellent | > 4.2 |
| .good | 4.1 - 4.2 |
| .fair | 3.7 - 4.0 |
| .poor | 3.1 - 3.6 |
| .bad | ≤ 3.0 |
AI Agent Integration
1. Anonymous Login
client.anonymousLogin(
targetId: "your-ai-assistant-id",
targetType: "ai_assistant"
)
2. Start Conversation
let call = client.newInvite(
callerName: "User",
callerNumber: "user",
destinationNumber: "ai-assistant",
callId: UUID()
)
3. Receive Transcripts
let cancellable = client.aiAssistantManager.subscribeToTranscriptUpdates { transcripts in
for item in transcripts {
print("\(item.role): \(item.content)")
}
}
4. Send Text Message
let success = client.sendAIAssistantMessage("Hello, can you help me?")
Custom Logging
class MyLogger: TxLogger {
func log(level: LogLevel, message: String) {
MyAnalytics.log(level: level, message: message)
}
}
let txConfig = TxConfig(
sipUser: sipUser,
password: password,
logLevel: .all,
customLogger: MyLogger()
)
Troubleshooting
| Issue | Solution |
|---|
| No audio | Ensure microphone permission granted |
| Push not working | Verify APNS certificate in Telnyx Portal |
| CallKit crash on iOS 13+ | Must report incoming call to CallKit |
| Audio routing issues | Use enableAudioSession/disableAudioSession in CXProviderDelegate |
| Login fails | Verify SIP credentials in Telnyx Portal |
Resources