Webhook e idempotência em uma integração PIX ↔ stablecoin
A regra de ouro
1. gere external_id no seu banco 2. persista o pedido como "creating" 3. chame a Lunium com o mesmo external_id 4. em timeout, repita exatamente a mesma chamada 5. aceite webhook repetido sem repetir efeito 6. reconcilie por consulta quando houver dúvida
Consumidor de webhook
Leia o corpo bruto
A assinatura foi calculada sobre os bytes recebidos. Validar um JSON reserializado pode produzir outra sequência.
Valide timestamp e assinatura
Rejeite assinatura inválida e janela antiga. Compare o HMAC em tempo constante.
Persista por identificador do evento
Uma restrição única torna o consumo idempotente. Evento repetido deve responder sucesso sem aplicar o efeito outra vez.
Aplique transição válida
Não retroceda uma operação concluída porque um evento anterior chegou atrasado.
Responda 2xx depois do commit
Se a persistência falhar, responda erro para permitir nova entrega. Não confirme antes de salvar.
Pseudocódigo seguro
const raw = await request.rawBody()
verifyLuniumSignature(request.headers, raw)
const event = JSON.parse(raw)
await db.transaction(async tx => {
if (await tx.events.exists(event.event_id)) return
await tx.events.insert(event.event_id, event)
await tx.orders.applyForwardTransition(event.data)
})
return response.status(204)
Como interpretar falhas
Timeout na criação
Repita com o mesmo external_id. Não crie um identificador novo.
Webhook repetido
Responda 2xx depois de confirmar que o evento já foi persistido.
Webhook atrasado
Valide a transição; não sobrescreva um estado terminal mais novo.
Webhook perdido
Use consulta de status e histórico de entregas para reconciliar.
Teste antes de produção
Configure uma URL HTTPS pública na chave e use POST /webhooks/test. Depois valide retry, assinatura errada, evento repetido e indisponibilidade temporária do seu endpoint.
Quer revisar seu consumidor antes do primeiro PIX?
Envie somente a estrutura do fluxo e um request_id de teste — nunca a chave, CPF, carteira ou payload com segredo.