Boas práticas
O que separa uma integração que passa no teste de uma que aguenta produção.
1. Mande sempre o externalId
É o número do pedido no seu sistema, e é o que torna a criação idempotente.
POST /v1/charges {"externalId": "pedido-8891", …}
Se a resposta se perder — timeout, deploy no meio, cliente que clicou duas
vezes — repetir a mesma requisição devolve a mesma cobrança, com 200 em
vez de 201. Sem externalId, cada requisição cria um QR novo, e o seu
cliente fica com dois QRs para o mesmo pedido.
Se o corpo for diferente com o mesmo externalId, a resposta é 409 external_id_conflict — dois pedidos disputando um número é bug seu, e é melhor
descobrir na hora.
Para o retry cego de uma biblioteca de HTTP, mande também o cabeçalho
Idempotency-Key com um UUID por tentativa lógica.
2. Deduplique pelo id do evento
A entrega do webhook é at-least-once. Guarde event.id numa tabela com chave
única e ignore o que já processou. Isto não é opcional: reenvio manual do
painel e retentativa nossa produzem o mesmo evento duas vezes de propósito.
3. Confirme por GET antes de liberar produto
O webhook chega antes, mas é um POST na sua URL pública. Depois de validar a
assinatura, confirme o estado com GET /v1/charges/{id} e decida em cima da
resposta.
Uma requisição a mais por venda é barato perto de liberar produto por um evento que não era nosso.
4. Trate o atraso de liquidação
Este é o ponto que mais dá problema em integração nova.
Pix pago ──── até 24h ────► DePix na sua carteira
│ │
paid_pending_settlement settled
- Não libere produto em
paid_pending_settlement. O dinheiro saiu do banco do seu cliente, mas ainda não é seu. - Mostre
settlement.estimatedAtna tela do pedido: é a data em que o DePix deve sair, e é o que evita o chamado "paguei e não recebi". - Se você vende algo instantâneo (jogo, crédito, assinatura), decida
conscientemente se assume o risco de liberar antes — e assuma sabendo que
charge.refundedexiste.
5. Nunca confie só no webhook
Endpoint fora do ar, deploy no meio da tarde, firewall que bloqueou nosso IP: tudo isso acontece. Tenha uma rede de segurança:
- uma rotina que roda de tempos em tempos e consulta
GET /v1/charges?status=paid_pending_settlementpara achar o que ficou para trás; - e o mesmo para pedidos seus que estão abertos há mais tempo que o esperado.
6. Guarde o requestId de todo erro
Toda resposta de erro traz error.requestId. Grave no seu log junto do pedido.
É por ele que o suporte acha a requisição — sem ele, a investigação começa por
"mais ou menos que horas foi?".
7. Repita o que dá para repetir
429— respeite oRetry-After.409 charge_processing— repita em 1 segundo.503e 5xx — espera crescente.4xxem geral — não repita igual; corrija.
Ver a tabela de erros.
8. A chave é segredo de servidor
- Nunca no navegador, nunca em app mobile, nunca no repositório.
- Uma chave por integração, com nome que diga qual é: revogar a da loja antiga não pode derrubar a nova.
- Rotacione trocando: crie a nova, publique, confirme que rodou, revogue a velha. O painel mostra o último uso de cada uma.
- O segredo do webhook é outro e tem outra função: a chave de API prova que é você chamando; o segredo do webhook prova que somos nós chamando. Vazar um não pode entregar o outro.
9. Teste no sandbox o que dói em produção
A chave p2d_test_ roda o ciclo inteiro sem dinheiro. Exercite antes de ligar:
- o que a sua tela mostra em
paid_pending_settlement; - o seu código de validação de assinatura, com
POST /v1/webhooks/test; - o caminho de
charge.expirede o decharge.refunded; - o seu deduplicador, reenviando a mesma entrega pelo painel duas vezes.
10. Cancelou? Tire o QR da tela
POST /v1/charges/{id}/cancel fecha a cobrança do nosso lado, mas o QR Code já
emitido continua tecnicamente pagável — nosso provedor de Pix não tem como
invalidá-lo.
Se o seu cliente pagar assim mesmo, o pagamento vence: a cobrança volta a andar
e você recebe charge.paid_pending_settlement. Trate esse caso, e sobretudo
tire o QR da frente dele ao cancelar.