|
Documentação
Agende uma demoPlataforma
PlataformaMCPCLIAPI
Workflows
GuiasChangelog

Boas-vindas

  • Visão geral
  • Autenticação
  • Erros e códigos de status
  • Assinaturas de webhook

Localização

  • Visão geral
  • Criar jobs
  • Bloquear chaves não traduzíveis
  • Acompanhar um grupo de jobs
  • Buscar um job
  • Listar jobs
  • Entrega de webhook
  • Progresso em tempo real (WebSocket)

Pipeline

  • Visão geral
  • Edição por IA antes da localização
  • Revisão humana
  • avaliação por IA (pós-edição)
  • Reescreva para soar natural
  • Verificação por retradução
  • Configure o pipeline
  • Acompanhe execuções do pipeline

Provisionamento

  • Visão geral
  • Criar um job de provisionamento
  • Tipos de fonte
  • O que a IA extrai
  • Entrega de webhook
  • Progresso em tempo real (WebSocket)

Síncrono

  • Localize
  • Recognize

Gerenciamento do engine

  • Sugestões do engine

Assinaturas de webhook

Quando um job assíncrono termina, Lingo.dev não obriga você a ficar consultando o status. Ela chama você de volta: um POST para o endpoint HTTPS que você registrou como seu callbackUrl. Essa é a conveniência. Mas também é a exposição: uma URL pública aceita tudo o que a internet mandar, e qualquer pessoa que descubra a sua pode POST um evento forjado de "job concluído" para o seu handler.

Por isso, a regra para todo callback é a mesma: verifique antes de confiar. Cada entrega vem com uma assinatura calculada a partir de um segredo que só você e a Lingo.dev possuem. Recalcule do seu lado, compare em tempo constante, e um payload forjado nunca chega à sua lógica de negócio. Esta página concentra todo esse mecanismo. Os callbacks de localização e provisionamento usam exatamente o mesmo processo — aquelas páginas cobrem apenas seus próprios formatos de payload e apontam de volta para esta aqui quando o assunto é verificação.

Nesta página

  • Os três cabeçalhos
  • O segredo de assinatura
  • Como verificar uma assinatura
  • Por que o corpo bruto importa
  • Como rejeitar replays
  • Responda rápido, processe depois
  • Retries e backoff

Os três cabeçalhos#

A Lingo.dev segue a especificação Standard Webhooks, um esquema aberto adotado por vários provedores, então você verifica com base em um contrato publicado — e não em uma solução proprietária e fora do padrão. Cada entrega inclui três cabeçalhos:

CabeçalhoDescrição
webhook-idUm identificador único da entrega.
webhook-timestampTimestamp Unix, em segundos, de quando a entrega foi enviada.
webhook-signatureA própria assinatura: v1,{base64(HMAC-SHA256(secret, "{id}.{timestamp}.{body}"))}

O conteúdo assinado são três partes unidas por pontos — webhook-id, depois webhook-timestamp e, por fim, o corpo bruto da requisição — nessa ordem exata. Reconstrua essa string, aplique HMAC-SHA256 com o seu segredo, codifique o resultado em base64 e você terá o valor a ser comparado.

O cabeçalho webhook-signature pode trazer mais de uma assinatura separada por espaço, cada uma marcada com uma versão de esquema (v1,...). Um verificador aceita a entrega se qualquer assinatura corresponder. Percorrer a lista, em vez de ler um único valor, é a forma defensiva de interpretar esse cabeçalho — por isso os exemplos abaixo iteram por todas as assinaturas presentes.

O segredo de assinatura#

O segredo é gerado para a sua organização na primeira vez que você envia um job com um callbackUrl. Ele vem com o prefixo whsec_, seguido pelos bytes da chave codificados em base64:

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

Remova o prefixo whsec_ e decodifique o restante em base64 para recuperar os bytes brutos da chave — esse valor decodificado é a chave HMAC, não a string com prefixo. Assinar usando literalmente o texto whsec_... é o motivo mais comum para uma implementação aparentemente correta nunca funcionar, então decodifique primeiro.

Trate esse segredo como uma chave de API

O segredo de assinatura é o que separa um callback legítimo de um forjado. Mantenha-o no servidor, fora do controle de versão e longe de qualquer bundle de cliente. Quem tiver esse segredo pode assinar payloads que o seu handler vai aceitar. Consulte API Keys para ver como a Lingo.dev trata credenciais no escopo da organização.

Como verificar uma assinatura#

A verificação cabe em uma única função, que você posiciona uma vez antes do seu handler. Ela faz três coisas: recalcula a assinatura esperada a partir do corpo bruto, compara com o valor recebido usando uma checagem em tempo constante e rejeita tudo o que não corresponder antes de o seu código rodar. É a mesma função que protege todo evento assíncrono enviado pela Lingo.dev — conclusões de localização, conclusões de provisionamento, qualquer tipo, em qualquer superfície do produto.

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");
}

Compare com uma função de tempo constante — crypto.timingSafeEqual, hmac.compare_digest — e nunca com ==. Uma comparação simples de string retorna assim que encontra bytes diferentes, e essa diferença de tempo já basta para vazar a assinatura um byte por vez. A comparação em tempo constante fecha esse canal lateral, por isso ambos os exemplos acima a utilizam.

Por que o corpo bruto importa#

Repare que ambas as funções assinam payload — o corpo exatamente como chegou pela rede, antes de qualquer parsing de JSON. Esse é o detalhe que mais costuma derrubar uma integração que, fora isso, estaria correta — e vale a pena deixar isso explícito justamente no ponto em que ele costuma causar problema:

A assinatura é calculada sobre os bytes exatos que a Lingo.dev enviou. No momento em que você faz o parsing do corpo para um objeto e o serializa de novo, pode mudar espaços em branco, ordem das chaves ou formatação numérica — e o HMAC recalculado deixa de bater com a assinatura gerada sobre os bytes originais. O payload continua com o mesmo significado; os bytes, não.

Verifique com base no corpo bruto, não no objeto parseado

Capture o corpo bruto da requisição antes que o framework faça o parsing e passe esses bytes ao verificador. No Express, use express.raw({ type: "application/json" }) na rota do webhook. No FastAPI, leia await request.body(). Só faça o parsing depois que a assinatura for validada — primeiro verifique, depois parseie.

Como rejeitar replays#

Um payload válido e assinado, se capturado por um invasor, pode ser reenviado literalmente — a assinatura continua válida, porque nada nela muda entre a entrega original e uma cópia enviada uma hora depois. O cabeçalho webhook-timestamp é o que limita essa janela: ele registra quando a entrega foi enviada, para que o seu verificador possa rejeitar qualquer coisa mais antiga do que a tolerância que você definir. Nos exemplos acima, ela é de cinco minutos.

A checagem de timestamp bloqueia replays antigos: uma cópia capturada e reenviada depois do limite de tolerância falha no teste de validade e nunca chega ao seu handler.

Responda rápido, processe depois#

Depois que uma entrega for verificada, retorne 200 imediatamente e só então faça o trabalho de verdade — gravações no banco, chamadas downstream, invalidação de cache — depois da resposta.

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);
  }
);

O motivo é mecânico, não estilístico. Um handler lento mantém a conexão HTTP aberta; se demorar o suficiente para expirar, a entrega conta como falha e entra em retry — então colocar trabalho pesado no caminho da resposta transforma um evento em vários. Confirme rápido, envie o trabalho para uma fila ou tarefa em segundo plano, e um único evento continua sendo um único evento. Os formatos de payload sobre os quais você faz o switch dentro de handleEvent ficam nas páginas de cada produto: callbacks de localização e callbacks de provisionamento.

Retries e backoff#

Seu endpoint vai ficar indisponível às vezes — seja por um deploy, timeout ou bad gateway. Quando isso acontecer, a Lingo.dev não descarta o evento.

Se o seu endpoint retornar um status diferente de 2xx ou estiver inacessível, a entrega entra em retry com backoff exponencial a partir de 30 segundos, até 5 tentativas. Depois da quinta tentativa, a entrega é marcada como falha e a Lingo.dev para de tentar — mas o resultado não se perde. Ele continua disponível no registro do job, então um período de indisponibilidade custa um callback, nunca o resultado em si. Esse registro do job é o seu plano B: use o webhook para o caso mais comum e trate o job armazenado como a fonte da verdade à qual você sempre pode recorrer. Para um job de tradução, consulte-o diretamente.

Próximos passos#

Autenticação
Como as chaves de API autenticam todas as requisições à API
Webhooks de localização
Os formatos de payload de translation.completed e translation.failed
Webhooks de provisionamento
Payloads de callback para jobs de provisionamento de AI engine

Esta página foi útil?

Max PrilutskiyMax Prilutskiy·Atualizado há 12 dias·7 min de leitura