Ir al contenido
Guía

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 · Publicado el · Actualizado el

Duración
Alrededor de una hora
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?

  1. 01Crea el agente una vez

    Este agente usa Gemini Live, un solo modelo en tiempo real que escucha, razona y habla:

    shell
    curl 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 id en tu servidor como AGENT_ID.

  2. 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.mjs
    import 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 url del socket, un client_secret que funciona una sola vez, durante un minuto, para este agente, y audio_format: "mulaw_8000". Crear la sesión no cuesta nada; la facturación empieza cuando la app se conecta. max_duration_seconds limita la conversación, entre 10 y 3600 segundos, 600 por defecto. metadata se devuelve en el registro de la llamada y nunca se envía al modelo. allowed_origins solo importa en navegadores, así que una app nativa lo omite.

  3. 03Conéctate desde la app

    Añade el SDK con flutter pub add wixzel_voice http y abre la sesión que devolvió tu servidor:

    voice_call.dart
    import '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();
      }
    }

    RealtimeConnection habla 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. pcm16ToMulaw y mulawToPcm16 convierten entre PCM de 16 bits y lo que viaja por la red.

    Pide primero permiso para el micrófono: RECORD_AUDIO en Android y NSMicrophoneUsageDescription en iOS.

  4. 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.

  5. 05Consulta la sesión después

    Cada sesión es un registro de llamada con channel: "web":

    shell
    curl "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 from ni to, e incluye su duración, cost_micros y tu metadata; GET /v1/calls/{id} añade la transcripción. Termina con el mismo webhook callCompleted que 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.

Costo mensual de motor de lo que construye esta guía
Sesiones al mesMinutos conectadosCosto de motor al mes
5002000$98.20
250010.000$491
10.00040.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 tiene una. Su SDK de Dart, 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?

No. Las sesiones en apps web y móviles funcionan por WebSocket sin nada que conectar. La troncal y el número solo hacen falta para llamadas desde y hacia teléfonos reales.

¿Por qué el agente se interrumpe a sí mismo?

Está oyendo su propia voz por el altavoz. Activa la cancelación de eco en tu plugin de grabación, o prueba con auriculares para confirmarlo.

¿Puedo usar otro motor en la app?

Sí. Los pipelines classic y sarvam también funcionan por el socket: elige classic para una voz concreta de ElevenLabs, o sarvam para idiomas de la India. El precio es el precio por minuto del propio motor, el mismo que en una llamada telefónica; aquí se usa Gemini Live, a $0.0491.

¿Qué pasa si la cuenta se queda sin crédito a mitad de una sesión?

La app recibe un 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.

API para telefonía con agentes

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.

Dale a Claude una línea telefónica con el MCP de Wixzel Voice

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.

Recepcionista con IA para Twilio en Node.js con Wixzel Voice

Tu número actual de Twilio atendido por un recepcionista de IA que agenda citas en la API, hecho en Node.js.

Agente telefónico en hindi y malayalam con sarvam en Wixzel Voice

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.

Gemini Live en una llamada real con la API de Wixzel Voice

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.

Haz una campaña de calificación de leads con IA con Wixzel Voice

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.

Añade una mascota de IA que habla a tu web con Wixzel Voice

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.

Muestra la mascota de tu agente en Flutter con Wixzel Voice

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.