Ir para o conteúdo

Entrega e autenticação

Requisição HTTP

A Moonu envia o corpo do evento por HTTP POST para o endpoint configurado.

Item Valor
Método POST
Corpo JSON UTF-8
Sucesso Qualquer resposta 2xx
Redirecionamentos Não são seguidos
Timeout de conexão 3 segundos
Timeout da requisição 10 segundos

Headers enviados

Header Descrição
Content-Type application/json
User-Agent Moonu-Webhooks/1.0
X-Moonu-Event Tipo do evento, como call.completed
X-Moonu-Event-Id Mesmo identificador presente em eventId no corpo
X-Moonu-Timestamp Unix timestamp em segundos usado na assinatura
X-Moonu-Signature Assinatura HMAC, prefixada por v1=

Validação da assinatura

A assinatura é HMAC-SHA256 sobre a sequência exata abaixo, usando o segredo do endpoint:

<X-Moonu-Timestamp>.<corpo HTTP bruto>

O valor do header é v1=<hex-hmac-sha256>. Valide o corpo bruto, antes de reformatar ou desserializar/re-serializar o JSON, e compare a assinatura em tempo constante.

Exemplo em Node.js. rawBody deve ser o corpo recebido antes do parse de JSON:

import crypto from "node:crypto";

function isValidMoonuSignature({ secret, timestamp, rawBody, signature }) {
  const signed = `${timestamp}.${rawBody}`;
  const expected = `v1=${crypto
    .createHmac("sha256", secret)
    .update(signed, "utf8")
    .digest("hex")}`;

  const actual = Buffer.from(signature || "");
  const expectedBuffer = Buffer.from(expected);
  return actual.length === expectedBuffer.length && crypto.timingSafeEqual(expectedBuffer, actual);
}

Valide também o timestamp

A assinatura confirma a origem do corpo, mas sua aplicação deve rejeitar timestamps muito antigos conforme a janela de tolerância definida pela sua própria política de segurança.

Retentativas e idempotência

Falhas de rede e respostas 408, 425, 429 ou 5xx podem ser tentadas novamente. Os intervalos são: 1 minuto, 5 minutos, 30 minutos, 2 horas, 8 horas, 24 horas e 37 horas.

Use eventId como chave de deduplicação. O mesmo evento mantém o mesmo eventId nas tentativas de entrega. Em geral, o corpo é preservado; no evento recording.available, a URL temporária e expiresAt podem ser renovados em uma nova tentativa. Não assuma entrega exatamente uma vez nem ordem entre eventos.

Fluxo recomendado

Valide a assinatura do corpo bruto, registre eventId de forma idempotente, processe a chamada e responda 2xx. Se o mesmo eventId chegar novamente, responda 2xx sem repetir o efeito de negócio.