Webhooks
Payload de webhook v2 (atual)
v2 é o formato que recomendamos para todas as novas integrações. Usa um envelope baseado em eventos — um campo event no topo nomeando o tipo de evento, mais um objeto payload com os campos específicos do evento. Os nomes de campos são snake_case. Pagador e recebedor são sempre objetos aninhados separados, com o ISPB da instituição sempre preenchido.
Quatro vantagens concretas sobre v1:
- Roteamento é um switch em
event— sem mais inferência a partir de combinações detype+status. - ISPB e nome da instituição são campos de primeira classe. Conciliação contra extrato bancário vira comparação de string em vez de heurística.
- Infrações são seu próprio evento (
infraction.updated), não um objeto embutido. Você pode rotear para uma fila separada de handler de compliance. - Rastreio de origem (V2) — todo webhook de cash-in carrega
source_type+ IDs polimórficos (invoice_id,offer_id,product_id,quick_link_id) indicando exatamente de onde a transação veio (API direta, Invoice, Checkout de Offer, QuickLink). Sem precisar fazerGET /transactions/:idpra descobrir a origem.
Estrutura do envelope
Todo webhook v2 tem a mesma forma na raiz:
{
"event": "cashin.paid",
"environment": "LIVE",
"payload": { /* campos especificos do evento */ }
}
event— o tipo do evento (ex:cashin.paid). Use pra rotear no seu handler.environment—"TEST"(sandbox, chavesk_test_*) ou"LIVE"(produção). É um único campo, sempre na raiz do envelope. Serve pra separar o processamento — ex: em TEST manda pra um canal de dev, em LIVE pro fluxo real.payload— os campos específicos do evento (em snake_case).
ℹ️
environmentfica só na raiz. O objetopayloadnão repete o ambiente. Os exemplos depayloadabaixo mostram sóevent+payloadpor brevidade, mas o envelope real que chega na sua URL sempre inclui oenvironment(TEST/LIVE) no topo, como no exemplo acima.
No seu handler, faça roteamento por event:
switch (webhook.event) {
// Cash-in (transações)
case "cashin.paid": return handleCashinPaid(webhook.payload);
case "cashin.refunded": return handleCashinRefunded(webhook.payload);
case "deposit.credited": return handleDepositCredited(webhook.payload);
// Cash-out (saques)
case "cashout.success": return handleCashoutSuccess(webhook.payload);
case "cashout.failed": return handleCashoutFailed(webhook.payload);
case "cashout.returned": return handleCashoutReturned(webhook.payload);
// Disputas (chargeback + bloqueio de compliance dividem este evento)
case "infraction.updated": return handleInfraction(webhook.payload);
// Subscription lifecycle (assinaturas recorrentes)
case "subscription.created": return handleSubscriptionCreated(webhook);
case "subscription.activated": return handleSubscriptionActivated(webhook);
case "subscription.paused": return handleSubscriptionPaused(webhook);
case "subscription.canceled": return handleSubscriptionCanceled(webhook);
case "subscription.past_due": return handleSubscriptionPastDue(webhook);
case "subscription.expired": return handleSubscriptionExpired(webhook);
default:
logger.warn("unknown_webhook_event", { event: webhook.event });
// Retorne 200 mesmo assim — nao nos faca dar retry em algo que voce ainda nao processa
}
✅ Eventos
subscription.*usam o mesmo envelope dos demais eventos V2:{ event, environment, payload: {...} }, com os campos do recurso aninhados empayloade em snake_case (subscription_id,next_billing_at, etc). Veja os exemplos detalhados nas subseções abaixo.
Sempre inclua um branch default. Podemos adicionar novos tipos de evento no futuro, e você não quer que eventos desconhecidos quebrem seu handler e disparem nosso loop de retry.
Métodos de pagamento
Todo webhook de cash-in (cashin.paid, cashin.refunded, deposit.credited, infraction.updated) carrega o campo payment_method (v2) / paymentMethod (v1) dizendo por qual trilho a venda foi feita. O valor é o código canônico do método — estável e em MAIÚSCULAS, igual aos outros enums do payload (status, source_type).
| Valor | Método | Moeda |
|---|---|---|
PIX | Pix | BRL |
PIX_BOLETO | Cobrança híbrida: o pagador escolhe Pix ou boleto | BRL |
BOLETO | Boleto registrado | BRL |
CREDIT_CARD | Cartão de crédito | BRL |
SPEI | SPEI (transferência) | MXN |
OXXO | OXXO (dinheiro no balcão) | MXN |
BANK_TRANSFER | Transferência bancária (CVU/CBU) | ARS |
QR | QR | BOB |
PSE | PSE (débito online) | COP |
NEQUI | Nequi (carteira digital) | COP |
BREB | BRE-B (instantâneo) | COP |
PIX_BOLETO(híbrido): o campo reflete como a cobrança foi criada — uma invoice única que aceita os dois trilhos. O valor permanecePIX_BOLETOmesmo depois de paga; a liquidação entra na família do Pix.
Saque (cash-out) não traz
payment_method— é sempre Pix de saída.
Compatibilidade: o campo é aditivo. Se o seu handler ainda não o lê, nada muda; quando quiser, use-o pra ramificar (ex.: tratar boleto diferente de Pix no seu ERP). Pode vir
nullem transações muito antigas sem método registrado.
cashin.paid
Disparado quando uma transação de cash-in é paga (PIX confirmado ou cartão autorizado e capturado).
{
"event": "cashin.paid",
"environment": "LIVE",
"payload": {
"transaction_id": "17615714245971918718644287",
"external_id": "id-da-transacao-no-seu-sistema",
"amount": 1100,
"payment_method": "PIX",
"end_to_end_id": "E18236120202610271324s05499b347c",
"payer": {
"name": "João Silva",
"document": "99999999999",
"ispb": "19318318",
"institution": "NU PAGAMENTOS"
},
"receiver": {
"name": "Nome da sua empresa",
"document": "12345678000190",
"ispb": "18236120",
"institution": "iHubGames"
},
"pix_key": "chave-pix@empresa.com",
"source_type": "API",
"invoice_id": null,
"offer_id": null,
"product_id": null,
"quick_link_id": null
}
}
transaction_id— nosso ID internoexternal_id— sua referência setada na criaçãoamount— centavospayment_method— método de pagamento da venda (PIX,CREDIT_CARD,BOLETO,PIX_BOLETO,SPEI, …). Veja a tabela completa na subseção Métodos de pagamento abaixo.end_to_end_id— E2E do Bacen para conciliaçãopayer/receiver— dados institucionais.ispbé o identificador de 8 dígitos do Bacen;institutioné o nome legível.pix_key— a chave Pix que recebeu o pagamento. Preenchida em contas com conta Pix dedicada (BaaS) ou chave de recebimento cadastrada — permite identificar em qual chave o valor caiu.nullnas demais contas. Só em BRL.clabe— a CLABE que recebeu o pagamento, em MXN. Mutuamente exclusivo compix_key: chega um ou outro, nunca os dois.source_type— (V2) origem da transação. Um dos valores:API,INVOICE,CHECKOUT_OFFER,QUICK_LINK. Pode sernullem transações criadas antes do refator V2.invoice_id/offer_id/product_id/quick_link_id— (V2) IDs polimórficos. Apenas os campos relevantes aosource_typevêm preenchidos; o resto vemnull. Veja a seção Rastreio de origem (V2) abaixo pra entender o mapeamento.
cashin.refunded
Mesma forma de envelope e mesmos campos de payload de cashin.paid — o que muda é o que aconteceu. O status da transação mapeia para a tabela de status da v1:
| Evento v2 | Status equivalente em v1 |
|---|---|
cashin.paid | APPROVED |
cashin.refunded | REFUNDED |
infraction.updated | CHARGEBACK ou BLOCKED (veja seção abaixo) |
Em cashin.refunded: o estorno foi concluído com sucesso. Use pra atualizar receita e revogar acesso ao recurso (se aplicável).
Sobre CHARGEBACK e BLOCKED: V2 NÃO tem eventos próprios pra disputa de cartão ou bloqueio de compliance. Ambos os casos disparam o evento infraction.updated — veja a subseção dedicada abaixo. Os campos da disputa vêm no payload da infração, e o transaction_id relacionado também.
Uma observação sobre INCOSISTENT: v2 não tem evento próprio cashin.incosistent. Quando a transação subjacente entra no estado INCOSISTENT (valor pago ≠ valor cobrado), você recebe um evento cashin.paid com um campo status: "INCOSISTENT" embutido no payload. Trate como anomalia: não entregue, não estorne, abra um ticket com transaction_id e x-trace-id.
Sobre REJECTED: v2 NÃO emite nenhum evento quando a transação é rejeitada pelo PSP. Você só vê o status: REJECTED na resposta HTTP do POST /transactions/v2/purchase. Se você usa V1 (postbackUrl per-tx), o POST V1 sai com status: "REJECTED" no payload.
deposit.credited
Disparado quando cai dinheiro direto numa chave sua, sem cobrança pré-criada — alguém te pagou sem passar por um checkout ou QR gerado por você.
Acontece sempre que a sua conta tem um identificador fixo de recebimento:
| Moeda | Identificador | Campo no payload |
|---|---|---|
| BRL | Chave Pix | pix_key |
| MXN | CLABE | clabe |
Não é exclusivo de conta BaaS: conta de gateway comum com chave cadastrada recebe este evento normalmente.
O envelope e o payload são idênticos aos de cashin.paid (mesmos campos: transaction_id, amount, payer, receiver, end_to_end_id, etc). O que muda é só o event:
{
"event": "deposit.credited",
"environment": "LIVE",
"payload": {
"transaction_id": "17615714245971918718644287",
"external_id": null,
"amount": 5000,
"payment_method": "PIX",
"end_to_end_id": "E18236120202610271324s05499b347c",
"payer": {
"name": "João Silva",
"document": "99999999999",
"ispb": "19318318",
"institution": "NU PAGAMENTOS"
},
"receiver": {
"name": "Nome da sua empresa",
"document": "12345678000190",
"ispb": "18236120",
"institution": "iHubGames"
},
"pix_key": "chave-pix@empresa.com",
"source_type": "DEPOSIT",
"invoice_id": null,
"offer_id": null,
"product_id": null,
"quick_link_id": null
}
}
Em MXN, o mesmo evento chega com clabe no lugar de pix_key:
{
"event": "deposit.credited",
"environment": "LIVE",
"payload": {
"transaction_id": "17615714245971918718644287",
"external_id": null,
"amount": 100000,
"payment_method": "SPEI",
"end_to_end_id": "MAGEN1234567890",
"payer": {
"name": "Juan Perez",
"document": "012345678901234567",
"ispb": "",
"institution": ""
},
"receiver": {
"name": "Nome da sua empresa",
"document": "12345678000190",
"ispb": "",
"institution": ""
},
"clabe": "646180111812345678",
"source_type": "DEPOSIT",
"invoice_id": null,
"offer_id": null,
"product_id": null,
"quick_link_id": null
}
}
Os dois campos são mutuamente exclusivos: chega
pix_keyouclabe, nunca os dois. O campo ausente é a informação de que aquele trilho não se aplica.
⚠️ Migração: antes, PIX recebido direto na nominal chegava como
cashin.paid. Agora chega comodeposit.credited. Se você quer continuar tratando esses recebimentos, assine também o eventodeposit.creditedno seu endpoint — não bastacashin.paid.
cashout.success / cashout.failed / cashout.returned
Eventos de cash-out usam o mesmo envelope. Recebedor e pagador são invertidos comparados ao cash-in — sua empresa agora é o pagador.
cashout.success (transferência concluída):
{
"event": "cashout.success",
"environment": "LIVE",
"payload": {
"withdrawal_id": "17615714245971918718644287",
"external_id": "id-do-saque-no-seu-sistema",
"amount": 5000,
"end_to_end_id": "E18236120202610271324s05499b347c",
"receiver": {
"name": "Maria Souza",
"document": "98765432100",
"ispb": "60701190",
"institution": "ITAÚ UNIBANCO"
},
"payer": {
"name": "Nome da sua empresa",
"document": "12345678000190",
"ispb": "18236120",
"institution": "iHubGames"
}
}
}
cashout.failed (rejeitada pela instituição do recebedor ou pelo SPI):
{
"event": "cashout.failed",
"environment": "LIVE",
"payload": {
"withdrawal_id": "17615714245971918718644287",
"external_id": "id-do-saque-no-seu-sistema",
"amount": 5000,
"end_to_end_id": "E18236120202610271324s05499b347c",
"error_message": "Chave PIX não encontrada no DICT"
}
}
O motivo da falha vem em
error_message(texto livre do provedor/SPI). Não há um campoerror_codeestruturado no payload.
Em cashout.failed, os fundos retornam imediatamente para o seu saldo disponível. Nenhuma ação manual é necessária do seu lado, mas você deve notificar o usuário que o saque dele não passou.
cashout.returned (foi bem-sucedido inicialmente, depois voltou):
Mesma forma de payload de cashout.success, mas a instituição de destino devolveu os fundos — geralmente porque a conta foi encerrada ou sinalizada. Os fundos estão de volta no seu saldo. Investigue antes de tentar de novo.
Nunca faça retry de um saque sem o webhook final. Enquanto o status estiver em WITHDRAW_REQUEST ou WITHDRAW_PROCESSING, aguarde — disparar uma segunda chamada é a forma mais comum de empresas pagarem o mesmo destinatário duas vezes por acidente.
infraction.updated
Dispara sempre que uma infração muda de estado. Em v1 isso era embutido dentro de webhooks de transação; em v2 tem seu próprio evento, pra que você possa rotear pra um handler diferente (tipicamente uma fila de revisão de compliance).
{
"event": "infraction.updated",
"environment": "LIVE",
"payload": {
"transaction_id": "17615714245971918718644287",
"external_id": "id-da-transacao-no-seu-sistema",
"amount": 1100,
"end_to_end_id": "E18236120202610271324s05499b347c",
"payer": {
"name": "João Silva",
"document": "99999999999",
"ispb": "19318318",
"institution": "NU PAGAMENTOS"
},
"receiver": {
"name": "Nome da sua empresa",
"document": "12345678000190",
"ispb": "18236120",
"institution": "iHubGames"
},
"pix_key": null,
"source_type": "CHECKOUT_OFFER",
"invoice_id": "inv_a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"offer_id": "off_d4c3b2a1-f6e5-b8a7-d0c9-f2e1b4a3d6c5",
"product_id": "prd_7e8f9a0b-1c2d-3e4f-5a6b-7c8d9e0f1a2b",
"quick_link_id": null,
"infraction": {
"id": "dd0b2c77-8dd6-4eb5-b254-a46417eac46d",
"status": "AWAITING_CUSTOMER_RESPONSE",
"reason_details": "Pagador reportou a transação como não autorizada.",
"analysis_result": null,
"analysis_details": "Aguardando suas evidências.",
"created_at": "2026-04-08T14:30:00.000Z",
"closed_at": null,
"cancelled_at": null,
"response_at": null,
"defended_at": null
}
}
}
A semântica dos status é a mesma da v1 — veja a tabela de status de infração na documentação v1. Os campos passam para snake_case (created_at, reason_details, analysis_result).
Os campos de origem (source_type, invoice_id, offer_id, product_id, quick_link_id) vêm preenchidos igual em cashin.paid — útil pra rotear a infração pra equipe de compliance certa (ex: disputas em Invoice recorrente costumam exigir tratamento diferente de disputas em QuickLink avulso).
Valores finais de analysis_result:
AGREED— concordamos com a parte que abriu a disputa. A transação está sendo revertida.DISAGREED— rejeitamos a disputa. A transação se mantém.null— análise ainda não finalizada; o status estará emUNDER_REVIEWou anterior.
subscription.* (eventos de assinatura recorrente)
✅ Mesmo envelope dos eventos cashin/cashout:
subscription.*usa{ event, environment, payload: {...} }com os campos do recurso aninhados empayloade em snake_case (subscription_id,next_billing_at, etc.). Padronizado com os demais webhooks V2.
Os 6 eventos cobrem todas as transições de status do model Subscription. Subscriptions são criadas via POST /v2/subscriptions (standalone B2B) ou automaticamente quando alguém compra uma Offer recorrente em /o/:userSlug/:offerSlug.
subscription.created — disparado em qualquer subscription nova:
{
"event": "subscription.created",
"environment": "LIVE",
"payload": {
"subscription_id": "sub_8f2c1a9d",
"status": "ACTIVE",
"amount": 9990,
"billing_cycle": "MONTHLY",
"billing_cycle_count": 1,
"next_billing_at": "2026-06-28T00:00:00.000Z",
"customer_id": "cus_...",
"offer_id": "off_..."
}
}
statusno momento da criação (pode serTRIAL,ACTIVE, etc).- Se
status === "ACTIVE"no create, você recebesubscription.activatedtambém logo em seguida.
subscription.activated — disparado quando status vai pra ACTIVE:
Dispara em 2 cenários: criação direto pra ACTIVE, ou retomada após PAUSED.
{
"event": "subscription.activated",
"environment": "LIVE",
"payload": {
"subscription_id": "sub_8f2c1a9d",
"previous_status": null,
"next_billing_at": "2026-06-28T00:00:00.000Z"
}
}
previous_status vem null na criação direto pra ACTIVE, ou "PAUSED" se foi retomada.
subscription.paused — assinatura pausada:
{
"event": "subscription.paused",
"environment": "LIVE",
"payload": {
"subscription_id": "sub_8f2c1a9d",
"previous_status": "ACTIVE",
"paused_at": "2026-05-15T14:30:00.000Z"
}
}
Use pra pausar acesso ao recurso temporariamente. Quando vier subscription.activated depois, restaure.
subscription.canceled — encerrada definitivamente:
{
"event": "subscription.canceled",
"environment": "LIVE",
"payload": {
"subscription_id": "sub_8f2c1a9d",
"previous_status": "ACTIVE",
"canceled_at": "2026-05-15T14:30:00.000Z",
"canceled_reason": "Cliente solicitou via suporte",
"invoices_canceled": 3
}
}
invoices_canceled indica quantas faturas futuras (status PENDING/OVERDUE/DRAFT) foram canceladas automaticamente junto. Use pra revogar acesso na próxima data de billing (não imediatamente — cliente já pagou pelo ciclo atual).
subscription.past_due — cliente atrasou pagamento:
{
"event": "subscription.past_due",
"environment": "LIVE",
"payload": {
"subscription_id": "sub_8f2c1a9d",
"previous_status": "ACTIVE"
}
}
Dispara quando a invoice mais recente da subscription fica OVERDUE. Sistema marca a subscription como PAST_DUE. Use pra iniciar dunning — e-mails de cobrança, suspensão após N dias, etc. Se o cliente pagar a invoice depois, vem um subscription.activated (retomada).
subscription.expired — fim natural do contrato:
{
"event": "subscription.expired",
"environment": "LIVE",
"payload": {
"subscription_id": "sub_8f2c1a9d",
"previous_status": "ACTIVE"
}
}
Dispara quando maxCycles é atingido OU expiresAt passou. Diferente de canceled (que é intervenção manual), expired é o fim previsto do contrato. Não dispara mais nenhum cobrança depois disso.
📌 Idempotência: todos os
subscription.*são one-shot por transição. Marcar PAST_DUE 2x não dispara o evento de novo (lógica emSubscriptionsService.markPastDue). Cancel já cancelada também vira no-op. Usepayload.subscription_id+eventcomo chave única no seu handler.
transfer.internal / balance.converted (saldo)
Dois eventos que mexem no seu saldo sem passar por uma venda ou um saque. Só chegam se você assinar — nenhum dos dois existia antes e nenhuma integração antiga precisa mudar.
transfer.internal — saldo movido entre contas iHubGames:
{
"event": "transfer.internal",
"environment": "LIVE",
"payload": {
"transfer_id": "8f2c1a9d-3e4b-4a76-b8e1-9c0d2e3f4a5b",
"status": "COMPLETED",
"direction": "sent",
"amount": 15000,
"fee": 100,
"total": 15100,
"currency": "BRL",
"description": "repasse mensal",
"completed_at": "2026-09-10T18:32:04.512Z",
"counterparty": { "name": "Loja do Tião", "account_number": 12185 }
}
}
O mesmo evento chega nos DOIS lados — quem enviou e quem recebeu — com o mesmo transfer_id. O que os distingue é o direction:
direction | Quem recebe | amount | fee | total |
|---|---|---|---|---|
sent | quem enviou | valor transferido | taxa da sua conta | valor + taxa (o que saiu do saldo) |
received | quem recebeu | valor transferido | sempre 0 | igual ao amount |
A taxa é cobrada por cima: o destino recebe o valor cheio e a origem paga valor + taxa. Se as duas contas forem suas, você recebe os dois webhooks — some pelo direction, nunca pelo transfer_id, ou vai contar a mesma transferência duas vezes.
Dispara tanto pela transferência feita no painel quanto por POST /internal-transfers. counterparty é sempre o outro lado, visto de quem recebe o webhook.
⚠️ Receber transferência não exige módulo nenhum: qualquer conta iHubGames pode ser destino. Se você só recebe, ainda assim vale assinar este evento.
balance.converted — liquidação de câmbio:
{
"event": "balance.converted",
"environment": "LIVE",
"payload": {
"settlement_id": "b7d1e4a2-88c3-4f10-9a55-1c2d3e4f5a6b",
"from": { "currency": "MXN", "amount": 250000 },
"to": { "currency": "BRL", "amount": 75000 },
"quoted_rate": 0.306100,
"spread_percent": 2,
"rate": 0.300000,
"converted_at": "2026-09-10T14:02:11.004Z"
}
}
Chega quando o saldo que você mantém em moeda estrangeira é convertido para a sua moeda base. Não é uma ação sua — a liquidação é executada por nós, em lote, e converte o saldo disponível inteiro daquela moeda.
As três pontas da cotação vêm juntas, e cada uma responde uma pergunta diferente:
| Campo | O que é |
|---|---|
quoted_rate | cotação que o banco nos passou |
spread_percent | margem aplicada sobre ela |
rate | cotação de fato aplicada = quoted_rate × (1 − spread_percent/100) |
Para reproduzir o valor creditado use rate, não quoted_rate. A conversão arredonda para baixo, então from.amount × rate pode dar alguns centavos acima do to.amount recebido — o valor que vale é sempre o to.amount.
settlement_id identifica o lote e é o mesmo para todas as contas convertidas naquela execução. Ele aparece no seu histórico de conversões no painel.
ℹ️ Se a sua conta opera só em BRL, este evento nunca vai acontecer — ele depende de existir saldo em outra moeda.
Rastreio de origem (V2)
Todo webhook de cash-in (cashin.paid, cashin.refunded, deposit.credited, infraction.updated) carrega cinco campos extras que indicam de onde a transação veio. Permite roteamento e reconciliação direta no seu lado, sem ter que fazer GET /transactions/:id pra descobrir a origem.
{
"source_type": "QUICK_LINK",
"invoice_id": null,
"offer_id": null,
"product_id": null,
"quick_link_id": "ql_8f2c1a9d-3e4b-4a76-b8e1-9c0d2e3f4a5b"
}
Mapeamento source_type → IDs preenchidos:
source_type | Origem | IDs presentes (não-null) |
|---|---|---|
API | Criada via POST /transactions direto | nenhum |
INVOICE | Cobrança avulsa ou parcela recorrente, paga em /i/:invoiceId | invoice_id (+ offer_id e product_id se a Invoice veio de uma Offer) |
CHECKOUT_OFFER | Compra em /o/:userSlug/:offerSlug (catálogo público) | offer_id, product_id (+ invoice_id quando a Offer é recorrente) |
QUICK_LINK | Pagamento em /p/:userSlug/:linkSlug (link rápido) | quick_link_id |
Por que isso ajuda na prática:
- Reconciliação direta. Se você guarda
offer_id/quick_link_idno seu banco, dá pra fazer JOIN do webhook sem precisar de uma chamada à nossa API. - Roteamento por origem. Tratamento de infração em Invoice recorrente costuma ser diferente do tratamento em QuickLink (B2B vs campanha avulsa). Routeie por
source_typeantes de processar. - Métricas. Permite separar receita por canal (catálogo vs link rápido vs API direta) só com os webhooks.
ℹ️ Transações pré-V2: transações criadas antes do refator V2 podem chegar com
source_type: nulle todos os IDsnull. Trate onullcomo equivalente aAPI— origem desconhecida.
⚠️ Não confie só no
source_typepra decidir. Sempre cheque qual ID veio populado antes de fazer lookup — algumas combinações têm múltiplos IDs (ex:CHECKOUT_OFFERrecorrente trazoffer_id+product_id+invoice_id).
Observações operacionais
- Todos os valores estão em centavos.
1100é R$ 11,00, nunca R$ 1100,00. - Todos os timestamps são ISO 8601 em UTC.
external_idé a sua referência de negócio setada na criação. Use como sua chave de join ao persistir dados de webhook.end_to_end_idé único em todo o ecossistema PIX brasileiro e nunca colide. Use para conciliar com extrato bancário.- Reentregamos entregas que falharam com backoff exponencial por até 24 horas. Faça seu handler idempotente.
- Persista o
x-trace-idde toda entrega de webhook junto com a linha que você criar. É o caminho mais rápido pra investigar qualquer coisa estranha depois. - (V2) Quando precisar saber a origem da transação (Invoice/Offer/QuickLink/API direta), use os campos
source_type+ IDs polimórficos em vez de fazerGET /transactions/:id. Veja Rastreio de origem (V2).