RelyaWhatsApp para negóciosTestar grátis
Arquitetura · Eventos

Webhooks que não somem nem duplicam.

Valide a origem, responda rápido, repita com segurança e trate cada evento uma única vez.

Por Equipe RelyaRevisado em 14/07/2026Leitura: 10 minutos
Fluxo de webhooks assinados entre a Relya e o sistema do cliente
Entrega confiável exige assinatura, resposta rápida, retry e deduplicação.

Webhook confiável não é “uma URL que recebe JSON”. É um contrato com autenticação, identificador estável, resposta rápida, novas tentativas e uma regra explícita para não processar o mesmo evento duas vezes.

O contrato do webhook

A Relya envia eventos de conexão, mensagem recebida, envio e mudança de estado. O corpo inclui tipo, instância, horário e dados. Os cabeçalhos carregam a prova de origem e o identificador da entrega:

  • x-relya-delivery-id: identificador estável;
  • x-relya-timestamp: horário Unix em segundos;
  • x-relya-event-type: categoria do evento;
  • x-relya-signature: HMAC-SHA256 no formato v1=...;
  • idempotency-key: o mesmo identificador da entrega.
{
  "event": "messages_update",
  "instanceId": "instancia-id",
  "timestamp": "2026-07-14T12:00:00.000Z",
  "data": {
    "status": "delivered"
  }
}

Validar a assinatura HMAC

Não confie apenas em IP ou em um campo dentro do JSON. Calcule o HMAC usando o segredo da instância e o corpo bruto recebido. A entrada assinada é:

TIMESTAMP.ID_DA_ENTREGA.CORPO_BRUTO

Se o framework converter o JSON e depois serializar novamente, espaços e ordem de campos podem mudar. Capture os bytes brutos antes do parser.

import crypto from "node:crypto";

function webhookValido({ segredo, timestamp, deliveryId, corpoBruto, assinatura }) {
  const agora = Math.floor(Date.now() / 1000);
  if (Math.abs(agora - Number(timestamp)) > 300) return false;

  const digest = crypto
    .createHmac("sha256", segredo)
    .update(`${timestamp}.${deliveryId}.${corpoBruto}`)
    .digest("hex");

  const esperado = Buffer.from(`v1=${digest}`);
  const recebido = Buffer.from(assinatura || "");
  return esperado.length === recebido.length &&
    crypto.timingSafeEqual(esperado, recebido);
}

A comparação em tempo constante reduz vazamento por diferença de tempo. Rejeite timestamps com diferença superior a cinco minutos para limitar replay. Ao trocar o segredo no painel, atualize o receptor de forma coordenada.

Responda rápido e processe depois

Seu endpoint deve validar, registrar a entrada e responder 2xx. Atualizar CRM, chamar IA, baixar mídia ou enviar e-mail deve ocorrer em uma fila interna. Se você fizer tudo antes de responder, um processamento lento pode gerar timeout e uma nova tentativa desnecessária.

  1. Receba o corpo bruto.
  2. Valide timestamp e assinatura.
  3. Inicie uma transação curta.
  4. Insira o delivery-id com restrição de unicidade.
  5. Grave o evento na fila interna.
  6. Confirme a transação.
  7. Responda 204 ou outro 2xx.
  8. Um worker processa o evento depois.
O momento certo do 2xx

Responda depois que o evento estiver persistido, não logo ao abrir a conexão. Se o processo cair entre a resposta e a gravação, o provedor acredita que entregou e você perde o evento.

Como funcionam as novas tentativas

Na configuração padrão da Relya são até seis tentativas totais. A primeira é imediata. Erro de rede, timeout ou resposta fora de 200–299 agenda novas tentativas.

TentativaEsperaMesmo delivery ID?
1ImediataSim
25 segundosSim
310 segundosSim
420 segundosSim
540 segundosSim
680 segundosSim

Depois da falha definitiva, o painel permite replay operacional. O identificador permanece estável. Isso é intencional: o consumidor pode reconhecer que se trata do mesmo evento.

Idempotência no consumidor

Imagine que seu servidor grava o pedido e começa a responder, mas a conexão cai antes de o provedor receber o 204. A Relya repete. Sem deduplicação, o CRM cria dois tickets ou a automação envia duas respostas.

A solução mais segura é uma coluna única para delivery_id. A inserção e a entrada na fila devem ocorrer na mesma transação. Se a restrição de unicidade falhar, responda 2xx: o evento já foi aceito anteriormente.

CREATE TABLE webhook_inbox (
  delivery_id TEXT PRIMARY KEY,
  event_type TEXT NOT NULL,
  raw_body TEXT NOT NULL,
  received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  processed_at TIMESTAMP NULL
);

Isso implementa entrega “pelo menos uma vez” no transporte e efeito “uma vez” no seu domínio, dentro do limite da transação local.

Teste de falha antes do cliente real

  1. Cadastre uma URL HTTPS pública.
  2. Gere um evento e confirme assinatura e persistência.
  3. Derrube temporariamente o endpoint.
  4. Confirme tentativas e atrasos no painel.
  5. Restaure e verifique a entrega.
  6. Reenvie o mesmo payload e delivery ID.
  7. Confirme que seu sistema não repete o efeito.
  8. Teste assinatura inválida e timestamp antigo.
  9. Force processamento lento e confirme que a resposta HTTP continua rápida.
  10. Simule indisponibilidade da fila interna e não responda 2xx antes da gravação.

Esse teste vale mais que um “webhook recebido com sucesso” em ambiente perfeito. Para o fluxo de envio, leia o guia da API. O contrato completo está na documentação pública.

Escopo técnico

Cabeçalhos, assinatura e política de repetição descritos correspondem à Relya em 14/07/2026. Sempre valide a documentação do seu provedor: nomes e prazos não são universais.

Teste webhooks com falha de verdade.

Veja tentativa, próximo horário, último erro e replay no painel.

Criar uma instância →