Webhooks — Visão geral
Webhooks são o sinal oficial. Callbacks do navegador (onSuccess)
e visões do dashboard são conveniência; webhooks são a verdade.
Garantias de entrega
- Pelo menos uma vez. Um único evento pode ser entregue até 6
vezes se o seu servidor não retornar 2xx dentro do timeout.
Torne o seu handler idempotente — deduplique em
X-Delivery(o payload não tem campoevent_id; o UUID estável da entrega é a chave de idempotência). - Um evento por requisição HTTP. Sem batching.
- Isolamento por endpoint. Se você tem múltiplos endpoints registrados, cada um tem a própria trilha de entrega e retry. Um endpoint lento não atrasa os outros.
- Assinado. Todo payload leva um header
X-Signature(e durante a janela de 24 horas depois de uma rotação, também umX-Signature-Prev). Verifique antes de fazer qualquer coisa com o body. Veja Verificação de assinatura.
Tipos de evento que se pode assinar
| Evento | Dispara quando… |
|---|---|
payment.settled | A transferência on-chain atingiu a contagem de confirmações da rede. Use isto para marcar pedidos como pagos. |
payment.failed | Um pagamento fiat foi rejeitado pelo provedor de pagamento. Não dispara para timeouts de cripto — esses afloram como checkout.expired, e pagamentos cripto curtos afloram como payment.underpaid. |
payment.underpaid | Fundos chegaram, mas abaixo do total do pedido (típico: taxa de transferência de stablecoin descontada do valor). |
payment.overpaid | Fundos chegaram em excesso ao total do pedido. O excedente é registrado mas não é auto-reembolsado. |
order.created | Um novo pedido foi aberto — pela sua chamada de API B2B ou por uma conversão de checkout-session. |
order.canceled | Um pedido foi movido para cancelado. O data.reason do payload distingue cancel manual de payment_timeout (um pedido não pago expirou por timeout). |
order.resolved | Um pedido PARTIAL_PAID foi resolvido para PAID — o lojista aceitou o déficit. |
order.reopened | Um pedido previamente auto-cancelado (canceled_reason=payment_timeout) foi reaberto pelo lojista. |
checkout.created | Um comprador abriu o checkout para um pedido. |
checkout.completed | O fluxo do lado do comprador terminou (não implica liquidação on-chain — use payment.settled para isso). |
checkout.expired | O comprador abandonou e o TTL da sessão acabou. |
payment.refund.requested | Um registro de reembolso foi criado — seja a partir de uma chamada de API iniciada pelo lojista, seja a partir de um formulário de pedido de reembolso submetido pelo cliente. |
payment.refund.approved | Um reembolso pendente passou pelo seu workflow de aprovação. |
payment.refund.rejected | Um reembolso pendente foi negado. |
payment.refund.executed | A transferência on-chain do reembolso foi confirmada e o registro foi para o estado terminal executed. |
refund_request.created | Um token de pedido de reembolso foi criado. data.source é b2b / dashboard / renewal. Opcional inscrever-se — útil para pipelines de auditoria que acompanham qual token está ativo no momento por pedido. |
refund_request.renewal_requested | Um comprador clicou em “Pedir link novo” depois que o token expirou. Fortemente recomendado inscrever-se — é o sinal de que o widget de renovação tem um novo item para agir. |
refund_request.renewed | Uma renovação foi aprovada e um novo token substituiu o antigo. data.old_token / data.new_token formam a cadeia de auditoria. |
refund_request.canceled | Um lojista moveu um token para CANCELED pelo dashboard (por exemplo, negou um pedido de renovação, matou um link vivo). Idempotente — só a primeira transição emite. data.reason é a nota opcional do lojista. |
Planejados (em breve)
Em breve. Esses eventos pertencem a faturas recorrentes e assinaturas, que ainda não estão disponíveis. Eles não estão na tabela de eventos assináveis acima e não podem ser assinados hoje. Veja Faturas recorrentes.
| Evento planejado | Dispara quando… |
|---|---|
subscription.created | Uma assinatura é criada. |
invoice.created | Uma fatura de um ciclo de cobrança é criada. |
invoice.paid | Uma fatura é paga. |
subscription.past_due | Uma fatura continua sem pagamento após o vencimento. |
subscription.canceled | Uma assinatura é cancelada. |
O formulário de endpoint do dashboard lista os mesmos eventos. Tentar se inscrever em um evento que não existe é rejeitado quando você salva o endpoint.
Eventos de teste não são assináveis. O botão Send Test por
endpoint no dashboard envia um evento webhook.test.ping para aquele
único endpoint imediatamente, sem retentativas. Ele não aparece no
catálogo acima: você o recebe por ter um endpoint registrado, não por
ter se inscrito.
Inscreva-se só nos eventos que você trata. Cada endpoint tem o
próprio filtro de evento; o wildcard "*" significa “todo evento,
incluindo os adicionados no futuro”. Inscrever-se em menos eventos
deixa o seu handler mais simples e significa menos retentativas quando
o seu endpoint tem erros.
Payload + headers
O corpo HTTP é o objeto de dados específico do evento, direto.
Sem envelope externo estilo Stripe — campos como tipo do evento, ID
da entrega e timestamp de emissão ficam nos headers. Para
payment.settled o body fica assim:
{
"receipt_id": "rcp_…",
"order_id": "ord_…",
"payment_intent_id": "pin_…",
"checkout_session_id": "cst_…",
"merchant_id": "mer_…",
"customer_id": "cus_…",
"total": "49.00",
"currency": "USD",
"payment_method": "crypto",
"token": "USDC",
"network": "polygon",
"tx_hash": "0x…",
"deposit_address": "0x…",
"treasury_address": "0x…",
"amount_received": "49.00",
"confirmations": 5,
"metadata": { /* por evento */ }
}Outros eventos carregam os próprios campos. Nomes de campo são
estáveis (lower snake_case); o tx hash on-chain é sempre tx_hash.
tx_hash é o identificador da transação no formato próprio da rede (0x… em redes EVM; o hash ou a assinatura nativos em TRON, Solana e TON). deposit_address pode estar ausente em TRON, Solana e TON, porque lá os compradores pagam diretamente a sua carteira de Tesouraria, e confirmations segue Redes e ativos.
Headers na requisição de entrada
Content-Type: application/json
X-Event: payment.settled
X-Delivery: 7c9e6679-7425-40de-944b-e07fc1f90ae7
Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7
X-Timestamp: 1729536000
X-Signature: sha256=9a8b7c…
X-Signature-Prev: sha256=fa31b2… (só durante uma janela de tolerância de rotação)| Header | O que é |
|---|---|
X-Event | O tipo do evento (por exemplo, payment.settled). Rotear nisso na camada de proxy se você quer pular o parse do JSON. |
X-Delivery | UUID que identifica a linha de entrega. Estável em todas as retentativas do mesmo par (event, endpoint) — use como sua chave de idempotência. |
Idempotency-Key | Espelha o X-Delivery (mesmo valor). Definido em toda entrega. |
X-Timestamp | Unix-segundos do momento em que a tentativa foi enviada. Assinado dentro do payload para que um par (body, X-Signature) capturado não possa ser replayado indefinidamente — rejeite entregas cujo timestamp esteja fora da sua janela de tolerância. |
X-Signature | sha256=<hex> de HMAC-SHA256(secret, X-Timestamp + "." + raw_body). Veja Verificação de assinatura. |
X-Signature-Prev | Mesmo algoritmo com o segredo anterior. Presente só na janela de 24 horas depois que você rotaciona — deixa verificadores rodando qualquer das duas chaves continuarem aceitando entregas durante o cutover. Depois da janela fechar o header para de ser enviado. |
Cronograma de retry
Se o seu endpoint não retornar 2xx dentro do timeout, a gente
retenta neste cronograma (timestamps relativos à primeira tentativa):
| Tentativa | Atraso | Acumulado |
|---|---|---|
| 1 | 0s | 0s |
| 2 | +1 min | 1m |
| 3 | +5 min | 6m |
| 4 | +15 min | 21m |
| 5 | +1 hora | 1h 21m |
| 6 | +6 horas | 7h 21m |
Depois da tentativa 6 falhar, a entrega é marcada como Failed e o
e-mail da sua conta é notificado. Você pode reenviar eventos com falha
pelo painel Developers → Webhooks → Delivery
history do dashboard. Cada reenvio é uma nova entrega com o próprio
X-Delivery.
Registrar um endpoint
No dashboard do lojista :
- Developers → Webhooks → + Add endpoint
- Cole a sua URL — só
https://…(HTTP puro é rejeitado; o formulário de criação também bloqueialocalhost, ranges de IP privados e URLs com userinfo) - Escolha os eventos para assinar (ou
*para todos) - Escolha o ambiente — test ou live (cada um tem o próprio segredo; eles nunca se cruzam)
- Salve → o dashboard mostra o segredo de assinatura (
whsec_…) uma vez. Guarde do lado do servidor; você vai precisar para as próximas duas features.
Você pode registrar até 10 endpoints por ambiente por lojista (por exemplo, um para entrega em produção, um para espelhamento em staging, um para um notificador no Slack). Cada um mantém o próprio estado de retry e segredo.
Ações de ciclo de vida em cada endpoint
O menu ⋮ em cada card de endpoint expõe:
- Edit — muda a URL, descrição ou lista de inscrições. A URL
nova é re-validada com as mesmas regras
https:///SSRF da criação. - Send Test — faz POST síncrono de um envelope
webhook.test.pingassinado com o seu segredo atual. O dashboard mostra o status HTTP, latência e um snippet de 512 bytes da sua resposta. Pings de teste não são retentados, então você recebe a resposta imediatamente. - Rotate Secret — gera um segredo novo. O anterior permanece
válido por 24 horas (entregas levam tanto
X-SignaturequantoX-Signature-Prevdurante a janela para que verificadores rodando qualquer das duas chaves continuem aceitando eventos enquanto você redeploya). - Reveal Secret — re-exibe o segredo existente. Condicionado a verificação 2FA recente e registrado no log de auditoria; use só quando você perdeu a sua cópia e o Rotate não é aceitável.
- Enable / Disable — liga ou desliga o endpoint sem perder o histórico de entrega. Endpoints desabilitados ficam no dashboard mas não recebem novas entregas.
- Delete — permanente. Use Disable se você pode reabilitar depois.
Dicas para handlers
- Retorne 2xx rápido. Confirme com
200 OKantes de fazer trabalho pesado — mova a entrega para um job em background. O timeout por tentativa é de 10 segundos; segurar a resposta mais do que isso dispara um retry. O timeout é do lado da plataforma e não é configurável pelo lojista — contate o suporte se o seu handler genuinamente precisa de mais tempo. - Deduplique em
X-Delivery(ouIdempotency-Key— mesmo valor). Mesmo que você retorne 2xx, um proxy upstream pode derrubar a conexão e disparar um retry; o ID da entrega é estável em toda retentativa da mesma linha de entrega, então é a chave certa. - Tolere tipos de evento desconhecidos. Eventos novos podem aparecer; retorne 200 e no-op no lugar de 4xx, ou essas entregas vão continuar sendo retentadas.
- Logue
X-Deliveryjunto com a sua lógica de negócio. Quando algo dá errado, essa é a chave de junção entre o nosso lado e o seu.
Próximos passos
- Verificação de assinatura — o algoritmo exato + padrões de proteção contra replay.
- Conceitos → Sessões — em que estado uma sessão está quando cada evento dispara.