Estrategia de reintentos de webhooks en sistemas de pago
Cómo reintenta Qvard un webhook de pago, qué cuenta como entrega fallida y cómo construir un manejador que sobreviva a reintentos, repeticiones y caídas sin cumplir un pedido dos veces.

Un webhook de pago es un mensaje que no debe perderse y no debe procesarse dos veces. Las redes fallan y los servidores se reinician, así que el remitente reintenta; por eso tu manejador debe escribirse pensando en las repeticiones. Esta guía describe cómo reintenta Qvard, qué considera un fallo y cómo construir un manejador y una rutina de recuperación en torno a ese comportamiento.
Cuándo se envía un webhook
Qvard envía el webhook cuando un pago está completado. Es un POST a la callback URL guardada en tu proyecto, con un JSON pequeño que contiene tu id (el identificador que diste al crear el pago) y el status, firmado en la cabecera X-Qvard-Signature.
Un pago corto, rechazado o que sigue esperando no genera un webhook de finalización. Si necesitas saber de esos pagos, míralos en el panel; no esperes un mensaje que no se envía.
Qué cuenta como entregado
Qvard considera una entrega exitosa solo cuando tu endpoint responde exactamente HTTP 200. Todo lo demás es un fallo:
- cualquier otro código de estado, incluidos
201,202,204y todos los4xxy5xx; - un tiempo de espera agotado, porque tu servidor fue lento o inalcanzable;
- una redirección: Qvard no sigue
301ni302, así que una regla que redirigehttpahttpso añade una barra final hace fallar la entrega.
Esta precisión es deliberada. Un 204 No Content parece una respuesta perfectamente buena y aun así es una entrega fallida. Devuelve 200, con cuerpo vacío si quieres.
Cómo funcionan los reintentos
Tras una entrega fallida, Qvard lo intenta de nuevo. En total hay cinco intentos en unos seis minutos. De ello se siguen dos consecuencias:
- Una caída corta queda cubierta. Si tu servidor se reinicia o un despliegue tarda uno o dos minutos, un intento posterior tiene éxito y no pierdes nada.
- Una caída más larga no. Tras el último intento Qvard se detiene. Si tu servidor estuvo caído más de esos seis minutos, la notificación no llega sola; reenvíala para ese pago desde el panel.
Por los reintentos, tu manejador verá a veces el mismo evento más de una vez, por ejemplo cuando tu servidor hizo el trabajo pero la respuesta se perdió al volver. El ejemplo siguiente está construido para eso: la actualización solo cambia un pedido aún no pagado, el cumplimiento se encola solo cuando una fila cambió y la respuesta es 200 en cualquier caso.
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 ordersQué debe hacer tu manejador
Cuatro reglas cubren casi todos los problemas:
- Verificar la firma. Comprueba la cabecera
X-Qvard-Signature(HMAC-SHA256 con tu clave secreta sobre el timestamp y el cuerpo sin procesar) en tiempo constante y responde400si no coincide. Verifícala antes de actuar sobre el pago. Consulta cómo proteger los webhooks de pagos cripto. - Ser idempotente. Haz condicional el cambio de estado, «marcar pagado si aún no está pagado», idealmente en una sola sentencia de base de datos, para que dos entregas paralelas no ganen ambas.
- Responder rápido. Comprobar, actualizar, encolar y responder exactamente 200. Haz los correos, la generación de archivos y las llamadas a otros sistemas desde una cola, para que un servicio lento no se convierta en un tiempo de espera del webhook.
- Responder 200 a lo que nunca vas a tratar. Un pedido desconocido o una repetición también deben devolver 200; si no, Qvard sigue reintentando un mensaje que ya gestionaste o que nunca gestionarás.
Fallos que parecen errores de Qvard
La mayoría de los informes de «el webhook no llegó» tienen una causa en el lado receptor:
- Un cortafuegos o WAF que bloquea solicitudes de direcciones desconocidas, o una página de «próximamente» o de mantenimiento.
- Una redirección de
httpahttps, dewwwal dominio sin prefijo o a una barra final. - Un error del framework después de decidida la respuesta, por ejemplo una excepción en un middleware que convierte tu 200 en un 500.
- Un manejador lento que hace el trabajo antes de responder y choca con un tiempo de espera.
- Una caché o CDN delante de la ruta.
- La URL equivocada guardada en el proyecto, o una errata en la ruta.
Prueba el endpoint desde fuera de tu red con una solicitud que lleve una cabecera X-Qvard-Signature válida y revisa en el registro de acceso de tu servidor el código de respuesta, no solo si llegó la solicitud.
Una rutina de recuperación para notificaciones perdidas
Los reintentos cubren caídas cortas; para el resto, añade una red de seguridad.
- Registra cada entrega con el
id, elstatus, tu decisión (aplicado, repetición, pedido desconocido, clave rechazada) y la hora. Una línea ausente en el registro es la forma de notar un hueco. - Concilia con un calendario. Una vez al día, compara los pedidos que aún consideras sin pagar con los pagos y el saldo de tu panel de Qvard. Todo lo que el panel muestre como completado y tú como sin pagar es una notificación perdida.
- Reenvía desde el panel esos pagos y deja que el manejador idempotente los aplique.
- Alerta cuando una racha de solicitudes recibe un error, para enterarte de un endpoint roto en minutos y no al final del día.
El resto de la integración, incluida la creación del pago, está en cómo aceptar pagos cripto en un sitio web y API de pagos cripto para desarrolladores.
FAQ
¿Cuántas veces reintenta Qvard un webhook?
Hay cinco intentos en total en unos seis minutos. Después Qvard se detiene, y puedes reenviar la notificación del pago desde el panel.
¿Qué código de estado debe devolver mi endpoint?
Exactamente 200. Cualquier otro estado, incluido 204, un tiempo de espera agotado o una redirección cuenta como entrega fallida y se reintenta.
¿Puede llegar dos veces el mismo webhook?
Sí, por ejemplo cuando tu servidor procesó la primera entrega pero la respuesta se perdió. Haz el manejador idempotente y responde 200 igualmente a las repeticiones.
¿Recibo un webhook por un pago corto o rechazado?
No. El webhook se envía cuando un pago se completa. Comprueba los demás pagos en el panel.
