Saltar a la navegación

Webhooks

Los webhooks de Sync Labs entregan notificaciones HTTP POST en tiempo real cuando tus jobs de generación de lip sync se completan o fallan. Usa webhooks en lugar de polling para construir flujos de trabajo eficientes y orientados a eventos que respondan a cambios de estado sin llamadas repetidas a la API.

¿Cómo configuro webhooks?

Para utilizar webhooks:

  1. Especifica un parámetro webhookUrl al llamar a un endpoint de API que lo soporte.
  2. Asegúrate de que tu endpoint de webhook pueda recibir y manejar requests HTTP POST y pueda consumir el Webhook Payload para actualizaciones de estado.

Por motivos de seguridad, tu webhook URL debe estar configurada para aceptar y procesar requests HTTPS POST.

Al aprovechar webhooks, puedes simplificar tu flujo con llamadas no bloqueantes, lo que te permite enfocarte en otras tareas mientras recibes notificaciones de finalización de jobs.

Si no recibes eventos, confirma que se incluyó exactamente webhookUrl en el request de generación, que el endpoint sea públicamente accesible por HTTPS y que devuelva una respuesta 2xx rápidamente a requests POST. Si la entrega falla porque tu endpoint no está disponible o rechaza el request, haz polling del endpoint de estado de generación como fallback.

¿Cuándo se envían los webhooks?

Se envía un webhook de generación cuando la generación llega a un estado final: COMPLETED, FAILED o REJECTED. Se envía un webhook de batch cuando el batch queda COMPLETED o FAILED.

Cada request lleva estos headers:

HeaderValor
Content-Typeapplication/json
Sync-Signaturet=<segundos unix>,v1=<HMAC-SHA256 en hex>. Consulta «Verifica firmas de webhook» más abajo.
Sync-Delivery-IdUn ID de esta entrega. Es el mismo en cada reintento del mismo evento.

Reintentos y duplicados

Sync reintenta los webhooks que tu endpoint no aceptó, tanto de generaciones como de batches:

  • Hasta 6 intentos durante unas 2 horas. Los reintentos esperan aproximadamente 1, 3, 9, 27 y 60 minutos (±25% de variación).
  • Qué se reintenta: timeouts, errores de conexión y respuestas 429 y 5xx. Un header Retry-After en esas respuestas reemplaza la espera calculada (entre 1 segundo y 60 minutos).
  • Qué no se reintenta: cualquier otra respuesta 4xx, incluida 408, una URL no válida o una redirección distinta de 307/308. La entrega se detiene en la primera respuesta de este tipo.
  • Timeout: cada intento espera hasta 10 segundos tu respuesta. Devuelve 2xx de inmediato y haz el trabajo lento después.
  • Destinos: la URL debe resolver a una dirección pública. Se siguen hasta tres redirecciones 307/308.

Un reintento puede llegar después de que tu endpoint ya procesó el evento, por ejemplo cuando tu respuesta 2xx superó el timeout, así que maneja duplicados. Deduplica con el header Sync-Delivery-Id o con el id de la generación o del batch en el payload. Cada intento se firma de nuevo con un timestamp nuevo, por lo que el valor de Sync-Signature cambia entre intentos de la misma entrega.

Si todos los intentos fallan, el evento no se vuelve a enviar. Consulta Get Generation o Get Batch para recuperar los resultados perdidos.

Manejo de errores en webhooks

El payload del webhook de generación tiene los mismos campos que Get Generation para id, createdAt, status, model, input, webhookUrl, options y outputUrl, más synthesizedAudioUrl en las generaciones que usaron una entrada de texto. Cuando una generación falla o es rechazada, dos campos explican el motivo; en los demás casos ambos son null:

  • error: un mensaje de error legible por humanos que describe qué salió mal
  • errorCode: un código de error legible por máquinas para el manejo programático de errores (consulta la guía de manejo de errores y GET /v2/errors para ver la lista completa)
webhook_payload.json
{
"id": "0f8b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"createdAt": "2026-01-10T12:00:00.000Z",
"status": "FAILED",
"model": "lipsync-2",
"input": [
{ "type": "video", "url": "https://example.com/video.mp4" },
{ "type": "audio", "url": "https://example.com/audio.mp3" }
],
"webhookUrl": "https://example.com/webhook",
"options": {},
"outputUrl": null,
"error": "The provided video URL is inaccessible.",
"errorCode": "generation_input_video_inaccessible"
}

Decide según errorCode, no según el texto del mensaje. Consulta la referencia del payload para ver todos los campos.

Verifica firmas de webhook

Para asegurar que los requests de webhook provengan de Sync Labs, firmamos cada request con una firma. La firma se incluye en el header Sync-Signature.

Para verificar firmas, usa tu signing secret. También puedes obtenerlo programáticamente con GET /v2/organizations/webhook/secret. Guarda el valor completo whsec_... de forma segura; masked=true solo sirve para mostrarlo y no debe usarse para verificar.

El header tiene la forma t=<timestamp>,v1=<firma>:

  • t: la hora Unix en segundos en que se envió este intento
  • v1: el HMAC-SHA256 en hex de <t>.<cuerpo raw del request>, con tu signing secret como clave

Calcula el HMAC sobre los bytes exactos que recibiste, no sobre JSON vuelto a serializar. Para limitar repeticiones, también puedes rechazar requests cuyo t esté lejos de tu hora actual; deja unos minutos de margen para diferencias de reloj.

const express = require("express");
const { createHmac, timingSafeEqual } = require("crypto");
const app = express();
const WEBHOOK_SECRET = "whsec_";
// Keep the exact request bytes for signature verification.
app.use("/webhook", express.raw({ type: "application/json" }));
const verifySignature = (rawBody, signature, secret) => {
try {
if (!signature) {
return false;
}
const [, timestamp, receivedSignature] =
signature.match(/t=(\d+),v1=(.+)/) ?? [];
if (!timestamp || !receivedSignature) {
return false;
}
const signedPayload = Buffer.concat([
Buffer.from(`${timestamp}.`),
rawBody,
]);
const expectedSignature = createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
// Timing-safe comparison to prevent timing attacks
return timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
);
} catch (error) {
return false;
}
};
app.post("/webhook", (req, res) => {
const signature = req.headers["sync-signature"];
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(400).json({
message: "Invalid signature",
});
}
return res.status(200).json({
message: "Webhook signature verified",
});
});
app.listen(8080, () => {});

Recursos relacionados

  • procesamiento por lotes — usa webhooks con procesamiento por lotes para flujos de alto volumen
  • Rate Limits — comprende el rate limiting de la API y cómo afecta la entrega de webhooks

Preguntas frecuentes

Usa una herramienta de túnel como ngrok para exponer tu servidor local a internet. Inicia tu endpoint de webhook en local y luego ejecuta ngrok para obtener una URL HTTPS pública. Pasa esa URL como webhookUrl al crear una generación. Sync Labs enviará requests POST a tu servidor local a través del túnel.

Sync reintenta los timeouts, los errores de conexión y las respuestas 429 y 5xx hasta 6 veces durante unas 2 horas, respetando Retry-After. Las demás respuestas 4xx no se reintentan. Si tu endpoint sigue caído después del último intento, el evento no se vuelve a enviar, así que consulta el endpoint de estado de la generación o del batch para recuperar los resultados perdidos. Los reintentos pueden entregar el mismo evento más de una vez; deduplica con el header Sync-Delivery-Id o con el id de la generación o del batch.

Cada request de webhook incluye un header Sync-Signature que contiene un timestamp y un hash HMAC-SHA256. Usa tu signing secret de la página de configuración de webhooks para calcular la firma esperada y compárala con una función de comparación segura en tiempo para evitar timing attacks. Consulta los ejemplos de código de arriba.