Webhooks
O FeitPay envia um POST HTTPS aos seus endpoints cadastrados sempre que acontece algo que você assinou. As
entregas são assinadas, pelo menos uma vez (at-least-once) e repetidas com backoff — por isso o seu
receptor precisa verificar, descartar duplicatas e responder rápido.
Cadastre um endpoint
Seção intitulada “Cadastre um endpoint”{ "url": "https://merchant.example/webhooks/feitpay", "enabled_events": ["payment.confirmed", "payment.review_required", "payment.reorged", "payment.expired"], "description": "Entrega de pedidos"}A resposta traz o secret de assinatura (whsec_…) uma única vez. Use ["*"] para receber todos os tipos de
evento. Endpoints são por ambiente e podem ser limitados a uma loja. Eventos live só são entregues a endpoints
cadastrados no modo live, então cadastre o seu quando a sua conta for ativada.
As URLs precisam ser HTTPS públicas. Elas são verificadas no cadastro e de novo no momento da entrega: o IP de
destino é validado na conexão (faixas privadas, loopback, link-local e de metadados são recusadas), redirecionamentos
não são seguidos e tamanhos e tempos são limitados. No desenvolvimento local, uma URL privada como
http://localhost:4400 só funciona quando o operador da plataforma habilita uma flag explícita de desenvolvimento.
Eventos
Seção intitulada “Eventos”event_type |
Enviado quando |
|---|---|
payment_intent.created |
Uma intenção é criada (API, painel, fatura ou link de pagamento) |
invoice.awaiting_payment |
A intenção de uma fatura tem cotação travada e endereço |
payment.detected |
Uma transferência foi vista, mas não é final |
payment.confirming |
A transferência está em um bloco, acumulando confirmações |
payment.confirmed |
O valor esperado é final — verifique data.fulfillable |
payment.expired |
A cotação expirou sem pagamento |
payment.review_required |
underpaid, overpaid, late_payment, manual_review ou pagamento extra após a confirmação |
payment.reorged |
Um bloco com o pagamento foi substituído — pause a entrega |
payment.resolved |
Uma resolução do lojista ou do operador foi registrada |
payment.canceled |
Você cancelou a intenção |
webhook.test |
Você clicou em Enviar evento de teste (ou chamou POST /v1/webhook-endpoints/{id}/test) |
merchant.verification_updated |
O status da verificação da sua empresa mudou |
payout.requested |
Um saque foi solicitado e o valor ficou retido do seu saldo em custódia |
payout.approved |
Um segundo membro aprovou o saque; em seguida um operador do FeitPay o revisa |
payout.rejected |
Um operador do FeitPay rejeitou o saque; o valor volta a ficar disponível |
payout.canceled |
O saque foi cancelado antes do envio; o valor volta a ficar disponível |
payout.sent |
A carteira quente da plataforma transmitiu o saque (data.tx_hash) |
payout.completed |
O Bitcart informa que a transação do saque foi confirmada |
payout.failed |
O saque não pôde ser enviado; o valor continua retido enquanto o FeitPay investiga, a menos que nada possa ter sido transmitido |
Eventos de pagamento e de saque começam quando a sua conta é ativada para pagamentos live. O store_id dos eventos payout.* é null porque saldos e saques pertencem à organização inteira.
Payload
Seção intitulada “Payload”O corpo é o mesmo objeto retornado por GET /v1/events/{event_id}:
{ "event_id": "evt_01J…", "event_type": "payment.confirmed", "api_version": "2026-10-01", "created_at": "2026-10-20T12:00:00Z", "merchant_id": "org_…", "store_id": "store_01J…", "environment": "live", "object": { "type": "payment_intent", "id": "pi_01J…" }, "data": { "status": "confirmed", "previous_status": "confirming", "status_reason": "bitcart_complete", "amount": "59.90", "currency": "EUR", "merchant_order_id": "order-123", "fulfillable": true, "quote": null, "totals": null }}data é propositalmente mínimo. Em pagamentos live, quote e totals são null (eles descrevem o processamento
somente de observação); consulte o objeto processor da intenção com GET /v1/payment-intents/{id} para ver o
ativo e o valor pagos. payment_intent.created traz status, amount, currency, merchant_order_id e
checkout_url; webhook.test traz message e endpoint_id; merchant.verification_updated traz kyb_status e
previous_kyb_status.
Cabeçalhos
Seção intitulada “Cabeçalhos”| Cabeçalho | Valor |
|---|---|
FeitPay-Signature |
t=<unix>,v1=<hex> — vários v1 durante a rotação do segredo |
FeitPay-Event-Id |
Igual a event_id |
FeitPay-Event-Type |
Igual a event_type |
FeitPay-Delivery-Id |
A entrega (whd_…) — uma por evento e endpoint |
FeitPay-Delivery-Attempt |
1 na primeira tentativa, depois 2, 3, … |
Content-Type |
application/json |
Seu receptor, em cinco regras
Seção intitulada “Seu receptor, em cinco regras”- Verifique a assinatura sobre o corpo bruto antes de interpretá-lo (como).
Responda
400em caso de falha. - Descarte duplicatas pelo
event_id. As entregas são at-least-once; o mesmo evento pode chegar mais de uma vez, inclusive após uma nova tentativa manual. - Confirme rápido com qualquer
2xx(em poucos segundos). Faça o trabalho lento depois. - Não dependa da ordem. Use
data.statuse, quando importar, releia a intenção. - Consulte antes de ações irreversíveis. Antes de enviar mercadorias ou conceder valor, chame
GET /v1/payment-intents/{id}e confirafulfillable. O webhook é um sinal; a API é a fonte da verdade.
Novas tentativas, dead letters e reenvio
Seção intitulada “Novas tentativas, dead letters e reenvio”- Uma entrega tem sucesso com qualquer
2xx. Qualquer outra coisa — não2xx, timeout, erro de conexão — é repetida com backoff exponencial e jitter. - Após o número máximo de tentativas, a entrega é marcada como
deade listada como dead letter para os operadores; ela nunca é descartada em silêncio. - Cada tentativa fica registrada: horário, duração, status da resposta e um trecho curto e sanitizado da resposta.
Consulte no painel ou com
GET /v1/webhook-deliveries/{id}. - Tentar agora:
POST /v1/webhook-deliveries/{id}/retryrecoloca a entrega na fila imediatamente. O mesmoevent_idé entregue de novo, e a ação vai para o log de auditoria.
Rotação do segredo de assinatura
Seção intitulada “Rotação do segredo de assinatura”POST /v1/webhook-endpoints/{id}/rotate-secret retorna um novo segredo (exibido uma vez). O segredo anterior continua
assinando em paralelo por previous_secret_ttl_hours (padrão 24, até 168, ou 0 para expirá-lo na hora); durante
essa janela, cada entrega traz duas assinaturas v1. Publique o novo segredo — ou configure os dois — e deixe o
antigo expirar.
O que nunca enviamos
Seção intitulada “O que nunca enviamos”Webhooks nunca contêm chaves de API, segredos de assinatura, dados pessoais completos de clientes ou chaves públicas
estendidas de carteiras. Os logs, dos dois lados, nunca devem registrar o valor de FeitPay-Signature junto com o
seu segredo.