Webhook é uma requisição HTTP que um sistema envia para uma URL sua no instante em que um evento acontece, como um pagamento confirmado. Em vez de o seu código perguntar de minuto em minuto "já pagou?" (o chamado polling), o gateway avisa você, uma vez, com os dados do evento no corpo da requisição.
No HeroPay, os webhooks avisam o seu sistema sobre 9 eventos do ciclo de pagamento: pagamento confirmado, Pix gerado, boleto emitido, cartão recusado, estorno, chargeback e os três momentos da assinatura (ativação, atualização e cancelamento). Cada evento chega como POST JSON assinado com HMAC SHA-256 no header X-HeroPay-Signature e é reenviado automaticamente se o seu endpoint falhar. Webhooks não têm custo: estão incluídos em qualquer conta, inclusive no sandbox gratuito, e você só paga pela transação aprovada (Pix R$ 0, boleto R$ 0, cartão 3,49%, veja preços). É para quem constrói SaaS, app, curso ou loja e precisa liberar acesso, emitir nota ou avisar o time sem depender de alguém olhando o painel.
Criar conta sandbox Ler a documentação
O essencial em 60 segundos
- Webhook é um aviso por HTTP que o gateway envia para a sua URL quando algo acontece com um pagamento. Ele troca o polling (perguntar sem parar) por um evento que chega sozinho, na hora.
- O HeroPay tem 9 gatilhos de webhook:
spark_payment_confirmed,payment_pix_created,spark_payment_boleto_created,payment_credit_cart_refused,refunded,chargeback_request,subscription_activate,subscription_updateesubscription_cancel. - Todo webhook do HeroPay chega assinado: o header
X-HeroPay-Signaturetraz um HMAC SHA-256 do corpo bruto da requisição, calculado com um segredo que só você e o HeroPay conhecem. Assinatura que não bate, requisição descartada. - Se o seu endpoint não responder
2xx, o HeroPay reenvia o evento automaticamente. Por isso o mesmo evento pode chegar mais de uma vez: deduplique antes de agir. - Você cadastra um webhook por gatilho com
POST /webhook, ou pede o código em português a Claude, ChatGPT, Cursor, Lovable e outros, colando ollms.txtdo HeroPay na conversa. - O payload traz comprador, pagamento, oferta, produto, carrinho (com
cart.src, a origem da venda), assinatura, parcelamento e dados de Pix, boleto e cartão, incluindo o motivo da recusa. - Regra de ouro: libere acesso quando o webhook de pagamento confirmado chegar e o valor bater, nunca quando o comprador voltar para a página de obrigado.
O que é webhook e para que serve?
Pense no webhook como uma "API ao contrário". Numa API comum, o seu sistema chama o gateway e pede uma informação. No webhook, o gateway chama o seu sistema e entrega a informação sem ser perguntado. Por isso o nome vem de hook, gancho: você pendura uma URL num evento e, quando ele acontece, a requisição cai nela.
Em pagamentos, isso resolve um problema concreto: o dinheiro não confirma no momento em que o comprador clica em "pagar". O Pix confirma quando o banco do comprador liquida, o boleto confirma dias depois quando é compensado, e a cobrança mensal de uma assinatura acontece enquanto o seu cliente está dormindo. Sem webhook, o seu código precisaria consultar cada pedido pendente o tempo todo. Com webhook, ele só reage quando há o que reagir.
Webhook ou polling: qual usar?
| Webhook | Polling | |
|---|---|---|
| Quem inicia | O gateway, quando o evento acontece | O seu código, num intervalo fixo |
| Atraso até saber do pagamento | Segundos | Até o próximo ciclo da consulta |
| Requisições gastas | Uma por evento | Uma por pedido pendente, a cada ciclo |
| Pega boleto pago três dias depois | Sim, sozinho | Só se você lembrar de continuar consultando |
| Onde falha | Endpoint fora do ar (coberto pela retentativa) | Consulta que ninguém agendou |
A conta em escala: com 500 pedidos pendentes consultados a cada 30 segundos, o seu sistema faz 1,44 milhão de requisições por dia para descobrir algumas dezenas de pagamentos. Com webhook, são algumas dezenas de requisições, uma por evento. Polling ainda tem lugar, como rede de segurança para conciliar no fim do dia com GET /sales/unitary, mas não como fonte principal de verdade.
Mais definições do tema no glossário.
Como funciona o webhook de pagamento no HeroPay?
Do ponto de vista do dinheiro, o fluxo tem cinco passos:
- Você cadastra a URL. Um
POST /webhookpor gatilho, dizendo qual evento quer ouvir e para onde mandar. Guarde o segredo de assinatura do webhook em variável de ambiente. - O comprador paga. Pelo link de pagamento ou pelo checkout, com Pix, cartão em até 12x ou boleto.
- O HeroPay assina e envia. Calcula o HMAC SHA-256 do corpo com o seu segredo, coloca no header
X-HeroPay-Signaturee faz oPOSTJSON para a sua URL. - Você valida e responde 200. Recalcula a assinatura, confere se o evento já foi processado, responde
200rápido e joga o trabalho pesado para uma fila. - Se falhar, o HeroPay tenta de novo. Qualquer resposta fora de
2xx(ou timeout) faz o evento ser reenviado automaticamente.
Quais eventos o webhook do HeroPay envia?
São 9 gatilhos, com os nomes exatos que você passa no campo trigger. Confira a grafia do gatilho de recusa: é payment_credit_cart_refused, com cart, não card. Esse é o nome como ele existe na plataforma, e escrever card devolve 422 ("Trigger is not included in the list").
Gatilho (trigger) | Quando dispara | Ação típica no seu SaaS |
|---|---|---|
spark_payment_confirmed | Pagamento confirmado, numa compra avulsa ou numa mensalidade de assinatura | Validar valor e liberar acesso; emitir nota fiscal; mandar boas-vindas |
payment_pix_created | Comprador gerou o Pix no checkout | Marcar pedido como "aguardando Pix"; lembrete se não pagar em alguns minutos |
spark_payment_boleto_created | Boleto emitido | Enviar o boleto por e-mail ou WhatsApp; agendar lembrete antes do vencimento |
payment_credit_cart_refused | Cartão recusado, numa compra avulsa ou numa mensalidade | Ler payment_methods.credit_card.refused_message e sugerir outro cartão ou Pix |
refunded | Você estornou a compra | Revogar acesso; ajustar comissão e nota fiscal |
chargeback_request | Comprador contestou a compra na operadora do cartão | Revogar acesso; alertar o time e separar as provas de entrega |
subscription_activate | Assinatura ativada, ao confirmar o primeiro pagamento | Criar o plano do cliente no seu banco; começar onboarding |
subscription_update | Mudança de status da assinatura ou novo pagamento | Atualizar status e próxima cobrança; renovar o acesso do ciclo |
subscription_cancel | Assinatura cancelada pelo cliente ou por você, por chargeback, estorno, falta de pagamento ou limite de atrasos | Agendar o fim do acesso para o fim do período pago; disparar pesquisa de saída |
Fonte: documentação de webhooks e API de webhooks. Verificado em set/2026.
O que o HeroPay não avisa por webhook (ainda)
Honestidade de dev para dev, para você não esperar um evento que não vem:
- Carrinho abandonado não é webhook. Os abandonos ficam disponíveis por consulta em
GET /reports/tracking/abandoned_carts(comstart_dateeend_date). Para recuperar, agende uma consulta periódica e dispare a mensagem pelo seu CRM. Veja carrinhos. - Não existe evento de Pix expirado nem de boleto vencido. Use a data que já vem no payload (
payment_methods.pix.expiration_atepayment_methods.boleto.expiration_at) para agendar o seu próprio lembrete, ou consulteGET /reports/transaction/pending/*para ver o que está expirando. - Não existe gatilho "todos os eventos". Cada webhook ouve um gatilho. Para ouvir os 9, cadastre 9 webhooks (podem apontar para a mesma URL).
Na prática, por código
1. Cadastre o webhook
curl -X POST "https://api.beta.heropay.tech/webhook" \
-H "Authorization: Bearer $HEROPAY_JWT_TOKEN" \
-H "Accept: application/vnd.herospark.com; version=1" \
-H "Content-Type: application/json" \
-d '{
"webhook": {
"trigger": "spark_payment_confirmed",
"webhook_url": "https://seuapp.com/webhooks/heropay",
"request_method": "post"
}
}'Os três campos são obrigatórios. webhook_url precisa ser uma URL http ou https válida (em produção, use sempre https), e request_method aceita post, get, put ou delete: use post, que é o que os exemplos desta página e as bibliotecas de webhook esperam.
Resposta 201 Created:
{
"message": "Webhook created successfully",
"data": {
"id": 201738,
"name": "Pagamento confirmado",
"active": true,
"created_at": "2025-08-28T07:21:35.464Z",
"updated_at": "2025-08-28T07:21:35.464Z",
"can_update": true,
"configurations": {
"url": "https://seuapp.com/webhooks/heropay",
"request_method": "post"
},
"event": {
"id": 122758,
"trigger": "spark_payment_confirmed"
}
}
}Guarde o data.id: é ele que você usa para administrar o webhook.
| Operação | Endpoint |
|---|---|
Listar webhooks (paginado com page e per_page, padrão 50) | GET /webhook |
| Pausar sem perder a configuração | PUT /webhook/{id}/disable |
| Religar | PUT /webhook/{id}/enable |
| Remover | DELETE /webhook/{id} |
2. Entenda o payload
Todo evento usa o mesmo template, então um único handler lê os 9 gatilhos. Exemplo de um spark_payment_confirmed de uma venda via Pix de R$ 97,00 (valores ilustrativos):
{
"buyer": {
"name": "Ana Souza",
"email": "ana@exemplo.com",
"phone": "(11) 98888-7777",
"phone_raw": "11988887777",
"document_id": "12345678909",
"document_type": "cpf",
"city": "São Paulo",
"state": "SP",
"district": "Pinheiros",
"complement": "",
"address": { "number": "100", "street": "Rua dos Pinheiros" }
},
"payment": {
"id": "8812345",
"date": "2026-09-23T10:12:44-03:00",
"value": "97.00",
"due_at": "",
"method": "pix",
"status": "paid",
"net_value_cents": "9700"
},
"offer": {
"title": "Plano Pro",
"price": "97.00",
"discount": "",
"with_discount": "false",
"discount_value": ""
},
"product": { "name": "Plano Pro" },
"cart": { "src": "lancamento-setembro" },
"subscription": {
"id": "",
"status": "",
"expiration_at": "",
"available_until": "",
"next_invoice_at": ""
},
"installments": { "count": "1", "fees": "" },
"payment_methods": {
"pix": { "expiration_at": "2026-09-23T10:42:44-03:00" },
"boleto": { "expiration_at": "" },
"credit_card": { "refused_message": "" }
}
}O exemplo segue os blocos e campos do template; os valores são ilustrativos. Antes de escrever o parser, pague um link no sandbox e confira o formato de valores, datas e status no primeiro evento que chegar.
Três campos que valem ouro:
cart.src: a origem que você passou no link (?src=instagram,?src=lancamento-setembro). Chega em todos os webhooks daquela compra, então atribuição de campanha sai de graça, sem UTM perdida no redirecionamento. Quando nenhumsrcé informado, vem vazio.payment.net_value_cents: quanto entra na sua conta depois das tarifas, em centavos. Numa venda de R$ 97 no Pix,9700; no cartão, cerca de9361(R$ 97 menos R$ 3,39 de tarifa).payment_methods.credit_card.refused_message: o motivo da recusa informado pela operadora, no eventopayment_credit_cart_refused. É o que permite responder "limite insuficiente, tente parcelar ou pagar no Pix" em vez de um "pagamento recusado" seco.
Não assuma tipo numérico: converta os valores para número antes de comparar.
3. Valide a assinatura
A assinatura vem no header X-HeroPay-Signature: um HMAC SHA-256 do corpo bruto da requisição, calculado com o segredo do seu webhook. Calcule sobre os bytes exatos que chegaram. Os exemplos abaixo comparam a assinatura recebida com o HMAC codificado em hexadecimal e em base64, para o seu código não depender da codificação. Se você já confirmou a codificação usada na sua conta, pode deixar só ela. Se o seu framework fizer o parse do JSON antes, a reserialização muda espaços e ordem de chaves e a assinatura nunca bate.
Node.js (Express):
import crypto from "node:crypto";
// Express com body cru (express.raw({ type: "application/json" }))
app.post("/webhooks/heropay", (req, res) => {
const recebida = req.get("X-HeroPay-Signature") || "";
const mac = crypto
.createHmac("sha256", process.env.HEROPAY_WEBHOOK_SECRET)
.update(req.body)
.digest();
// aceita hexadecimal ou base64; comparação em tempo constante
const valida = [mac.toString("hex"), mac.toString("base64")].some(
(esperada) =>
esperada.length === recebida.length &&
crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(recebida))
);
if (!valida) {
return res.sendStatus(401);
}
const evento = JSON.parse(req.body);
// deduplique antes de agir: o mesmo evento pode chegar mais de uma vez
res.sendStatus(200);
});Python (Flask):
import base64
import hashlib
import hmac
import json
import os
from flask import Flask, abort, request
app = Flask(__name__)
SEGREDO = os.environ["HEROPAY_WEBHOOK_SECRET"].encode()
@app.post("/webhooks/heropay")
def heropay_webhook():
corpo = request.get_data() # bytes crus, antes de qualquer parse
recebida = request.headers.get("X-HeroPay-Signature", "")
mac = hmac.new(SEGREDO, corpo, hashlib.sha256).digest()
# aceita hexadecimal ou base64; comparação em tempo constante
candidatas = [mac.hex(), base64.b64encode(mac).decode()]
if not any(hmac.compare_digest(recebida, esperada) for esperada in candidatas):
abort(401)
evento = json.loads(corpo)
# deduplique e enfileire; o trabalho pesado roda fora da requisição
return "", 200Os dois exemplos usam comparação em tempo constante (timingSafeEqual e compare_digest). Comparar com == vaza, pelo tempo de resposta, quantos caracteres da assinatura estão certos, o que ajuda quem tenta forjar uma.
Por prompt: peça o webhook à sua ferramenta de IA
Cole https://heropay.tech/llms.txt na conversa com Claude, ChatGPT, Cursor, Lovable e outros, e a ferramenta passa a conhecer a API do HeroPay a partir das docs abertas. Aí o cadastro e a rota viram uma frase:
Com base em https://heropay.tech/llms.txt, escreva o curl que cadastra um
webhook de pagamento confirmado apontando para
https://seuapp.com/webhooks/heropay e a rota em Express que valida o
X-HeroPay-Signature com HMAC SHA-256 sobre o corpo bruto, deduplica por
gatilho + payment.id e responde 200 antes de liberar o acesso numa fila.O código gerado usa o mesmo POST /webhook desta página. A chave de API fica no seu ambiente (variável HEROPAY_JWT_TOKEN), nunca colada no chat. Um MCP server oficial está em desenvolvimento; até lá, llms.txt, docs e API REST são o caminho que funciona hoje. Passo a passo em /integracoes/lovable.
Boas práticas: como receber webhook de pagamento sem liberar acesso errado
Responda 200 rápido e processe depois
O seu endpoint deve validar a assinatura, gravar o evento e responder 200 em milissegundos. Emitir nota, mandar e-mail e chamar outras APIs vai para uma fila (Sidekiq, BullMQ, Celery, SQS, o que você já usa). Endpoint que demora vira timeout, timeout vira retentativa, e retentativa vira o mesmo evento processado duas vezes.
Deduplique: o mesmo evento pode chegar duas vezes
A entrega de webhook é "pelo menos uma vez", não "exatamente uma vez": a retentativa automática pode entregar um evento que você já processou, por exemplo quando o seu 200 se perdeu na rede. O payload não traz um ID de evento próprio, então monte a chave de deduplicação com o que ele tem: gatilho + payment.id (e, nos gatilhos de assinatura, + subscription.status). Grave essa chave numa tabela com índice único antes de agir; se o insert falhar por duplicidade, responda 200 e não faça nada.
Seja idempotente em cada ação
Deduplicar protege a porta; idempotência protege a casa. Escreva cada ação para que rodar duas vezes dê o mesmo resultado: "definir acesso como ativo" em vez de "somar 30 dias", "criar nota se não existir nota para este payment.id" em vez de "criar nota". Se a deduplicação falhar um dia, o estrago é zero.
Valide o valor antes de liberar acesso
Assinatura válida prova que o evento veio do HeroPay. Não prova que é o evento que você esperava. Antes de liberar, confira no seu banco: o payment.status indica pago, o offer.title é o plano que o usuário escolheu e o payment.value bate com o preço que você cobra por ele. Um webhook de um produto de R$ 9,90 não pode liberar o plano anual de R$ 970. Para ações de alto valor, reconsulte a venda pela API (GET /sales/unitary com searchForPeopleWith e paymentStatus) antes de liberar.
Não dependa da ordem de chegada
Eventos podem chegar fora de ordem: um subscription_update pode chegar antes do spark_payment_confirmed da mesma cobrança, principalmente se um deles foi retentado. Trate cada evento como "o estado agora é X" e, quando a ordem importar, compare datas do payload (payment.date, subscription.next_invoice_at) antes de sobrescrever um estado mais novo com um mais antigo.
Proteja o endpoint e o segredo
Use HTTPS. Guarde o segredo em variável de ambiente, nunca no repositório. Isente a rota de webhook da proteção CSRF do seu framework (Rails, Django, Laravel), senão o POST legítimo é bloqueado. Rejeite com 401 qualquer requisição sem assinatura válida e não logue o corpo inteiro com dados pessoais do comprador.
Tenha um plano para o replay
A assinatura do HeroPay cobre o corpo da requisição. Não conte com um carimbo de tempo no header, como o t=... do Stripe-Signature, para barrar replay: uma requisição válida capturada poderia ser reenviada mais tarde com a mesma assinatura. A deduplicação por gatilho + payment.id já neutraliza esse replay: o evento repetido é descartado. Mais um motivo para nunca pular a deduplicação.
Casos de uso
SaaS com plano mensal de R$ 97
O micro-SaaS cria o link recorrente e ouve quatro gatilhos. subscription_activate cria o plano; spark_payment_confirmed renova o acesso a cada mensalidade; payment_credit_cart_refused manda um e-mail com o motivo da recusa e o link para trocar o cartão; subscription_cancel agenda o fim do acesso para o último dia pago. Com 300 assinantes, são cerca de 300 eventos de confirmação por mês e nenhuma consulta de polling. No cartão, cada mensalidade de R$ 97 deixa cerca de R$ 93,61 líquidos (tarifa de 3,49%); se o assinante migrar para o Pix, R$ 97 inteiros. Veja assinaturas e Pix Automático.
Curso ou infoproduto com lançamento
O produtor divulga o mesmo produto com ?src=instagram, ?src=youtube e ?src=email. Cada spark_payment_confirmed chega com cart.src, e uma planilha ou dashboard soma a receita por canal sem depender de pixel. O spark_payment_boleto_created dispara o boleto no WhatsApp na hora, e o payment_pix_created sem confirmação depois de alguns minutos vira lembrete. Veja link de pagamento.
Loja ou app com risco de chargeback
Num produto de R$ 297, um chargeback_request sem reação significa entregar o produto e perder o valor. O handler revoga o acesso, abre um ticket interno com os dados de buyer e payment e separa as evidências de uso para a contestação. O mesmo handler trata refunded para ajustar comissão e nota. Veja antifraude e segurança.
Limites e regras
- Um gatilho por webhook. Para ouvir os 9 eventos, cadastre 9 webhooks. Todos podem apontar para a mesma URL; uma rota por gatilho deixa o tipo do evento explícito no caminho.
- Sem ID de evento no payload. Deduplique por gatilho +
payment.id, como descrito acima. - Replay se barra na deduplicação. Não dependa de carimbo de tempo na assinatura.
- Sem evento de carrinho abandonado, Pix expirado ou boleto vencido. Use os endpoints de relatório para esses casos.
- Sem lista de IPs de origem publicada. Autentique pela assinatura, não pelo IP.
- Retentativa automática em qualquer resposta fora de
2xx. Não conte com um número fixo de tentativas: a conciliação diária comGET /sales/unitarypega o que escapar. - Segredo é credencial. Trate o segredo do webhook como senha: variável de ambiente, fora do repositório e fora dos logs.
- Sandbox idêntico à produção. O payload que você testa em
api.beta.heropay.teché o mesmo que chega no ar; para ir para produção, troque a chave e a URL base.
Perguntas frequentes
O que é webhook, em poucas palavras?
Webhook é uma requisição HTTP que um sistema envia automaticamente para uma URL sua quando um evento acontece. Em pagamentos, é o gateway avisando o seu servidor que um Pix foi pago, um cartão foi recusado ou uma assinatura foi cancelada, com os dados do evento no corpo da requisição. A diferença para uma API comum é a direção: na API você pergunta; no webhook o gateway conta, sem ser perguntado. Isso elimina o polling, que é consultar o status repetidas vezes até mudar, e deixa o seu sistema reagir em segundos, inclusive a eventos que acontecem dias depois da compra, como um boleto compensado.
Qual a diferença entre webhook e API?
A API é o caminho de ida: o seu código faz uma requisição ao HeroPay para criar um link, consultar o saldo ou pedir um estorno, e recebe a resposta na hora. O webhook é o caminho de volta: o HeroPay faz uma requisição ao seu código quando algo muda do lado dele, como um pagamento confirmado. As duas coisas trabalham juntas numa integração de pagamento: você cria a cobrança pela API (POST /payment_links) e fica sabendo do resultado pelo webhook (spark_payment_confirmed). Usar só a API obriga você a consultar o status em loop; usar webhook deixa a confirmação chegar sozinha.
Como funciona o webhook Pix no HeroPay?
Dois gatilhos cobrem o Pix. O payment_pix_created dispara quando o comprador gera o QR Code no checkout, com a data de expiração em payment_methods.pix.expiration_at. O spark_payment_confirmed dispara quando o Pix é pago, com payment.method igual a pix. Com o primeiro, você marca o pedido como aguardando e pode lembrar o comprador; com o segundo, libera o acesso. Não existe evento de Pix expirado: se o pagamento não confirmar até a data de expiração, trate como não pago. O Pix custa R$ 0 por transação no HeroPay. Veja /pix.
Como validar a assinatura do webhook do HeroPay?
Leia o header X-HeroPay-Signature, calcule um HMAC SHA-256 do corpo bruto da requisição usando o segredo do seu webhook e compare com a assinatura recebida usando uma função de tempo constante (crypto.timingSafeEqual no Node, hmac.compare_digest no Python). Se não baterem, responda 401 e descarte. O erro mais comum é calcular sobre o JSON já parseado e reserializado, que muda espaços e ordem das chaves: use o corpo exatamente como chegou, com express.raw no Express ou request.get_data() no Flask.
O que acontece se o meu servidor estiver fora do ar?
O HeroPay reenvia o evento automaticamente sempre que o seu endpoint não responde com um status 2xx, seja por erro 500, timeout ou servidor indisponível. Você não perde a confirmação de pagamento por uma queda curta. A consequência é que o mesmo evento pode chegar mais de uma vez, inclusive depois que o seu servidor já processou a primeira entrega mas a resposta se perdeu. Por isso deduplique por gatilho mais payment.id e escreva ações idempotentes. Como rede de segurança extra, rode uma conciliação diária comparando o seu banco com GET /sales/unitary.
Por que recebi o mesmo webhook duas vezes?
Porque a entrega é "pelo menos uma vez". Se o seu endpoint demorou para responder, respondeu erro ou a resposta 200 se perdeu na rede, o HeroPay considera que a entrega falhou e tenta de novo. É o comportamento esperado de qualquer gateway sério, inclusive do Stripe. A solução é deduplicar: grave a chave gatilho + payment.id (mais subscription.status nos eventos de assinatura) numa tabela com índice único antes de agir, e ignore a segunda entrega respondendo 200. Responder rápido, com o trabalho pesado numa fila, reduz muito a chance de duplicidade.
Qual é o nome certo do gatilho de cartão recusado?
payment_credit_cart_refused, com "cart" e não "card". A grafia é essa na plataforma e na API; enviar payment_credit_card_refused em POST /webhook devolve erro 422 porque o gatilho não está na lista válida. O evento dispara tanto numa compra avulsa recusada quanto numa mensalidade de assinatura recusada, e o motivo informado pela operadora vem em payment_methods.credit_card.refused_message. Use esse motivo para orientar o comprador: limite insuficiente pede parcelamento ou Pix; cartão bloqueado pede outro cartão.
O HeroPay tem webhook de carrinho abandonado?
Não. Carrinho abandonado não é um gatilho de webhook no HeroPay. Os abandonos, checkouts em que o comprador preencheu os dados de contato e não pagou, ficam disponíveis pela API em GET /reports/tracking/abandoned_carts, filtrando por start_date e end_date. O padrão é agendar uma consulta a cada hora e disparar a recuperação pelo seu CRM, WhatsApp ou e-mail. Se você usa o checkout completo da HeroSpark, a recuperação automática de carrinho com gatilho em 15 minutos já vem pronta. Veja carrinhos.
Posso usar a mesma URL para todos os eventos?
Pode. Cada webhook cadastrado ouve um gatilho, mas nada impede que os 9 apontem para a mesma URL. O trabalho é identificar, no handler, qual evento chegou. O caminho mais simples e à prova de dúvida é usar uma rota por gatilho (/webhooks/heropay/pagamento-confirmado, /webhooks/heropay/assinatura-cancelada) apontando para o mesmo código, que lê o tipo pelo caminho. Todos os eventos usam o mesmo template de payload, então um único parser serve para os 9.
Como testar webhook no ambiente local?
Exponha o seu localhost com um túnel (ngrok, cloudflared ou similar) e cadastre a URL pública gerada com POST /webhook no sandbox api.beta.heropay.tech. Para só ver o payload sem escrever código, aponte para um serviço como webhook.site. Crie um link de pagamento no sandbox, pague pelo checkout de teste e veja o evento chegar. Use PUT /webhook/{id}/disable para pausar os disparos sem perder a configuração e enable para religar. O sandbox é gratuito e idêntico à produção, então o payload testado é o mesmo que você vai receber no ar.
Webhook é seguro? Alguém pode forjar um pagamento?
Sem validação, sim: qualquer pessoa que descubra a sua URL pode mandar um POST dizendo "pago". É por isso que todo webhook do HeroPay é assinado com HMAC SHA-256 no header X-HeroPay-Signature. Só quem tem o segredo consegue gerar uma assinatura válida, e qualquer byte alterado no corpo invalida a assinatura. Sua parte é validar em toda requisição, guardar o segredo em variável de ambiente, usar HTTPS, deduplicar eventos para neutralizar reenvio malicioso e conferir o valor contra o seu banco antes de liberar acesso. Veja segurança.
Quanto custa usar webhooks no HeroPay?
Nada. Webhooks estão incluídos em qualquer conta, sem mensalidade e sem limite de cadastros publicado, inclusive no sandbox gratuito. Você paga só pela transação aprovada: Pix R$ 0, boleto R$ 0 e cartão 3,49%, em até 12x. Numa mensalidade de R$ 97 no cartão, a tarifa é de cerca de R$ 3,39; no Pix, zero. Não existe custo por evento entregue nem por retentativa. Tabela completa em /precos.
Comece agora
Cadastre o primeiro webhook no sandbox em cinco minutos, sem cartão de crédito e sem fila de homologação.