Voltar pro blog json-p.org

PilarRecuperação de vendas automática Publicado5 de agosto de 2026 AutorElton

Webhook Hotmart: hottok, eventos e o que fazer com cada um

O que é o hottok, onde ele chega (header X-HOTMART-HOTTOK), como validar sem perder evento, e a régua certa pra cada evento do webhook Hotmart: aprovado, PIX pendente, recusa, reembolso e chargeback.

Cliente gerou o PIX às 14h. Não pagou. Ninguém falou com ele. Às 16h o código expirou e a venda evaporou. Os eventos do webhook Hotmart contaram essa história inteira em tempo real, e do seu lado ninguém estava ouvindo.

Cartão recusado por limite. Do lado da Hotmart isso é um estado bem definido. Do seu lado, é silêncio. O cara ia comprar, o banco travou, e ninguém mandou "tenta de novo com outro cartão ou PIX". Perdeu.

Pior: teve gente que pediu reembolso três semanas atrás e você continua mandando conteúdo de boas-vindas pra ela, porque a sua automação nunca ouviu falar de reembolso. Cada um desses momentos é um evento que a Hotmart te avisou. Você só não estava escutando o evento certo.

Só que antes de reagir a qualquer evento tem uma pergunta que vem primeiro: esse POST veio mesmo da Hotmart? Quem responde é o hottok, o token que chega no header X-HOTMART-HOTTOK. É a parte que mais gente procura e a que mais gente implementa errado, então ela vem logo depois da resposta curta, antes do mapa de eventos.

A resposta curta

A Hotmart dispara um POST (payload versão 2.0.0) pra uma URL sua toda vez que o status de uma transação muda. O corpo é um JSON com um campo event (o nome do evento, tipo PURCHASE_APPROVED) e um campo data com os detalhes da compra. No header vem o X-HOTMART-HOTTOK, um token fixo da sua conta: se ele não for igual ao que você tem guardado, o POST não é confiável e não pode disparar nada. Reagir bem é fazer quatro coisas, nessa ordem: validar o hottok, não processar o mesmo evento duas vezes, identificar o evento certo e disparar a régua daquele estado. O resto do texto é o detalhe de cada uma.

Hottok da Hotmart: o que é, onde fica e como validar

O hottok é um token de verificação. Cada conta da Hotmart tem o seu, único, e a Hotmart manda esse mesmo valor em toda notificação de webhook daquela conta. A lógica é de segredo compartilhado: só você e a Hotmart conhecem o valor, então um POST que chega com ele foi mandado por quem conhece o segredo. Ele não é assinatura do corpo (não é HMAC), não é o client id ou client secret da API de vendas, e não muda de um evento pro outro.

A validação em si é uma comparação de string. O que derruba gente é o entorno dela. Em Node com Express:

const crypto = require('crypto')

// um ou mais hottoks válidos, separados por vírgula (um por conta Hotmart)
const HOTTOKS = (process.env.HOTMART_HOTTOKS || '')
  .split(',').map(t => t.trim()).filter(Boolean)

function hottokValido(recebido) {
  if (!recebido) return false
  const a = Buffer.from(recebido.trim())
  return HOTTOKS.some(t => {
    const b = Buffer.from(t)
    // timingSafeEqual lança erro se os tamanhos forem diferentes
    return a.length === b.length && crypto.timingSafeEqual(a, b)
  })
}

app.post('/webhook/hotmart', express.json(), async (req, res) => {
  const hottok = req.get('X-HOTMART-HOTTOK')  // req.get não liga pra maiúscula

  if (!hottokValido(hottok)) {
    await quarentena.salvar(req.body)          // guarda, mas NÃO age
    await alerta('webhook Hotmart: hottok ausente ou divergente')
    return res.sendStatus(200)
  }

  await fila.gravar(req.body)                  // grava rápido...
  res.sendStatus(200)                          // ...responde, e processa depois
})

Quatro detalhes que esse trecho resolve e que eu já vi quebrar em produção:

E se o hottok não bater?

Primeiro descubra qual dos três casos é, porque cada um tem causa diferente:

Depois decida o código de resposta pro POST rejeitado. Tem dois jeitos defensáveis, e eles têm consequências diferentes:

A regra que não muda em nenhum dos dois: evento sem hottok válido nunca libera acesso e nunca dispara mensagem. Quarentena é depósito, não fila de processamento. E lembre que o hottok é um segredo estático: se ele vazar (log com headers crus, print no grupo de suporte, commit no repositório), qualquer um forja um PURCHASE_APPROVED. Pra produto caro, vale uma segunda trava antes de liberar acesso: consultar aquela transação na API de vendas da Hotmart e conferir se o status bate com o que o webhook disse.

Webhook Hotmart eventos: o mapa de estados

Com o hottok validado, vem o formato. Na versão 2.0.0 do webhook, todo evento chega com o mesmo envelope. Muda só o que vem dentro de data. Um payload de compra aprovada, mascarado, é mais ou menos assim:

{
  "id": "e8f9a0b1-2c3d-4e5f-6789-0abcdef12345",
  "creation_date": 1737120000000,
  "event": "PURCHASE_APPROVED",
  "version": "2.0.0",
  "data": {
    "product": {
      "id": 0000000,
      "name": "Seu Produto"
    },
    "buyer": {
      "email": "c****@email.com",
      "name": "Nome Mascarado",
      "checkout_phone": "+55XXXXXXXXXXX"
    },
    "purchase": {
      "transaction": "HP00000000000000",
      "status": "APPROVED",
      "approved_date": 1737120000000,
      "payment": {
        "type": "PIX",
        "installments_number": 1
      },
      "price": {
        "value": 197.00,
        "currency_value": "BRL"
      }
    },
    "affiliates": [],
    "commissions": []
  }
}

Os campos que você vai usar sempre: event (o que aconteceu), data.purchase.transaction (o ID único da compra, sua chave), data.purchase.status (o estado atual da transação) e data.buyer (pra quem falar). O id lá em cima é o ID do evento em si, e ele importa pra idempotência, que a gente vê mais pra frente.

Um detalhe que aparece muito em busca: o "currency_value": "BRL" dentro de price é a moeda daquele valor, e o value vem como número decimal na unidade da moeda (197.00 é R$ 197,00). Se você vende pra fora ou tem oferta em dólar, agregue faturamento sempre olhando a moeda junto. Somar value de BRL com USD é o jeito mais rápido de o seu dashboard mentir.

Os eventos, um por um, e o que cada um quer dizer

Esses são os eventos de compra do webhook 2.0.0. Não invente nome, use exatamente estes:

E os de assinatura, se você vende recorrência: SUBSCRIPTION_CANCELLATION (assinatura cancelada), SWITCH_PLAN (troca de plano) e UPDATE_SUBSCRIPTION_CHARGE_DATE (mudança na data de cobrança). Tem ainda os de área de membros (CLUB_FIRST_ACCESS, CLUB_MODULE_COMPLETED), que servem mais pra engajamento do que pra recuperar venda.

Status da transação x nome do evento (não confunda)

Tem gente que roteia a régua pelo event e tem gente que roteia pelo data.purchase.status. Os dois existem e não são a mesma coisa. O event é a notificação ("mudou pra isso agora"). O status é o estado atual da transação. Os valores possíveis de status, na doc atual da Hotmart, são:

APPROVED            BLOCKED            CANCELLED
CHARGEBACK          COMPLETE           EXPIRED
NO_FUNDS            OVERDUE            PARTIALLY_REFUNDED
PRE_ORDER           PRINTED_BILLET     PROCESSING_TRANSACTION
PROTESTED           REFUNDED           STARTED
UNDER_ANALISYS      WAITING_PAYMENT

Três detalhes que economizam horas: NO_FUNDS é a recusa por saldo/limite insuficiente (a "recusa de cartão" que você quer recuperar) e WAITING_PAYMENT é o PIX/boleto aguardando. Reembolso aparece de dois jeitos: o evento PURCHASE_REFUNDED e o status REFUNDED, ou PARTIALLY_REFUNDED quando foi parcial (trate os dois como "cortar régua de venda"). E sim, UNDER_ANALISYS está escrito assim mesmo na Hotmart, com o erro de grafia. Copie exatamente, não "corrija" pra ANALYSIS, senão seu if nunca casa.

Idempotência: o mesmo evento vai chegar mais de uma vez

Webhook não é entrega garantida uma vez só. É entrega garantida pelo menos uma vez. Se sua resposta demorou, se deu um 500, se a rede engasgou, a Hotmart reenvia (até 5 tentativas, pelo que ela mesma documenta), e você ainda pode reenviar na mão pelo histórico. Se você não se proteger, o cliente recebe a mesma mensagem de "seu PIX está esperando" três vezes, e você parece um robô quebrado.

A defesa é idempotência: registre o id do evento (ou a dupla transaction + event) numa tabela antes de agir. Se já viu aquele id, ignora e responde 200.

-- pseudo-lógica
INSERT INTO eventos_vistos (evento_id) VALUES ($id)
ON CONFLICT (evento_id) DO NOTHING;
-- se não inseriu nada, é repetido: para aqui.

Sem isso, todo pico de tráfego (lançamento, dia de PIX em massa) vira disparo duplicado. É o erro que mais queima lista.

O que dá errado na prática

Os furos que eu mais vejo em operação de infoproduto na Hotmart:

A régua por estado: o que disparar pra cada evento

Aqui é onde o técnico vira dinheiro. Cada evento pede uma reação comercial diferente. Um mapa que funciona:

Quando essa régua sai no WhatsApp, que é onde o público brasileiro de infoproduto responde, vale decidir com cuidado por onde disparar. As opções e os trade-offs (bloqueio, custo, número oficial) estão em Evolution API vs Cloud API. E se você quiser saber de qual anúncio veio a venda que você recuperou, cuidado com a atribuição no meio do caminho: o motivo de o crédito se perder está em por que o UTM se perde no WhatsApp.

Fechando

O webhook da Hotmart não é integração de dev. É a lista de estados do seu funil chegando em tempo real. A ordem que não falha é: valida o hottok (com a caixa certa do header e quarentena pro que não bate), grava o evento antes de agir, ignora repetido, e só então liga cada estado numa régua específica. Quem só ouve "aprovado" opera no escuro pra tudo que dá errado antes e depois da venda. O código é a parte fácil. A decisão de qual mensagem cada estado merece é o que separa uma operação que fatura de uma que só assiste o dashboard.

Fontes consultadas em 10/11/2026: documentação de webhook 2.0.0 em developers.hotmart.com (envio do hottok no header X-HOTMART-HOTTOK e recomendação de validar antes de processar); blog oficial da Hotmart sobre HotConnect e Webhook (hottok único por conta, 5 retentativas em caso de erro, reprocessamento pelo histórico); central de ajuda da Hotmart sobre configuração de Webhook/Postback (retentativa até 5 vezes, desativação automática da configuração quando a URL retorna erro, histórico de 60 dias, reenvio manual) e sobre integração com o Hotmart Send (hottok no corpo, no sentido inverso).