Añade un agente de voz a Flutter por WebSocket con Wixzel Voice
Al final, tu app Flutter permite a un usuario hablar con un agente de voz que creaste, por WebSocket, sin troncal SIP ni número de teléfono. Tu servidor crea una sesión de un solo uso, así que tu clave de API nunca llega a la app, y el SDK de Dart wixzel_voice habla el protocolo. Calcula alrededor de una hora, sobre todo para conectar el micrófono y el altavoz. Una sesión se factura como una llamada telefónica con el mismo motor: $0.0491 por minuto conectado con el motor Gemini Live, unos $0.20 por una sesión de cuatro minutos, por segundo desde un saldo prepago, sin costo de telefonía.
Por Aqeel Shamsudheen · Publicado el · Actualizado el
- Duración
- Alrededor de una hora
- Motor
- gemini-live
- Tiempo de motor
- $0.0491 por minuto conectado
¿Qué necesitas antes de empezar?
- Una clave de API de Wixzel Voice con crédito, guardada en tu servidor.
- Un servidor que controles para crear sesiones. El ejemplo usa Node.js y Express; sirve cualquier lenguaje que pueda hacer una petición HTTPS.
- Una app Flutter con un plugin de grabación que transmita PCM de 16 bits, idealmente a 8 kHz mono con cancelación de eco, y un reproductor que acepte PCM sin procesar.
¿Cómo construyes esto en Wixzel Voice, paso a paso?
01Crea el agente una vez
Este agente usa Gemini Live, un solo modelo en tiempo real que escucha, razona y habla:
shellcurl https://api.voice.wixzel.com/v1/agents \ -H "Authorization: Bearer $WIXZEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "In-app assistant", "system_prompt": "You are the voice assistant inside the Acme app. Answer in one or two short sentences.", "opening_message": "Hi, what can I help you with?", "voice": { "realtime": { "model": "google/gemini-live", "voice": "Kore" } } }'Nada de este agente es específico de la app: el mismo agente podría atender un número de teléfono. Guarda su
iden tu servidor comoAGENT_ID.02Crea una sesión en tu servidor
La app nunca tiene tu clave de API. Pide la sesión a tu servidor, y tu servidor se la pide a la API con
POST /v1/realtime/sessions, aquí mediante el SDK de TypeScript:server.mjsimport express from 'express'; import { WixzelVoice } from 'wixzel-voice'; const wixzel = new WixzelVoice({ apiKey: process.env.WIXZEL_API_KEY }); const app = express(); // Replace with your own authentication. It must set req.userId. function requireUser(req, res, next) { req.userId = 'demo-user'; next(); } app.post('/voice/session', requireUser, async (req, res) => { const session = await wixzel.realtime.createSession({ agent_id: process.env.AGENT_ID, max_duration_seconds: 300, metadata: { user_id: req.userId }, }); res.json(session); }); app.listen(3000);La respuesta incluye la
urldel socket, unclient_secretque funciona una sola vez, durante un minuto, para este agente, yaudio_format: "mulaw_8000". Crear la sesión no cuesta nada; la facturación empieza cuando la app se conecta.max_duration_secondslimita la conversación, entre 10 y 3600 segundos, 600 por defecto.metadatase devuelve en el registro de la llamada y nunca se envía al modelo.allowed_originssolo importa en navegadores, así que una app nativa lo omite.03Conéctate desde la app
Añade el SDK con
flutter pub add wixzel_voice httpy abre la sesión que devolvió tu servidor:voice_call.dartimport 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:wixzel_voice/wixzel_voice.dart'; /// Whatever your playback plugin offers, behind a small interface. abstract class PcmPlayer { void add(Int16List samples); void clear(); } class VoiceCall { VoiceCall({required this.player, required this.onLine}); final PcmPlayer player; final void Function(String role, String text) onLine; RealtimeConnection? _conn; StreamSubscription<Uint8List>? _mic; final List<int> _pending = []; /// [micBytes] is 16-bit little-endian PCM, 8 kHz mono, from your recording plugin. Future<void> start(Stream<Uint8List> micBytes) async { final res = await http.post(Uri.parse('https://api.example.com/voice/session')); final status = res.statusCode; if (status != 200) throw Exception('Could not start a voice session: HTTP $status'); final session = RealtimeSession.fromJson(jsonDecode(res.body) as Map<String, dynamic>); final conn = RealtimeConnection.forSession(session); _conn = conn; conn.events.listen((event) { switch (event) { case RealtimeAudio(:final mulaw): player.add(mulawToPcm16(mulaw)); case RealtimeAudioClear(): player.clear(); // the user interrupted: drop queued audio case RealtimeTranscript(:final role, :final text): onLine(role, text); case RealtimeError(:final code, :final message): onLine('error', '$code: $message'); case RealtimeEnded(:final reason): onLine('system', 'ended: $reason'); _mic?.cancel(); default: } }); await conn.ready; _mic = micBytes.listen(_send); } void _send(Uint8List bytes) { final data = ByteData.sublistView(bytes); for (var i = 0; i + 1 < bytes.length; i += 2) { _pending.add(data.getInt16(i, Endian.little)); } // 160 samples is 20 ms at 8 kHz, the frame size the server expects. while (_pending.length >= 160) { final frame = Int16List.fromList(_pending.sublist(0, 160)); _pending.removeRange(0, 160); _conn?.sendAudio(pcm16ToMulaw(frame)); } } Future<void> end() async { await _mic?.cancel(); await _conn?.end(); } }RealtimeConnectionhabla el protocolo y nada más: no abre el micrófono ni el altavoz, lo que mantiene el SDK libre de dependencias de plugins y te deja usar los plugins de audio que ya tienes.pcm16ToMulawymulawToPcm16convierten entre PCM de 16 bits y lo que viaja por la red.Pide primero permiso para el micrófono:
RECORD_AUDIOen Android yNSMicrophoneUsageDescriptionen iOS.04Activa la cancelación de eco y cuenta con audio de calidad telefónica
Activa la cancelación de eco de la plataforma en el plugin de grabación. Sin ella, el agente se oye a sí mismo por el altavoz y se interrumpe.
El audio es G.711 µ-law a 8 kHz en ambas direcciones, igual que una línea telefónica, porque las sesiones pasan por la misma ruta de medios que las llamadas. Es suficiente para voz, pero más limitado que el audio de banda ancha. Graba a 8 kHz si tu plugin lo permite, o vuelve a muestrear antes de
pcm16ToMulaw, y reproduce el audio del agente a 8 kHz mono.05Consulta la sesión después
Cada sesión es un registro de llamada con
channel: "web":shellcurl "https://api.voice.wixzel.com/v1/calls?channel=web&limit=5" \ -H "Authorization: Bearer $WIXZEL_API_KEY"Una sesión web es entrante, no tiene
fromnito, e incluye su duración,cost_microsy tumetadata;GET /v1/calls/{id}añade la transcripción. Termina con el mismo webhookcallCompletedque envía una llamada telefónica. Las sesiones web no se graban.
¿Cuánto cuesta esto en Wixzel Voice?
Tiempo de motor para sesiones de 4 minutos con el motor gemini-live a $0.0491 por minuto conectado: unos $0.20 por sesión. No hay costo de telefonía, porque no hay línea telefónica.
| Sesiones al mes | Minutos conectados | Costo de motor al mes |
|---|---|---|
| 500 | 2000 | $98.20 |
| 2500 | 10.000 | $491 |
| 10.000 | 40.000 | $1,964 |
Una sesión cuesta lo mismo que una llamada telefónica con el mismo motor, por segundo, desde el mismo saldo prepago. Crear una sesión no cuesta nada, así que un usuario que abre la pantalla y luego deniega el micrófono no cuesta nada.
Prepago, sin suscripción y sin plan gratuito: agrega crédito desde $5 y el uso lo va consumiendo. Calcula tu propia combinación.
¿Qué límites de Wixzel Voice se aplican a esto?
- Audio de calidad telefónica: G.711 µ-law a 8 kHz en ambas direcciones, no banda ancha.
- El SDK de Dart no captura ni reproduce audio. Tu app aporta el micrófono y el altavoz mediante los plugins que elija.
- Las sesiones cuentan para el límite de llamadas simultáneas de la cuenta, cinco por defecto, junto con las llamadas telefónicas. El límite se amplía a petición.
- Una sesión dura 10 minutos por defecto y como máximo una hora, y dos minutos de silencio la terminan.
- Las sesiones web no se graban, y no hay una segunda línea para pasar al usuario con una persona.
Preguntas frecuentes
¿Hay una API de agentes de voz para Flutter?
wixzel_voice en pub.dev, incluye RealtimeConnection, que conecta una app Flutter o Dart a un agente de voz por WebSocket. Tu servidor crea la sesión con tu clave de API; la app solo recibe un secreto de un solo uso válido durante un minuto.¿Necesito un número de teléfono o una troncal SIP para un agente de voz dentro de la app?
¿Por qué el agente se interrumpe a sí mismo?
¿Puedo usar otro motor en la app?
¿Qué pasa si la cuenta se queda sin crédito a mitad de una sesión?
session.warning con el código low_balance y aproximadamente cuántos segundos quedan; después la sesión termina con el motivo insufficient_credits. Una sesión que no se puede financiar en absoluto se rechaza con el código de cierre 4402.Dónde leer más
- Realtime: web y móvildocumentación
Crear sesiones, el protocolo, los códigos de rechazo y la facturación.
- SDKdocumentación
Los SDK de Dart y TypeScript, incluido el cliente para navegador.
- Motores de vozdocumentación
Voces en tiempo real y compuestas, y las listas de voces.
- Webhooksdocumentación
callCompleted para sesiones web, con tus metadatos.
Acerca de Wixzel Voice
Wixzel Voice es una API de IA de voz para crear agentes de IA que hacen y contestan llamadas telefónicas reales a través de tu propia troncal SIP, o que hablan con personas en tus apps web y móviles. Una clave de API y un saldo prepagado cubren todos los motores de voz, con cobro por segundo de uso real.
Más guías
Claude, en Claude Code o claude.ai, haciendo una llamada real por tu troncal SIP y leyéndote la transcripción, con una confirmación antes de marcar.
Tu número actual de Twilio atendido por un recepcionista de IA que agenda citas en la API, hecho en Node.js.
Dos agentes telefónicos, uno en hindi y otro en malayalam, con los modelos de Sarvam para idiomas de la India, que atienden o hacen llamadas por tu troncal SIP.
Un agente Gemini Live llamando a un teléfono real por tu troncal SIP a partir de cuatro peticiones cURL, y el registro de la llamada con su transcripción y su costo.
Una lista de leads llamados por tu agente a través de tu troncal SIP, en lotes ajustados a tu límite de llamadas simultáneas, con el estado y la transcripción de cada llamada.
Una mascota que habla en la esquina de tu sitio web, a la que los visitantes llaman desde el navegador, limitada a tus dominios y a tus límites diarios, con cada persona que llama registrada.
Una pantalla de Flutter con la mascota de tu agente, que mueve la boca con la voz del agente durante una llamada en tiempo real, con tu clave de API guardada en tu servidor.
Crea tu primer agente de voz

