Estratégia de novas tentativas de webhooks em sistemas de pagamento
Como a Qvard repete um webhook de pagamento, o que conta como entrega falha e como construir um tratador que sobreviva a repetições, duplicatas e quedas sem cumprir um pedido duas vezes.

Um webhook de pagamento é uma mensagem que não pode se perder e não pode ser processada duas vezes. Redes falham e servidores reiniciam, então o remetente repete; é por isso que seu tratador precisa ser escrito pensando em repetições. Este guia descreve como a Qvard repete, o que ela conta como falha e como construir um tratador e uma rotina de recuperação em torno desse comportamento.
Quando um webhook é enviado
A Qvard envia o webhook quando um pagamento está concluído. É um POST para a callback URL salva no seu projeto, com um JSON pequeno contendo o seu id (o identificador que você informou ao criar o pagamento) e o status, assinado no cabeçalho X-Qvard-Signature.
Um pagamento a menor, rejeitado ou ainda aguardando não gera webhook de conclusão. Se você precisa saber desses pagamentos, olhe-os no painel; não espere uma mensagem que não é enviada.
O que conta como entregue
A Qvard considera uma entrega bem-sucedida somente quando seu endpoint responde exatamente HTTP 200. Todo o resto é falha:
- qualquer outro código de status, incluindo
201,202,204e todos os4xxe5xx; - um timeout, porque seu servidor foi lento ou ficou inacessível;
- um redirecionamento: a Qvard não segue
301nem302, então uma regra que redirecionahttpparahttpsou acrescenta uma barra final faz a entrega falhar.
Essa precisão é deliberada. Um 204 No Content parece uma resposta perfeitamente boa e ainda assim é uma entrega falha. Devolva 200, com corpo vazio se quiser.
Como funcionam as novas tentativas
Depois de uma entrega falha, a Qvard tenta de novo. No total são cinco tentativas em cerca de seis minutos. Duas consequências:
- Uma queda curta é coberta. Se seu servidor reinicia ou um deploy leva um ou dois minutos, uma tentativa posterior tem sucesso e você não perde nada.
- Uma queda mais longa não. Após a última tentativa a Qvard para. Se seu servidor ficou fora do ar por mais que esses seis minutos, a notificação não chega sozinha; reenvie-a para esse pagamento pelo painel.
Por causa das repetições, seu tratador às vezes verá o mesmo evento mais de uma vez, por exemplo quando seu servidor fez o trabalho mas a resposta se perdeu na volta. O exemplo abaixo foi construído para isso: a atualização só altera um pedido ainda não pago, o cumprimento só é enfileirado quando uma linha mudou e a resposta é 200 de qualquer forma.
import os
from fastapi import FastAPI, Request, Response
from qvard import Qvard
app = FastAPI()
SECRET = os.environ["QVARD_SECRET_KEY"]
qvard = Qvard(SECRET)
@app.post("/webhooks/qvard")
async def qvard_webhook(request: Request):
try:
body = qvard.webhooks.construct_event(await request.body(), request.headers.get("X-Qvard-Signature", ""), SECRET)
except ValueError:
return Response(status_code=400)
order_id, status = body.get("id"), body.get("status")
# Idempotent: the UPDATE changes a row only for an order that is not paid yet.
if status == "completed":
changed = await db.execute(
"UPDATE orders SET state = 'paid' WHERE id = :id AND state <> 'paid'", {"id": order_id}
)
if changed:
await queue.enqueue("fulfil", order_id) # slow work after the answer
return Response(status_code=200) # exactly 200, also for repeats and unknown ordersO que seu tratador deve fazer
Quatro regras cobrem quase todos os problemas:
- Verificar a assinatura. Compare o cabeçalho
X-Qvard-Signature(HMAC-SHA256 com a sua chave secreta sobre o timestamp e o corpo bruto) em tempo constante e responda400caso contrário. Verifique-a antes de agir sobre o pagamento. Veja como proteger webhooks de pagamentos em cripto. - Ser idempotente. Torne condicional a mudança de estado, «marcar pago se ainda não estiver pago», idealmente em uma única instrução de banco de dados, para que duas entregas paralelas não vençam ambas.
- Responder rápido. Conferir, atualizar, enfileirar e responder exatamente 200. Faça e-mails, geração de arquivos e chamadas a outros sistemas a partir de uma fila, para que um serviço lento não vire timeout no webhook.
- Responder 200 ao que você nunca vai tratar. Um pedido desconhecido ou uma repetição também devem devolver 200; do contrário a Qvard continua repetindo uma mensagem que você já tratou ou nunca tratará.
Falhas que parecem bugs da Qvard
A maioria dos relatos de «o webhook não chegou» tem causa no lado receptor:
- Um firewall ou WAF bloqueando requisições de endereços desconhecidos, ou uma página de «em breve» ou de manutenção.
- Um redirecionamento de
httpparahttps, dewwwpara o domínio sem prefixo ou para uma barra final. - Um erro do framework depois de a resposta já estar decidida, por exemplo uma exceção em um middleware que transforma seu 200 em 500.
- Um tratador lento que faz o trabalho antes de responder e esbarra em um timeout.
- Um cache ou CDN na frente da rota.
- A URL errada salva no projeto, ou um erro de digitação na rota.
Teste o endpoint de fora da sua rede com uma requisição que leve um cabeçalho X-Qvard-Signature válido e confira no log de acesso do servidor o código da resposta, não apenas se a requisição chegou.
Uma rotina de recuperação para notificações perdidas
As novas tentativas cobrem quedas curtas; para o resto, adicione uma rede de segurança.
- Registre cada entrega com o
id, ostatus, sua decisão (aplicado, repetição, pedido desconhecido, chave rejeitada) e a hora. Uma linha ausente no log é como você percebe uma lacuna. - Concilie em horário fixo. Uma vez por dia, compare os pedidos que você ainda considera não pagos com os pagamentos e o saldo no painel da Qvard. Tudo que o painel mostra como concluído e você como não pago é uma notificação perdida.
- Reenvie pelo painel esses pagamentos e deixe o tratador idempotente aplicá-los.
- Alerte quando uma sequência de requisições receber erro, para saber de um endpoint quebrado em minutos e não no fim do dia.
O restante da integração, incluindo a criação do pagamento, está em como aceitar pagamentos em cripto em um site e API de pagamentos em cripto para desenvolvedores.
FAQ
Quantas vezes a Qvard repete um webhook?
São cinco tentativas no total em cerca de seis minutos. Depois disso a Qvard para, e você pode reenviar a notificação do pagamento pelo painel.
Que código de status meu endpoint deve devolver?
Exatamente 200. Qualquer outro status, incluindo 204, um timeout ou um redirecionamento conta como entrega falha e é repetido.
O mesmo webhook pode chegar duas vezes?
Sim, por exemplo quando seu servidor processou a primeira entrega mas a resposta se perdeu. Torne o tratador idempotente e ainda assim responda 200 às repetições.
Recebo um webhook para um pagamento a menor ou rejeitado?
Não. O webhook é enviado quando um pagamento é concluído. Confira os demais pagamentos no painel.
