|
Documentação
Marcar uma demonstraçãoPlataforma
PlataformaMCPCLIAPI
Workflows
GuiasChangelog

Boas-vindas

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

Localização

  • Visão geral
  • Criar jobs
  • Bloquear chaves não traduzíveis
  • Acompanhar um grupo de jobs
  • Obter um trabalho
  • Listar tarefas
  • Entrega de webhooks
  • Progresso em tempo real (WebSocket)

Pipeline

  • Visão geral
  • Edição por IA pré-localização
  • Revisão humana
  • avaliação por IA (post-edit)
  • Reformular para soar natural
  • Verificação por retrotradução
  • Configurar o pipeline
  • Observar execuções do pipeline

Provisionamento

  • Visão geral
  • Criar uma tarefa de aprovisionamento
  • Tipos de fonte
  • O que a IA extrai
  • Entrega de webhook
  • Progresso em tempo real (WebSocket)

Síncrono

  • Localize
  • Recognize

Gestão do motor

  • Sugestões do motor

Assinaturas de webhook

Quando uma tarefa assíncrona termina, a Lingo.dev não o obriga a fazer polling. Em vez disso, faz-lhe um callback: um POST para o endpoint HTTPS que registou como callbackUrl. Essa é a conveniência. Mas também é a exposição — um URL público aceita tudo o que a internet lhe envia, e qualquer pessoa que descubra o seu pode POST um evento "tarefa concluída" forjado para o seu handler.

Por isso, a regra para todos os callbacks é a mesma: verifique antes de confiar. Cada entrega inclui uma assinatura calculada a partir de um segredo que só você e a Lingo.dev conhecem. Recalcule-a do seu lado, compare-a em tempo constante, e uma payload forjada nunca chegará à sua lógica de negócio. Esta página é a referência única para esse mecanismo. Tanto os callbacks de localização como os de aprovisionamento usam-no sem alterações — essas páginas explicam os respetivos formatos de payload e remetem para esta no que toca à verificação.

Nesta página

  • Os três cabeçalhos
  • O segredo de assinatura
  • Verificar uma assinatura
  • Porque é que o corpo em bruto importa
  • Rejeitar repetições
  • Responder rapidamente, processar depois
  • Novas tentativas e backoff

Os três cabeçalhos#

A Lingo.dev segue a especificação Standard Webhooks, um esquema aberto adotado por vários fornecedores, pelo que está a verificar com base num contrato publicado em vez de uma implementação proprietária e idiossincrática. 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 do pedido em bruto — exatamente por esta ordem. Reconstrua essa string, aplique-lhe HMAC-SHA256 com o seu segredo, codifique o resultado em base64 e terá o valor a comparar.

O cabeçalho webhook-signature pode conter mais do que uma assinatura, separadas por espaços, cada uma identificada com uma versão do 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 analisar este 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 submete uma tarefa com um callbackUrl. Tem o prefixo whsec_, seguido de bytes de chave codificados em base64:

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

Remova o prefixo whsec_ e descodifique em base64 o restante para recuperar os bytes em bruto da chave — esse valor descodificado é a chave HMAC, não a string com o prefixo. Assinar com o texto literal whsec_... é a razão mais comum para uma implementação aparentemente correta nunca corresponder, por isso descodifique primeiro.

Trate o segredo como uma chave de API

O segredo de assinatura é o que distingue um callback real de um callback forjado. Mantenha-o no servidor, fora do controlo de versões e fora de qualquer bundle de cliente. Qualquer pessoa que o tenha pode assinar payloads que o seu handler aceitará. Consulte API Keys para perceber como a Lingo.dev gere credenciais ao nível da organização.

Verificar uma assinatura#

A verificação resume-se a uma única função, colocada uma vez à frente do seu handler. Faz três coisas: recalcula a assinatura esperada a partir do corpo em bruto, compara-a com o que chegou usando uma verificação em tempo constante e rejeita tudo o que não corresponder antes de o seu código correr. A mesma função protege todos os eventos assíncronos que a Lingo.dev lhe envia — conclusões de localização, conclusões de aprovisionamento, todos os tipos, em todas as superfícies 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 em tempo constante — crypto.timingSafeEqual, hmac.compare_digest — e não com ==. Uma comparação simples de strings devolve assim que dois bytes diferem, e essa diferença de tempo basta para revelar a assinatura um byte de cada vez. A comparação em tempo constante fecha esse canal lateral, e é por isso que ambos os exemplos acima a usam.

Porque é que o corpo em bruto importa#

Repare que ambas as funções assinam payload — o corpo exatamente como chegou pela rede, antes de qualquer parsing de JSON. Este é o detalhe que mais frequentemente faz tropeçar uma integração que, de resto, está correta, e vale a pena dizê-lo de forma clara no ponto em que costuma falhar:

A assinatura é calculada sobre os bytes exatos que a Lingo.dev enviou. No momento em que faz parsing do corpo para um objeto e o serializa de novo, pode alterar espaços em branco, a ordem das chaves ou a formatação numérica — e o HMAC recalculado deixa de corresponder a uma assinatura obtida sobre os bytes originais. A payload é idêntica no significado; os bytes não.

Verifique com base no corpo em bruto, não no objeto analisado

Capture o corpo do pedido em bruto antes de o seu framework o analisar e passe esses bytes ao verificador. Em Express, use express.raw({ type: "application/json" }) na rota do webhook. Em FastAPI, leia await request.body(). Só faça o parsing depois de a assinatura ser validada — primeiro verificação, depois parsing.

Rejeitar repetições#

Uma payload válida e assinada que um atacante tenha capturado pode ser repetida palavra por palavra — a assinatura continua válida, porque nada nela muda entre a primeira entrega e uma cópia enviada uma hora mais tarde. O cabeçalho webhook-timestamp é o que limita essa janela: regista quando a entrega foi enviada, para que o seu verificador possa rejeitar qualquer coisa mais antiga do que a tolerância que definir. Os exemplos acima usam cinco minutos.

Uma verificação do timestamp bloqueia uma repetição antiga: uma cópia capturada e reenviada depois da sua tolerância falha o teste de frescura e nunca chega ao seu handler.

Responder rapidamente, processar depois#

Assim que uma entrega for verificada, devolva 200 de imediato e só depois trate do trabalho real — escritas na base de dados, chamadas a serviços a jusante, invalidação de cache — já depois 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);
  }
);

A razão é mecânica, não estilística. Um handler lento mantém a ligação HTTP aberta; se demorar o suficiente para atingir o timeout, a entrega conta como falhada e volta a ser tentada — por isso, trabalho pesado dentro do fluxo de resposta transforma um evento em vários. Acuse a receção rapidamente, entregue o trabalho a uma fila ou a uma tarefa em segundo plano, e um único evento continuará a ser um único evento. Os formatos de payload sobre os quais faz switch dentro de handleEvent vivem em cada produto: callbacks de localização e callbacks de aprovisionamento.

Novas tentativas e backoff#

Por vezes, o seu endpoint vai estar indisponível — um deploy, um timeout, um bad gateway. A Lingo.dev não descarta o evento quando isso acontece.

Se o seu endpoint devolver um estado não 2xx ou estiver inacessível, a entrega volta a ser tentada com backoff exponencial a partir de 30 segundos, até 5 tentativas. Após a quinta tentativa, a entrega é marcada como falhada e a Lingo.dev deixa de tentar — mas o resultado não se perde. Continua disponível no registo da tarefa, pelo que um período de indisponibilidade lhe custa um callback, nunca o próprio resultado. Esse registo da tarefa é a sua salvaguarda: crie o webhook para o caso comum e trate a tarefa armazenada como a fonte de verdade a que pode sempre recorrer. Para uma tarefa de tradução, faça polling diretamente.

Passos seguintes#

Autenticação
Como as chaves de API autenticam todos os pedidos à API
Webhooks de localização
Os formatos de payload de translation.completed e translation.failed
Webhooks de aprovisionamento
Payloads de callback para tarefas de aprovisionamento do motor de IA

Esta página foi útil?

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