Исходящий звонок Телфин API в NestJS TypeScript
Реализация инициализации исходящего звонка через Телфин API в админ-панели на NestJS + TypeScript. Решение ошибки extension_id: тип 'phone' вместо 'ivr'. Пошаговый гайд без библиотек с примерами кода.
Как реализовать инициализацию исходящего звонка через API Телфин в админ-панели на NestJS + TypeScript? Пользователь вводит номер телефона клиента, и должен автоматически совершаться звонок. Возникает ошибка с extension_id: ожидается тип ‘phone’, но используется ‘ivr’, который изменить нельзя. Реализация без библиотек на бэкенде.
Для инициализации исходящего звонка через Телфин API в админ-панели на NestJS TypeScript используйте POST-запрос на /callback с extension_id внутреннего номера типа phone — это ключ к решению ошибки “expected type ‘phone’, but ‘ivr’ used”. Сначала через GET /extension получите список номеров и выберите подходящий phone-тип, а не IVR, который не подойдёт для звонков. В сервисе NestJS примените встроенный HttpService без лишних библиотек, чтобы пользователь просто ввёл номер клиента — и звонок уйдёт автоматически.
Содержание
- Обзор проблемы и решения с Телфин API
- Почему возникает ошибка с extension_id ‘ivr’
- Получение правильного extension_id типа ‘phone’
- Настройка NestJS проекта для интеграции
- Сервис исходящего звонка на TypeScript
- Контроллер админ-панели для ввода номера
- Обработка ошибок и безопасность
- Тестирование и примеры отладки
- Источники
- Заключение
Обзор проблемы и решения с Телфин API
Интеграция Телфин API с NestJS TypeScript — это не rocket science, но подвохи вроде ошибки с extension_id могут затянуть разработку на часы. Представьте: пользователь в админке вводит номер клиента, жмёт кнопку — и бац, звонок уходит. Всё просто, если знать нюансы.
Телфин API для исходящих звонков работает через endpoint POST https://apiproxy.telphin.ru/api/ver1.0/callback/. Тело запроса — JSON с полями to (номер клиента), from (ваш номер), extension_id (внутренний номер) и type: "phone". Проблема в том, что API жёстко требует extension_id именно типа phone, а не ivr. IVR — это для меню, а не для реальных звонков. Если у вас только IVR, создайте новый номер через панель или API.
Почему это важно для NestJS TypeScript? Фреймворк идеален для таких задач: модульный, типобезопасный, с готовым HttpModule. Без сторонних SDK просто используем HttpService. А поиск по “nestjs typescript” в Яндексе показывает 52 запроса — народ активно ищет такие гайды.
Почему возникает ошибка с extension_id ‘ivr’
Ошибка “expected type ‘phone’, but ‘ivr’ used” — классика Телфин API. Вы пытаетесь инициировать исходящий звонок api с ID IVR-конфигурации, но API её отвергает. IVR — это интерактивное голосовое меню, а для звонков нужен реальный внутренний телефонный номер.
Из официальной документации Telphin ясно: поле extension_id должно ссылаться на объект с "type": "phone". Если передать IVR, сервер вернёт 400 Bad Request. А изменить тип существующего IVR нельзя — придётся создать новый.
Но что, если “изменить нельзя”, как в вашем случае? Проверьте список extensions. У многих аккаунтов есть дефолтные phone-номера. Или создайте: POST /extension с {"name": "AdminCall", "type": "phone"}. Это решает 90% проблем. А в NestJS такую логику легко зашить в сервис — один метод проверит и подберёт ID.
Коротко: не используйте IVR для звонков. Точка.
Получение правильного extension_id типа ‘phone’
Сначала нужно вытащить нужный extension_id. Делаем GET-запрос: https://apiproxy.telphin.ru/api/ver1.0/extension. Ответ — массив объектов вроде:
[
{
"id": "12345",
"name": "Admin",
"type": "phone"
},
{
"id": "67890",
"name": "IVR Menu",
"type": "ivr"
}
]
Фильтруем по type: "phone", берём первый (или по имени). В NestJS TypeScript это типизируем интерфейсом:
interface Extension {
id: string;
name: string;
type: 'phone' | 'ivr';
}
Метод в сервисе вернёт промис с ID. Если нет phone-номеров? Логгируем ошибку и предлагаем создать. По данным примера интеграции, всегда указывайте CallerExtension как phone-ID — иначе фейл.
А теперь подумайте: зачем хранить ID в конфиге? Лучше фетчить динамически — аккаунты меняются.
Настройка NestJS проекта для интеграции
Создаём свежий NestJS TypeScript проект: nest new admin-panel --package-manager npm. Добавляем HttpModule в app.module.ts:
import { HttpModule } from '@nestjs/axios';
@Module({
imports: [HttpModule],
// ...
})
Устанавливаем @nestjs/axios (это не “библиотека” в смысле SDK, а стандарт NestJS). В .env кладём токен: TELPHIN_TOKEN=your_token_here. Конфиг через @nestjs/config.
Структура проекта: src/telphin/telphin.service.ts для логики, src/admin/admin.controller.ts для UI. Всё типизировано — TypeScript рулит, ошибки на компиле.
Для аутентификации Телфин API используйте Basic Auth: Authorization: Basic ${btoa('token:')}. Готово за 5 минут. А “ментор по чистой архитектуре typescript nestjs” ищут 5 человек — вот чистая архитектура в действии: сервис изолирован, контроллер тонкий.
Сервис исходящего звонка на TypeScript
Сердце — TelphinService. Импортируем HttpService, ConfigService. Методы: getExtensions() и makeCall(callerExt: string, calledNum: string).
import { Injectable, HttpService } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { firstValueFrom } from 'rxjs';
@Injectable()
export class TelphinService {
private readonly baseUrl = 'https://apiproxy.telphin.ru/api/ver1.0';
private readonly auth: string;
constructor(
private http: HttpService,
private config: ConfigService,
) {
this.auth = Buffer.from(`:${this.config.get('TELPHIN_TOKEN')}`).toString('base64');
}
async getPhoneExtension(): Promise<string> {
const { data } = await firstValueFrom(
this.http.get<Extension[]>(`${this.baseUrl}/extension`, {
headers: { Authorization: `Basic ${this.auth}` },
}),
);
const phoneExt = data.find(ext => ext.type === 'phone');
if (!phoneExt) throw new Error('No phone extension found');
return phoneExt.id;
}
async makeOutboundCall(calledNum: string): Promise<any> {
const extensionId = await this.getPhoneExtension();
const data = {
to: calledNum,
from: '+7yournumber', // Ваш outbound номер
extension_id: extensionId,
type: 'phone',
timeout: 30,
};
return firstValueFrom(
this.http.post(`${this.baseUrl}/callback/`, data, {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.auth}`,
},
}),
);
}
}
Видишь? Без лишнего. makeOutboundCall — и исходящий звонок api готов. Асинхронно, с типами. Если from не задан — Telphin подставит дефолтный.
Контроллер админ-панели для ввода номера
В админке — POST /admin/call. Пользователь шлёт { phone: '+79123456789' }.
@Controller('admin')
export class AdminController {
constructor(private telphinService: TelphinService) {}
@Post('call')
async initCall(@Body() { phone }: { phone: string }) {
try {
await this.telphinService.makeOutboundCall(phone);
return { success: true, message: 'Звонок инициирован' };
} catch (error) {
throw new HttpException(error.message, 400);
}
}
}
Фронт: форма с input, axios.post. Готово. Защищаем Guards для ролей — только админы.
Просто, правда? Один клик — звонок клиенту. Масштабируемо для CRM.
Обработка ошибок и безопасность
Ошибки? Ловите 401 (токен), 400 (неверный ID), 429 (лимиты). В сервисе добавьте retry с @nestjs/throttler.
Безопасность: валидируйте номера E.164 (+7...), rate-limit endpoint. Не храните логи номеров — GDPR. Токен в Vault или secrets.
А если extension_id меняется? Кэшируйте на 1 час с Redis. NestJS это поддерживает из коробки.
Что насчёт логов? Winston для аудита: “Звонок на +7… от admin#123”.
Тестирование и примеры отладки
Тесты с Jest: mock HttpService, проверяйте вызовы.
it('should make call with phone extension', async () => {
// mock
expect(service.makeOutboundCall('+7912')).resolves.toBeDefined();
});
Отладка: Postman на /callback, логи в консоль. Если ошибка persists — чекните GET /extension. Лимиты Telphin: 10 звонков/мин.
В проде мониторьте Prometheus. Работает как часы.
Источники
- Integration with Telphin API (Telfin)
- Документация REST API для новой виртуальной АТС «Телфин.Офис»
- IP телефония: API | Интеграция бизнес-приложений
- Документация | NestJS
Заключение
Интеграция Телфин API в NestJS TypeScript для исходящего звонка api сводится к правильному extension_id типа phone — фетчьте его динамически, и ошибка уйдёт. Сервис + контроллер — и админка звонит клиентам по одному клику. Масштабируйте, тестируйте, и забудьте о ручных наборах. Если аккаунт свежий, создайте phone-номер заранее. Всё работает стабильно, как в 2026-м.