HubSpot: como funciona a entrega de webhooks

Um prazo de 5 segundos cobre um lote de até 100 notificações, e o eventId é documentado como sem garantia de ser único. A assinatura v3 e um verificador Node.

Comportamento de entrega

Lido na documentação do próprio provedor, verificado pela última vez em 2026-08-06. Onde o provedor não informa um número, esta tabela também não traz nenhum.

Prazo de resposta5 s

Vale para o lote inteiro, que pode trazer até 100 notificações

Verifique se seu handler responde a tempo

Novas tentativasAté 10 tentativas distribuídas ao longo de 24 horas
Desistenão publicado pelo provedor
Fontehttps://developers.hubspot.com/docs/api-reference/latest/webhooks/guide

Assinatura

Verificada com um verificador funcional, comprovado por uma suíte de testes, e não lida na documentação. Última verificação em 2026-08-20.

AlgoritmoHMAC-SHA256
Payload assinadoPOST{decodedUrl}{body}{timestamp}
Codificaçãobase64
Cabeçalhox-hubspot-signature-v3
Tolerância5 min

Verifique a assinatura

O corpo bruto da requisição, byte a byte, antes de qualquer parsing de JSON. Todos os esquemas desta página quebram no momento em que um framework serializa o payload de novo.

const crypto = require("node:crypto");

// v3: base64(HMAC-SHA256(secret, "POST" + url + body + timestamp))
const ts = req.headers["x-hubspot-request-timestamp"];
if (Math.abs(Date.now() - Number(ts)) > 300_000) throw new Error("stale");

const expected = crypto
  .createHmac("sha256", clientSecret) // the app's client secret
  .update("POST" + fullUrl + rawBody + ts)
  .digest("base64");
const received = Buffer.from(req.headers["x-hubspot-signature-v3"] ?? "");
const wanted = Buffer.from(expected);
// timingSafeEqual throws on a length mismatch, so check that first.
const valid =
  received.length === wanted.length && crypto.timingSafeEqual(received, wanted);

O fullUrl é a URL pública exata que a HubSpot chamou, com a query string, depois da decodificação de URL. Atrás de um proxy que reescreve o cabeçalho Host, reconstrua-a a partir dos cabeçalhos encaminhados, ou a assinatura nunca vai bater.

Armadilhas

O prazo de 5 segundos é por requisição, e uma requisição pode trazer um lote de até 100 notificações. Processá-las dentro da requisição significa que seu prazo real é de 50 ms para cada uma.

A HubSpot documenta que seu eventId não tem garantia de ser único e não promete nenhuma ordem, então o hábito, vindo da Stripe, de deduplicar pelo id do evento não vale aqui.

Leia também

O AnyHook fica entre HubSpot e seus endpoints: responde em cerca de um segundo, tenta de novo segundo um cronograma próprio quando seu servidor está fora do ar e guarda cada evento disponível para replay. Troque uma URL e mantenha seu código; para aceitar novas tentativas e replays, verifique o cabeçalho AnyHook-Signature, porque a assinatura original do remetente (HubSpot) expira após 5 min.

Como funciona →