|
Documentación
Agenda una demoPlataforma
PlataformaMCPCLIAPI
Flujos de trabajo
GuíasRegistro de cambios

Bienvenida

  • Descripción general
  • Autenticación
  • Errores y códigos de estado
  • Firmas de webhook

Localización

  • Descripción general
  • Crear trabajos
  • Bloquea las claves no traducibles
  • Monitorear un grupo de trabajos
  • Obtener un trabajo
  • Listar trabajos
  • Entrega de webhooks
  • Progreso en tiempo real (WebSocket)

Pipeline

  • Descripción general
  • Edición con IA previa a la localización
  • Revisión humana
  • evaluación de IA (post-edición)
  • Reescribe para que suene natural
  • Verificación de retrotraducción
  • Configura el pipeline
  • Ver ejecuciones del pipeline

Aprovisionamiento

  • Descripción general
  • Crear un trabajo de aprovisionamiento
  • Tipos de fuente
  • Lo que extrae la IA
  • Entrega de webhooks
  • Progreso en vivo (WebSocket)

Síncrono

  • Localize
  • Recognize

Gestión del motor

  • Sugerencias del motor

Firmas de webhook

Cuando termina un trabajo asíncrono, Lingo.dev no te obliga a consultarlo una y otra vez. Te llama de vuelta: hace un POST al endpoint HTTPS que registraste como tu callbackUrl. Esa es la conveniencia. También es la exposición: una URL pública acepta cualquier cosa que internet le envíe, y cualquiera que conozca la tuya puede POST un evento falso de "trabajo completado" a tu handler.

Por eso la regla para cada callback es la misma: verifica antes de confiar. Cada entrega incluye una firma calculada a partir de un secreto que solo tú y Lingo.dev conocen. Recalcúlala de tu lado, compárala en tiempo constante, y un payload falsificado nunca llegará a tu lógica de negocio. Esta página es el único lugar donde se explica ese mecanismo. Tanto los callbacks de localización como los de aprovisionamiento lo usan sin cambios; esas páginas cubren la forma de sus propios payloads y enlazan aquí para la verificación.

En esta página

  • Los tres encabezados
  • El secreto de firma
  • Verificar una firma
  • Por qué importa el cuerpo sin procesar
  • Rechazar reenvíos
  • Responder rápido, procesar después
  • Reintentos y backoff

Los tres encabezados#

Lingo.dev sigue la especificación Standard Webhooks, un esquema abierto que implementan varios proveedores, así que verificas contra un contrato publicado y no contra una solución ad hoc de un proveedor. Cada entrega incluye tres encabezados:

EncabezadoDescripción
webhook-idUn identificador único de la entrega.
webhook-timestampTimestamp Unix en segundos de cuando se envió la entrega.
webhook-signatureLa firma en sí: v1,{base64(HMAC-SHA256(secret, "{id}.{timestamp}.{body}"))}

El contenido firmado son tres partes unidas por puntos: webhook-id, luego webhook-timestamp y después el cuerpo sin procesar de la solicitud, en ese orden exacto. Reconstruye esa cadena, pásala por HMAC-SHA256 con tu secreto, codifica el resultado en base64 y tendrás el valor que debes comparar.

El encabezado webhook-signature puede incluir más de una firma separada por espacios, cada una etiquetada con una versión del esquema (v1,...). Un verificador acepta la entrega si cualquiera de las firmas coincide. Recorrer la lista, en vez de leer un solo valor, es la forma defensiva de interpretar este encabezado; por eso los ejemplos de abajo iteran sobre cada firma presente.

El secreto de firma#

El secreto se genera para tu organización la primera vez que envías un trabajo con un callbackUrl. Lleva el prefijo whsec_, seguido de bytes de clave codificados en base64:

text
whsec_Mf9aQ7n...base64...key...bytes

Quita el prefijo whsec_ y decodifica en base64 el resto para recuperar los bytes sin procesar de la clave; ese valor decodificado es la clave HMAC, no la cadena con prefijo. Firmar contra el texto literal whsec_... es la razón más común por la que una implementación que parece correcta nunca coincide, así que primero decodifica.

Trata el secreto como una clave de API

El secreto de firma es lo que separa un callback real de uno falsificado. Guárdalo del lado del servidor, fuera del control de versiones y fuera de cualquier bundle del cliente. Cualquiera que lo tenga puede firmar payloads que tu handler aceptará. Consulta API Keys para ver cómo Lingo.dev maneja las credenciales con alcance a nivel organización.

Verificar una firma#

La verificación es una sola función que montas una vez delante de tu handler. Hace tres cosas: recalcula la firma esperada a partir del cuerpo sin procesar, la compara con la que llegó usando una comprobación en tiempo constante y rechaza todo lo que no coincida antes de que tu código se ejecute. La misma función protege cada evento asíncrono que Lingo.dev te envía: finalizaciones de localización, finalizaciones de aprovisionamiento, cada tipo, cada superficie del producto.

javascript
import crypto from "node:crypto";

function verifyWebhook(payload, headers, secret) {
  const msgId = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  const signatures = headers["webhook-signature"];

  // Reject timestamps outside a tolerance window (replay prevention)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
    throw new Error("Webhook timestamp too old");
  }

  // Recompute the expected signature over id.timestamp.body
  const content = `${msgId}.${timestamp}.${payload}`;
  const secretBytes = Buffer.from(secret.replace("whsec_", ""), "base64");
  const expected = crypto
    .createHmac("sha256", secretBytes)
    .update(content)
    .digest("base64");

  // A delivery may carry several signatures; accept if any matches
  for (const sig of signatures.split(" ")) {
    const [version, value] = sig.split(",", 2);
    if (version === "v1" && crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(value)
    )) {
      return JSON.parse(payload);
    }
  }

  throw new Error("Invalid webhook signature");
}

Compara con una función de tiempo constante —crypto.timingSafeEqual, hmac.compare_digest—, no con ==. Una comparación de cadenas normal devuelve el resultado en cuanto dos bytes difieren, y esa diferencia de tiempo basta para filtrar la firma un byte a la vez. La comparación en tiempo constante cierra ese canal lateral, por eso ambos ejemplos anteriores la usan.

Por qué importa el cuerpo sin procesar#

Fíjate que ambas funciones firman payload: el cuerpo exactamente como llegó por la red, antes de cualquier parseo de JSON. Este es el detalle que con más frecuencia hace tropezar una integración que, por lo demás, está bien hecha, y vale la pena decirlo claro justo en el punto donde suele fallar:

La firma se calcula sobre los bytes exactos que Lingo.dev envió. En cuanto parseas el cuerpo como objeto y lo vuelves a serializar, puedes cambiar espacios en blanco, el orden de las claves o el formato numérico, y el HMAC recalculado ya no coincide con una firma tomada sobre los bytes originales. El payload es idéntico en significado; los bytes no.

Verifica contra el cuerpo sin procesar, no contra el objeto parseado

Captura el cuerpo sin procesar de la solicitud antes de que tu framework lo procese y pasa esos bytes al verificador. En Express, usa express.raw({ type: "application/json" }) en la ruta del webhook. En FastAPI, lee await request.body(). Parsea solo después de que la firma sea válida: primero verifica, después procesa.

Rechazar reenvíos#

Un payload firmado válido que un atacante haya capturado puede reenviarse tal cual: la firma sigue siendo válida, porque nada cambia entre la primera entrega y una copia enviada una hora después. El encabezado webhook-timestamp es lo que acota esa ventana: registra cuándo se envió la entrega, para que tu verificador pueda rechazar cualquier cosa más antigua que la tolerancia que elijas. Los ejemplos anteriores usan cinco minutos.

Una comprobación de timestamp frena un reenvío tardío: una copia capturada y reenviada después de tu tolerancia falla la prueba de vigencia y nunca llega a tu handler.

Responder rápido, procesar después#

Una vez verificada la entrega, devuelve 200 de inmediato y luego haz el trabajo real —escrituras en base de datos, llamadas a servicios downstream, invalidación de caché— después de responder.

javascript
app.post(
  "/webhooks/lingo",
  express.raw({ type: "application/json" }),
  (req, res) => {
    let event;
    try {
      event = verifyWebhook(req.body.toString(), req.headers, process.env.LINGO_WEBHOOK_SECRET);
    } catch {
      return res.status(401).send("invalid signature");
    }

    // Acknowledge first, process after - never block the response on slow work
    res.status(200).send("ok");
    void handleEvent(event);
  }
);

La razón es mecánica, no estilística. Un handler lento mantiene abierta la conexión HTTP; si tarda lo suficiente como para agotar el timeout, la entrega cuenta como fallida y se reintenta, así que hacer trabajo pesado dentro de la ruta de respuesta convierte un evento en varios. Confirma rápido, delega el trabajo a una cola o a una tarea en segundo plano, y un solo evento seguirá siendo un solo evento. Las formas de payload sobre las que haces switch dentro de handleEvent viven con cada producto: callbacks de localización y callbacks de aprovisionamiento.

Reintentos y backoff#

A veces tu endpoint no va a estar disponible: un deploy, un timeout, un bad gateway. Lingo.dev no descarta el evento cuando eso pasa.

Si tu endpoint devuelve un estado distinto de 2xx o no se puede alcanzar, la entrega se reintenta con backoff exponencial a partir de 30 segundos, hasta 5 intentos. Después del quinto intento, la entrega se marca como fallida y Lingo.dev deja de intentarlo, pero el resultado no se pierde. Sigue disponible desde el registro del trabajo, así que un periodo de inactividad te cuesta un callback, nunca el resultado en sí. Ese registro del trabajo es tu red de seguridad: usa el webhook para el caso común y trata el trabajo almacenado como la fuente de verdad a la que siempre puedes volver. Para un trabajo de traducción, consúltalo directamente.

Siguientes pasos#

Autenticación
Cómo las claves de API autentican cada solicitud a la API
Webhooks de localización
Las formas de payload de translation.completed y translation.failed
Webhooks de aprovisionamiento
Payloads de callback para trabajos de aprovisionamiento de motor de IA

¿Te resultó útil esta página?

Max PrilutskiyMax Prilutskiy·Actualizado hace 12 días·7 min de lectura