Webhooks
Un webhook es una URL tuya a la que Translay manda un POST con JSON cada vez que pasa algo: un video terminó de doblarse, una exportación está lista, YouTube pidió verificación. Sirve para encadenar Translay con n8n, Zapier, un bot o tu propio servidor sin preguntar a la API cada pocos segundos.
Crear un webhook
Sección titulada «Crear un webhook»- Abre Preferencias → Acceso y API → Webhooks y pulsa Agregar webhook.
- Escribe la URL (debe empezar por
http://ohttps://) y, si quieres, un nombre; sin nombre, usa la URL. - Elige de qué bibliotecas: Todas las bibliotecas o una.
- Marca los eventos que te interesan, o deja Todos los eventos.
- Pulsa Guardar webhook. Para dejar de recibir avisos sin borrarlo, desmarca Activo.
Cada webhook tiene un secreto (whsec_…) que se crea con él y no cambia al editarlo. Cópialo con el botón Secreto: lo necesitas para verificar la firma. Si se filtra, quita el webhook y crea otro.
Un webhook de una sola biblioteca recibe los eventos de esa biblioteca y también los generales, que no pertenecen a ninguna (cola.pausa, cola.terminada, youtube.bloqueo, …).
Qué llega
Sección titulada «Qué llega»POST /tu-ruta HTTP/1.1Content-Type: application/jsonUser-Agent: video-translator-webhooks/1X-VT-Event: video.procesadoX-VT-Delivery: 3f9a1c2b4d5eX-VT-Signature: sha256=9c1e…
{"event": "video.procesado", "time": 1730000000.0, "vault": "ciencia", "data": {"video": "yt_dQw4w9WgXcQ", "title": "…"}}| Campo | Qué es |
|---|---|
event |
El nombre del evento (igual que X-VT-Event). |
time |
Cuándo se envió, en segundos desde 1970 (Unix). |
vault |
El id de la biblioteca, o null si el evento es general. |
data |
Los datos del evento; cambian según el evento. |
X-VT-Delivery identifica cada envío. Cada reintento es un envío nuevo, con otro X-VT-Delivery y otro time.
Eventos más útiles
Sección titulada «Eventos más útiles»| Evento | Cuándo | data |
|---|---|---|
video.procesado |
Un video quedó traducido y doblado. | video, title |
video.error |
Un video falló. | video, title, error |
video.agregado |
Entraron videos a una biblioteca. | videos, count |
exportacion.lista |
Una exportación se puede descargar. | job, name, url, videos, kinds |
cola.terminada |
No queda nada por procesar. | (vacío) |
youtube.bloqueo |
YouTube pidió verificación y las descargas se pausaron. | until |
La lista completa, con todos los eventos, está en Eventos. La API también la da en GET /api/v1/events.
url en exportacion.lista es una ruta del servidor (empieza por /); para bajar el archivo, pídela con una clave de API.
Eventos agrupados
Sección titulada «Eventos agrupados»cola.cambio y descarga.progreso pueden ocurrir varias veces por segundo. En lugar de mandar cada uno, Translay junta los de cada webhook y cada 5 segundos manda solo el último.
Verificar la firma
Sección titulada «Verificar la firma»X-VT-Signature es sha256= seguido del HMAC-SHA256, en hexadecimal, del cuerpo tal como llegó (los bytes, antes de interpretarlo como JSON), con el secreto del webhook como clave. Si la firma no coincide, descarta el aviso.
import hashlib, hmac, jsonfrom http.server import BaseHTTPRequestHandler, HTTPServer
SECRETO = b"whsec_…"
def firma_valida(cuerpo: bytes, cabecera: str) -> bool: esperada = "sha256=" + hmac.new(SECRETO, cuerpo, hashlib.sha256).hexdigest() return hmac.compare_digest(esperada, cabecera or "")
class Hook(BaseHTTPRequestHandler): def do_POST(self): cuerpo = self.rfile.read(int(self.headers.get("Content-Length", 0))) if not firma_valida(cuerpo, self.headers.get("X-VT-Signature")): self.send_response(401) self.end_headers() return aviso = json.loads(cuerpo) print(aviso["event"], aviso["data"]) self.send_response(204) self.end_headers()
HTTPServer(("0.0.0.0", 9000), Hook).serve_forever()// Node 18 o más nuevo; guárdalo como .mjsimport { createServer } from "node:http";import { createHmac, timingSafeEqual } from "node:crypto";
const SECRETO = "whsec_…";
function firmaValida(cuerpo, cabecera = "") { const esperada = Buffer.from("sha256=" + createHmac("sha256", SECRETO).update(cuerpo).digest("hex")); const recibida = Buffer.from(cabecera); return esperada.length === recibida.length && timingSafeEqual(esperada, recibida);}
createServer(async (req, res) => { const partes = []; for await (const parte of req) partes.push(parte); const cuerpo = Buffer.concat(partes); if (!firmaValida(cuerpo, req.headers["x-vt-signature"])) { res.writeHead(401).end(); return; } const aviso = JSON.parse(cuerpo); console.log(aviso.event, aviso.data); res.writeHead(204).end();}).listen(9000);La firma prueba que el aviso viene de tu Translay, pero no impide que alguien que lo intercepte lo reenvíe más tarde. Si eso importa, usa HTTPS y descarta los avisos con un time muy viejo.
Reintentos y tiempo de espera
Sección titulada «Reintentos y tiempo de espera»- Cada envío espera la respuesta como mucho 8 segundos.
- Cuenta como entregado si tu servidor responde
2xx. Cualquier otro código, un error de conexión o pasarse del tiempo cuenta como fallo. - Tras un fallo se reintenta a los 10 s, 1 min y 5 min: hasta 4 intentos en total. Después se descarta.
- Si borras el webhook mientras espera un reintento, ese reintento no se manda.
Los envíos van de a uno, en orden. Responde rápido (por ejemplo, 204 y procesa después) para no frenar los siguientes.
Los reintentos y los avisos agrupados que aún no salieron viven en memoria: si el servidor se reinicia, se pierden.
Probar un webhook
Sección titulada «Probar un webhook»El botón Probar de cada webhook manda al momento un evento prueba, con vault en null y estos datos:
{"event": "prueba", "time": 1730000000.0, "vault": null, "data": {"mensaje": "Webhook de prueba desde el traductor"}}Llega firmado igual que los demás, aunque el webhook esté pausado o no tenga marcado ningún evento de esa clase: sirve para comprobar la URL y la verificación de la firma.
Registro de envíos
Sección titulada «Registro de envíos»Debajo de los webhooks, Últimos envíos muestra los 8 más recientes de todos los webhooks, con la hora, el evento y el código de respuesta. Los fallidos se marcan y dicen sin respuesta si no hubo ninguna; desde el segundo intento muestran el número de intento, y al pasar el mouse por encima, el error.
El servidor guarda en memoria los últimos 60 envíos; el registro se vacía al reiniciar.