Cómo recibes la traducción
Ya sabes cómo entra el audio: una sala, y algo que emite dentro de ella. Esta página es la otra mitad — por dónde sale, que es lo que decide cuánto trabajo tienes tú.
Hay tres caminos y no compiten: uno es llave en mano, otro es para quien ya tiene su propia plataforma, y el tercero es para quien quiere montar la interfaz él, con su marca y dentro de su producto. Se puede usar cualquiera, o varios a la vez.
Camino 1 · Nuestra sala, llave en mano
Lo pones todo nosotros y tú te conectas. La sala existe ya —la creaste en el paso 3— y ahí dentro está todo lo que produce la traducción:
- Una pista de audio por cada idioma, en directo. Si la sala traduce a tres, hay tres pistas sonando a la vez y cada oyente se suscribe a la suya.
- El texto de lo que se va diciendo, por el canal de datos de la misma conexión: la transcripción de lo dicho y su traducción a cada idioma.
Para entrar sólo hacen falta dos cosas, que te damos nosotros:
| Qué | Qué es |
|---|---|
| La dirección de la sala | A dónde se conecta quien escucha |
| Un pase temporal | Le deja escuchar esa sala y caduca solo |
Es WebRTC estándar, así que del otro lado vale cualquier cliente que hable WebRTC — incluido un navegador, sin instalar nada.
Cómo se distinguen los idiomas
Cada pista se llama <origen>_<destino>. En una sala que se habla en español
y traduce a inglés y francés hay dos pistas: es_en y es_fr. Quien escucha se
suscribe a una y ya está; cambiar de idioma es cambiar de pista, no reconectar.
El texto, y cuántos mensajes son
Los mensajes de texto viajan por el canal de datos con el tema
liora.transcript. Filtrar por ese tema deja descartar lo que no es tuyo sin
tener que abrir el JSON de cada frase; un cliente que no lo mire recibe lo mismo,
porque el tema es metadato del paquete.
Cuántos son depende de si hay a quién traducir:
- Traduciendo: cada frase produce el original y una traducción por idioma,
todos con el mismo
segment_id, para poder pintarlos en la misma línea en vez de ir apilando una por idioma.
⚠️Júntalas por `source_user` **y** `segment_id`, no sólo por el número
El segment_id se repite entre personas. Lo lleva cada micrófono por su
cuenta y todos empiezan en 1, así que en una sala con dos personas hablando hay
dos frases distintas con segment_id: 1.
Medido el 04/09/2026 con dos clientes de verdad: {1: [ana, bruno], 2: [ana, bruno]}. Juntando sólo por el número, a una frase le puedes pegar la traducción
de otra persona — y eso no da ningún error: se lee, y dice otra cosa.
La pareja es (source_user, segment_id), y source_user viaja en los dos
mensajes.
- Subtitulando —o traduciendo pero con todos hablando el mismo idioma, que es
lo mismo para esto—: un solo mensaje, el
original_transcript. No hay a quién traducir, así que no hay traducción que mandar.
Esa segunda fila es nueva desde el 04/09/2026: antes, una sala de sólo subtítulos no producía texto por este canal.
{
"type": "original_transcript",
"original_text": "¿Empezamos?",
"original_language": "es",
"source_user": "u_9f2c",
"display_name": "Ambón",
"timestamp": "2026-09-04T18:30:12.481Z",
"segment_id": 7
}
{
"type": "translation_text",
"text": "Shall we start?",
"language": "en",
"source_language": "es",
"source_user": "u_9f2c",
"display_name": "Ambón",
"timestamp": "2026-09-04T18:30:12.481Z",
"segment_id": 7
}
source_user es quién habla y es el campo que hay que usar para agrupar por
persona: display_name es para enseñar y puede no venir. timestamp es ISO 8601.
💡Si tu cliente no conoce un tipo de mensaje, se calla
Un mensaje con un type que no esperas no da error: se ignora en silencio.
Así que si no ves subtítulos, mira primero si estás filtrando por un type
que ya no llega.
Camino 2 · A tu propia plataforma
Si ya tienes WebRTC —una plataforma de vídeo, una app de eventos, una sala de control— no tienes que entrar en ninguna sala nuestra: dinos tu endpoint WHIP y publicamos ahí.
- Cada idioma es una sesión aparte. No una sesión con varias pistas: un extremo WHIP puede aceptar una sola pista de audio, y cinco idiomas en una llegarían como uno, con cuatro perdidos en silencio.
- Si pones
{lang}en la dirección, cada idioma va por su ruta; si no, el idioma viaja como parámetrolang. - Si nos das un token, se manda como
Authorization: Bearer. - Al terminar una emisión cerramos la sesión, así que no queda nada abierto.
https://tu-plataforma.example/ingest/{lang}/whip → …/ingest/en/whip
https://tu-plataforma.example/whip → …/whip?lang=en
Funciona con lo que ya usa todo el mundo para entrar audio por WHIP; si tu plataforma lo habla, esto entra.
El texto va por su lado
WHIP transporta medios y no lleva canal de datos, así que los subtítulos no caben ahí. Si los quieres, danos una dirección y te mandamos cada línea traducida por HTTP según sale.
Es un webhook y no una conexión abierta a propósito: es una línea cada pocos segundos, y así sobrevive a que reinicies tu servidor y pasa por cualquier proxy de empresa.
Qué te llega, campo a campo. Un POST con Content-Type: application/json
por cada línea. Si nos das un token, va en Authorization: Bearer.
{
"room": "rm_abc123",
"segmentId": 12,
"lang": "en",
"sourceLang": "es",
"text": "Good morning everyone",
"sourceUser": "juan",
"displayName": "Juan",
"ts": 1788557146020
}
segmentId— el orden de captura de la frase que produjo esta línea. Todos los idiomas de una misma frase lo comparten, así que sirve para agruparlos… con el aviso de arriba: se repite entre personas, y la pareja buena es(sourceUser, segmentId).sourceLanges lo que se habló ylangel idioma detext.displayNamepuede venir vacío en las primeras frases, y no es un fallo: el nombre de un invitado se anuncia DESPUÉS de que empiece a hablar. Deja un hueco que se rellene, no un nombre que falta.tses cuándo lo mandamos, en milisegundos.
Son los mismos nombres que en el canal de datos para las mismas cosas, así que si lees los dos caminos no tienes que aprender dos vocabularios.
Camino 3 · Tu aplicación, dentro de nuestra sala
El más potente, y el que más control te da. Tu aplicación entra en la sala como un participante más: mete el micrófono de cada persona y se lleva la traducción, y la interfaz la haces tú — tu marca, tu producto, tu diseño.
No hay ninguna librería nuestra que instalar. Todo lo de aquí es WebRTC estándar, así que vale lo que ya sepas usar: el navegador lo trae de fábrica, y en móvil o escritorio cualquier librería de las de siempre.
Lo primero: un pase por persona
curl -X POST https://<nuestra-api>/api/v1/rooms/<sala>/token \
-H "X-API-Key: TU_LLAVE" -H "Content-Type: application/json" \
-d '{"identity":"juan","name":"Juan","can_publish":true,"language":"es"}'
{ "success": true, "token": "…", "url": "wss://…", "room": "…",
"identity": "juan", "can_publish": true, "expires_at": 1787454262679 }
Uno por persona y por sesión. Lo pide tu servidor con tu llave y se lo pasa a tu aplicación; la llave no sale de tu servidor.
| Campo | Qué es |
|---|---|
identity |
Quién es esa persona para ti. Es lo que verás en el texto |
name |
Lo que se enseña. Opcional |
can_publish |
true si esa persona habla; false si sólo escucha |
language |
El idioma que habla. Obligatorio si publica |
ttl_minutes |
Cuánto vale. Opcional, con tope |
La identidad con la que entra la compone el servidor: quien sólo escucha
entra marcado como oyente y quien habla entra sin marcar. Usa la que te devuelve
en identity, no la que mandaste.
⛔Una pista por persona, nunca la mezcla
Si mandas la mezcla de tu reunión, todo cae en la misma transcripción: no hay forma de saber quién habla ni desde qué idioma traducir. No falla nada — simplemente sale mal. Y por eso quien publica declara su idioma: sin él no hay nada que traducir, y la llamada al token te lo dirá en vez de dejarte descubrirlo el día del acto.
Hablar: publica el micrófono
Con ese pase, tu aplicación publica el micrófono de esa persona en la sala. Si
publicas con tu propia librería en vez de con un cliente completo, manda el
nivel de audio en la cabecera (la extensión estándar ssrc-audio-level): es
lo que permite quedarse con quien habla cuando hay mucha gente. Si no llega, se
escucha a todo el mundo — funciona igual, pero pagas por todos.
Escuchar: pide el idioma que quieras
Para llevarte una traducción a tu aplicación tienes dos formas y las dos son estándar:
-
Como un participante más: te suscribes dentro de la sala a la pista del idioma que quieras, y el texto llega por el canal de datos de esa misma conexión.
-
Con un
POSTy ya está, si prefieres no entrar en la sala: mandas tu oferta a/whep/<idioma>con tu pase en la cabeceraAuthorization, y contestamos con la respuesta. A partir de ahí suena. Para colgar, unDELETEa la dirección que te damos enLocation.curl -X POST https://<nuestro-puente>/whep/en \ -H "Authorization: Bearer <el pase de esa persona>" \ -H "Content-Type: application/sdp" --data-binary @oferta.sdpLa sala no se pide: sale del pase. Y si en esa sala todavía no hay nadie hablando ese idioma, contestamos
404— vuelve a intentarlo, no hay nada roto.
El texto no cabe por ahí —ese protocolo lleva audio y no datos—, así que los subtítulos van por el canal de datos de la sala o por la dirección tuya del camino 2.
Cómo se llama cada cosa
Esto es lo único que hay que aprenderse, y son tres reglas:
| Qué | Cómo se llama |
|---|---|
| La pista que publica una persona | La que tú quieras; quien es lo dice su pase |
| La pista de una traducción | translator_<origen>_<destino>, por ejemplo translator_es_en |
| El tema de los mensajes de texto | liora.transcript |
Para escuchar en inglés, te suscribes a la pista cuyo nombre acaba en el idioma que quieres. El origen va delante para que sepas de qué se tradujo.
El texto: cuántos mensajes por frase
Los mensajes viajan con el tema liora.transcript, y cuántos son depende de
si hay a quién traducir:
- Traduciendo: el original y una traducción por idioma, todos con el mismo
segment_id, para que los juntes en la misma línea en vez de ir añadiendo una por idioma.
⚠️Júntalas por `source_user` **y** `segment_id`, no sólo por el número
El segment_id se repite entre personas. Lo lleva cada micrófono por su
cuenta y todos empiezan en 1, así que en una sala con dos personas hablando hay
dos frases distintas con segment_id: 1.
Medido el 04/09/2026 con dos clientes de verdad: {1: [ana, bruno], 2: [ana, bruno]}. Juntando sólo por el número, a una frase le puedes pegar la traducción
de otra persona — y eso no da ningún error: se lee, y dice otra cosa.
La pareja es (source_user, segment_id), y source_user viaja en los dos
mensajes.
- Subtitulando —o traduciendo con todos hablando el mismo idioma, que para esto
es lo mismo—: un solo mensaje, el
original_transcript. Nuevo desde el 04/09/2026; antes, una sala de sólo subtítulos no mandaba nada por aquí.
Esto es lo que más te importa de los tres caminos, porque el 3 es el único en el que te suscribes tú al canal: si tu pantalla espera siempre dos mensajes, se queda esperando el segundo en una sala donde todos hablan el mismo idioma.
Así se ven los dos, cuando hay traducción:
// Lo que se ha dicho
{ "type": "original_transcript", "original_text": "Buenos días a todos",
"original_language": "es", "source_user": "juan", "display_name": "Juan",
"timestamp": "2026-08-23T10:15:00Z", "segment_id": 12 }
// Y cada idioma al que se ha traducido
{ "type": "translation_text", "text": "Good morning everyone",
"language": "en", "source_language": "es", "source_user": "juan",
"display_name": "Juan", "timestamp": "2026-08-23T10:15:01Z", "segment_id": 12 }
source_user es la identidad con la que entró esa persona — la que te devolvió
la llamada del pase, no la que mandaste.
Lo que te cuesta cada camino
| Camino | Trabajo tuyo | Cuándo elegirlo |
|---|---|---|
| 1 · Llave en mano | Ninguno | Quieres que funcione hoy |
| 2 · A tu plataforma | Un servidor que acepte audio y un endpoint para el texto | Ya tienes por dónde escuchar |
| 3 · Tu aplicación | La interfaz, y publicar y suscribirte | Quieres tu marca y tu producto |
Los tres se pagan igual: por lo que se habla en la sala, no por camino.
Los dos a la vez
Publicar en tu plataforma no apaga la sala: por defecto es además, así que quien escuchaba por la sala sigue escuchando y tu sistema recibe lo mismo. Si sólo quieres lo tuyo, se puede dejar como único destino.
Quién configura esto
Tú, desde el panel o desde la API. En la ficha de cada sala hay una tarjeta, A dónde sale la traducción, con tres respuestas y ninguna que aprenderse: a una sala nuestra, a tu plataforma, o a las dos. Si eliges tu plataforma, pides la dirección y su clave, y hay un botón de probar ahora que llama de verdad a tu servidor antes de guardar nada y te dice qué contesta.
Por la API es lo mismo:
curl -X PUT https://<nuestra-api>/api/v1/rooms/<sala>/delivery \
-H "X-API-Key: TU_LLAVE" -H "Content-Type: application/json" \
-d '{"whip_url":"https://tu-plataforma/entrada","whip_only":false,"text_url":"https://tu-api/subtitulos"}'
Y POST …/delivery/test prueba una dirección sin guardarla.
ℹ️Se aplica en la próxima emisión
El reparto se monta al abrir la emisión, así que una sala que esté sonando ahora sigue saliendo donde salía hasta que termine. Lo dice también la pantalla.
La clave no vuelve nunca. Se dice si hay una puesta, y nada más. Guardar otros campos con el hueco de la clave en blanco no la borra: para quitarla hay que decirlo.
Lo demás —los idiomas de la sala, su nombre y si está encendida— lo manejas igual, desde el panel o desde la API.
ℹ️Los ajustes acústicos tampoco se tocan desde fuera
Cuánto tiene que sonar algo para contar como voz, o dónde acaba una frase, se afina escuchando cómo suena ese sitio. Si una sala no se oye como debería, dilo y la ajustamos.