Голосовые звонки с Twilio SDK
Пользователь нажимает кнопку вызова — а звонок не проходит. Или входящий звонок приходит с задержкой в 10 секунд. Чаще всего проблема в неправильной интеграции Twilio Voice: Access Token истёк, PushKit не настроен, CallKit вызван не в том порядке. Разберём, как избежать этих ошибок.
Twilio Programmable Voice SDK — промышленная платформа, избавляющая от возни с TURN-серверами и медиасерверами. Twilio берёт на себя медиатранспорт (Opus/DTLS/SRTP), глобальную маршрутизацию через 90+ data centers. Разработчику остаётся: сигнализация через Twilio, управление аудиосессией на мобильной платформе и интеграция с системными звонковыми интерфейсами (CallKit на iOS, ConnectionService на Android). У нас за плечами 5+ успешных проектов с Twilio Voice, гарантируем стабильную работу в продакшне.
Как настроить серверную часть: Access Token?
Twilio Voice SDK аутентифицируется через краткосрочные Access Token, которые генерирует ваш бэкенд с помощью Twilio Helper Library. Токен содержит VoiceGrant — разрешения на входящие и исходящие звонки. Типичная конфигурация:
from twilio.jwt.access_token import AccessToken
from twilio.jwt.access_token.grants import VoiceGrant
token = AccessToken(
account_sid=ACCOUNT_SID,
signing_key_sid=API_KEY_SID,
private_key=API_KEY_SECRET,
identity=user_id,
ttl=3600
)
token.add_grant(VoiceGrant(
outgoing_application_sid=TWIML_APP_SID,
incoming_allow=True
))
return token.to_jwt()
Мобильный клиент получает токен при запуске и обновляет его до истечения. Twilio SDK уведомляет через делегат accessTokenInvalidOrExpired — в этот момент нужно сделать запрос на новый токен и вызвать updateAccessToken. Рекомендуем TTL 3600 секунд — баланс между безопасностью и частотой обновления. Twilio official documentation подтверждает этот подход.
Шаги для генерации Access Token на сервере:
- Создать API Key в Twilio Console.
- Настроить TwiML Application с webhook URL.
- Написать эндпоинт на Node.js/Python, возвращающий JWT с VoiceGrant.
Почему для входящих звонков на iOS обязателен PushKit?
PushKit — единственный надёжный способ доставки входящего звонка в любом состоянии приложения. APNs VoIP channel гарантирует приоритетную доставку. Без PushKit приложение не получит сигнал в фоне.
func pushRegistry(_ registry: PKPushRegistry,
didUpdate credentials: PKPushCredentials,
for type: PKPushType) {
TwilioVoice.register(accessToken: token,
deviceToken: credentials.token) { error in }
}
func pushRegistry(_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType,
completion: @escaping () -> Void) {
TwilioVoice.handleNotification(payload.dictionaryPayload,
delegate: self,
delegateQueue: nil)
// ОБЯЗАТЕЛЬНО вызвать CallKit reportNewIncomingCall до completion
}
Нарушение правила вызова CallKit до completion в PushKit delegate — принудительное завершение приложения iOS. Это не предупреждение, это crash. Интеграция с CallKit через TVODefaultAudioDevice — Twilio предоставляет готовый AVAudioSession менеджер, который правильно взаимодействует с CallKit.
Как подключить ConnectionService на Android?
Зависимость: com.twilio:voice-android:6.x.x. SDK работает поверх WebRTC, но предоставляет высокоуровневый API.
// Инициализация
Voice.initialize(context, LogLevel.DEBUG)
// Исходящий звонок
val connectOptions = ConnectOptions.Builder(accessToken)
.params(mapOf("To" to phoneNumber))
.build()
val call = Voice.connect(context, connectOptions, object : Call.Listener {
override fun onConnected(call: Call) { /* звонок установлен */ }
override fun onDisconnected(call: Call, error: CallException?) { /* завершён */ }
override fun onConnectFailure(call: Call, error: CallException) { /* ошибка */ }
})
Входящие звонки приходят через FCM. Twilio SDK обрабатывает FCM payload через Voice.handleMessage():
override fun onMessageReceived(message: RemoteMessage) {
if (Voice.handleMessage(context, message.data, object : MessageListener {
override fun onCallInvite(callInvite: CallInvite) {
// показываем уведомление о входящем звонке
showIncomingCallNotification(callInvite)
}
override fun onCancelledCallInvite(cancelledInvite: CancelledCallInvite, ...) {
// звонок отменён до ответа
}
})) { /* это Twilio push */ }
}
Принятие звонка: callInvite.accept(context, callListener). Для корректной работы с ConnectionService обязательно указывать правильный android:name в манифесте — иначе система не сможет активировать службу.
TwiML и маршрутизация на сервере
Отметим: когда мобильный клиент звонит через Twilio, запрос идёт на ваш TwiML Application webhook. Бэкенд отвечает TwiML — XML-инструкциями для Twilio:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial callerId="+1234567890">
<Client>recipient_user_id</Client>
</Dial>
</Response>
Для звонков на обычные телефонные номера — <Number> вместо <Client>. Twilio выступает посредником, ваш сервер управляет логикой маршрутизации.
Запись звонков и аналитика
Запись через TwiML <Record> или программно через REST API — доступна без изменений в SDK. Twilio хранит записи на своих серверах, предоставляя URL для скачивания. Аналитика звонков (длительность, качество, статусы) — через Twilio Console или REST API. Мы настраиваем автоматическое уведомление о записи в вашу CRM.
Сравнение Twilio Voice со своим WebRTC
| Критерий | Twilio Programmable Voice | Собственный WebRTC + TURN |
|---|---|---|
| Время запуска в продакшн | 1–3 недели | 3–6 месяцев |
| Инфраструктура | Zero (готовая) | TURN-сервер, медиасервер, WebSocket |
| Дополнительная задержка | < 50 мс (при ближайшем PoP) | 0 мс (P2P) |
| Запись звонков | Встроенная | Требует разработки |
| Поддержка CallKit/ConnectionService | Встроенная | Требует реализации |
| Стоимость минуты | от $0.014 | $0.001–0.005 (только TURN) |
Когда стоит выбрать собственный WebRTC?
Twilio Voice добавляет latency через relay — все медиапотоки идут через дата-центры Twilio, не P2P. Для большинства задач это незаметно (< 50 мс дополнительной задержки при ближайшем PoP), но в регионах без близкого дата-центра Twilio (Центральная Азия, часть Африки) задержка может быть заметной. Стоимость: при больших объёмах (10 000+ минут/день) собственный WebRTC + TURN дешевле, но дороже в разработке и поддержке. Если вам нужна P2P-связь без посредников — пишите, мы поможем выбрать архитектуру.
Процесс интеграции
| Этап | Действия | Срок |
|---|---|---|
| 1. Настройка аккаунта Twilio | TwiML App, API Keys, Push Credentials (FCM/APNs) | 1 день |
| 2. Реализация бэкенда | Генерация Access Token, TwiML webhook (Node.js, Python, Go) | 2-5 дней |
| 3. Интеграция SDK | Android (Kotlin) + ConnectionService, iOS (Swift) + CallKit | 3-7 дней |
| 4. Тестирование | Реальные устройства, отладка багов | 2-3 дня |
Срок полной интеграции — 1–3 недели. Экономия на инфраструктуре — до $50 000 за первый год.
Что входит в работу
- Разработка и настройка бэкенда: генерация Access Token, TwiML webhook (Node.js или Python).
- Интеграция Twilio Voice SDK на iOS (Swift, CallKit) и Android (Kotlin, ConnectionService).
- Настройка PushKit (iOS) и FCM (Android) для входящих звонков.
- Тестирование на реальных устройствах, отладка багов.
- Документация по интеграции и инструкция по эксплуатации.
- Обучение вашей команды (до 2 часов онлайн).
Свяжитесь для консультации по архитектуре и стоимости. Закажите интеграцию Twilio Voice у нас — получите стабильные звонки за 2 недели.







