Skip to main content
Webhook deve ser o transporte principal. Reconciliação é a rede de segurança quando a entrega ou o processamento downstream ficam incertos.

Fluxo recomendado do consumidor

  1. Receba o evento.
  2. Faça validação básica de schema.
  3. Persista o ID do evento para deduplicação.
  4. Responda 200 OK.
  5. Processe de forma assíncrona.
  6. Reconcilie com GET se não for possível determinar o estado final com segurança.

Design seguro para retentativas

Seu consumidor deve ser seguro quando o mesmo webhook for entregue mais de uma vez.

Chave de deduplicação

Use o id de topo como chave de deduplicação tanto para pagamentos quanto para transferências.

Exemplo de tabela de eventos

create table pagou_webhook_events (
  event_id text primary key,
  event_kind text not null,
  resource_id text null,
  received_at timestamptz not null default now(),
  processed_at timestamptz null,
  payload jsonb not null
);

Exemplo de processamento

app.post("/webhooks/pagou", async (req, res) => {
	const eventId = String(req.body?.id ?? "");

	if (!eventId) {
		return res.status(400).json({ error: "missing_event_id" });
	}

	const inserted = await insertWebhookIfNew(eventId, req.body);
	res.status(200).json({ received: true });

	if (!inserted) {
		return;
	}

	await queue.enqueue({ eventId });
});

Quando reconciliar

Reconcilie quando:
  • seu worker cair depois do ack
  • alguma dependência interna der timeout
  • uma ação de fulfillment ou payout falhar no meio
  • suporte ou financeiro precisar do estado mais recente
  • um pagamento ou payout permanecer tempo demais em estado não final

Endpoints de reconciliação

  • Pagamentos: GET /v2/transactions/{id}
  • Transferências: GET /v2/transfers/{id}

Fluxo de reconciliação

async function reconcileWebhook(payload: any) {
	if (payload?.event === "transaction") {
		const transactionId = payload?.data?.id;
		return await pagou.transactions.retrieve(transactionId);
	}

	if (payload?.type?.startsWith("payout.")) {
		const transferId = payload?.data?.object?.id;
		return await pagou.transfers.retrieve(transferId);
	}

	return null;
}

Atualizações forward-safe

No seu domínio, aplique apenas transições seguras para frente.
  • não volte um pedido pago para não pago por causa de lógica antiga
  • não liquide o mesmo payout duas vezes
  • não cumpra o pedido novamente se você já agiu em transaction.paid

Como separar pagamentos e payouts

function isPaymentWebhook(payload: any): boolean {
	return payload?.event === "transaction";
}

function isTransferWebhook(payload: any): boolean {
	return typeof payload?.type === "string" && payload.type.startsWith("payout.");
}

Guias relacionados