Webhooks

Stratégie de nouvelles tentatives des webhooks dans les systèmes de paiement

Comment Qvard réessaie un webhook de paiement, ce qui compte comme livraison échouée et comment construire un gestionnaire qui survit aux nouvelles tentatives, aux répétitions et aux pannes sans exécuter une commande deux fois.

Un webhook de paiement est un message qui ne doit pas être perdu et sur lequel on ne doit pas agir deux fois. Les réseaux tombent et les serveurs redémarrent, l'expéditeur réessaie donc ; c'est pourquoi votre gestionnaire doit être écrit en pensant aux répétitions. Ce guide décrit comment Qvard réessaie, ce qu'il considère comme un échec et comment construire un gestionnaire et une routine de reprise autour de ce comportement.

Quand un webhook est envoyé

Qvard envoie le webhook lorsqu'un paiement est terminé. C'est un POST vers la callback URL enregistrée dans votre projet, avec un petit JSON contenant votre id (l'identifiant que vous avez donné à la création du paiement) et le status, signé dans l'en-tête X-Qvard-Signature.

Un paiement insuffisant, rejeté ou encore en attente ne produit pas de webhook de fin. Si vous avez besoin de connaître ces paiements, regardez-les dans l'espace client ; n'attendez pas un message qui n'est pas envoyé.

Ce qui compte comme livré

Qvard ne considère une livraison réussie que lorsque votre point de terminaison répond exactement HTTP 200. Tout le reste est un échec :

  • tout autre code de statut, y compris 201, 202, 204 et tous les 4xx et 5xx ;
  • un délai dépassé, parce que votre serveur a été lent ou injoignable ;
  • une redirection : Qvard ne suit ni 301 ni 302 ; une règle qui redirige http vers https ou ajoute une barre finale fait donc échouer la livraison.

Cette précision est voulue. Un 204 No Content ressemble à une très bonne réponse et reste une livraison échouée. Renvoyez 200, avec un corps vide si vous voulez.

Comment fonctionnent les nouvelles tentatives

Après une livraison échouée, Qvard réessaie. Au total, il y a cinq tentatives en environ six minutes. Deux conséquences :

  • Une courte panne est couverte. Si votre serveur redémarre ou qu'un déploiement prend une ou deux minutes, une tentative ultérieure réussit et vous ne perdez rien.
  • Une panne plus longue ne l'est pas. Après la dernière tentative, Qvard s'arrête. Si votre serveur est resté arrêté plus de ces six minutes, la notification n'arrive pas toute seule ; renvoyez-la pour ce paiement depuis l'espace client.

À cause des nouvelles tentatives, votre gestionnaire verra parfois le même événement plus d'une fois, par exemple quand votre serveur a fait le travail mais que la réponse s'est perdue au retour. L'exemple ci-dessous est construit pour cela : la mise à jour ne modifie qu'une commande pas encore payée, l'exécution n'est mise en file que lorsqu'une ligne a changé, et la réponse est 200 dans tous les cas.

Pythonwebhook.py
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 orders

Ce que doit faire votre gestionnaire

Quatre règles couvrent presque tous les problèmes :

  1. Vérifier la signature. Contrôlez l'en-tête X-Qvard-Signature (HMAC-SHA256 avec votre clé secrète, sur le corps brut) en temps constant et répondez 400 sinon. Faites-le avant d'agir sur le paiement. Voir comment sécuriser les webhooks de paiement crypto.
  2. Être idempotent. Rendez le changement d'état conditionnel, « marquer payé si pas déjà payé », idéalement en une seule instruction de base de données, pour que deux livraisons parallèles ne gagnent pas toutes les deux.
  3. Répondre vite. Vérifier, mettre à jour, mettre en file, répondre exactement 200. Faites les e-mails, la génération de fichiers et les appels à d'autres systèmes depuis une file, pour qu'un service lent ne devienne pas un délai dépassé du webhook.
  4. Répondre 200 à ce que vous ne traiterez jamais. Une commande inconnue ou une répétition doivent aussi renvoyer 200 ; sinon Qvard continue de réessayer un message que vous avez déjà traité ou ne traiterez jamais.

Des pannes qui ressemblent à des bugs de Qvard

La plupart des signalements « le webhook n'est pas arrivé » ont une cause côté réception :

  • Un pare-feu ou un WAF qui bloque les requêtes d'adresses inconnues, ou une page « bientôt disponible » ou de maintenance.
  • Une redirection de http vers https, de www vers le domaine nu ou vers une barre finale.
  • Une erreur du framework une fois la réponse déjà décidée, par exemple une exception dans un middleware qui transforme votre 200 en 500.
  • Un gestionnaire lent qui fait le travail avant de répondre et se heurte à un délai dépassé.
  • Un cache ou un CDN devant la route.
  • La mauvaise URL enregistrée dans le projet, ou une faute de frappe dans la route.

Testez le point de terminaison depuis l'extérieur de votre réseau avec une requête portant un en-tête X-Qvard-Signature valide, et regardez dans le journal d'accès de votre serveur le code de réponse, pas seulement si la requête est arrivée.

Une routine de reprise pour les notifications manquées

Les nouvelles tentatives couvrent les courtes pannes ; pour le reste, ajoutez un filet de sécurité.

  • Journalisez chaque livraison avec l'id, le status, votre décision (appliqué, répétition, commande inconnue, clé rejetée) et l'heure. Une ligne manquante dans le journal est la façon de repérer un trou.
  • Rapprochez selon un calendrier. Une fois par jour, comparez les commandes que vous considérez encore comme impayées avec les paiements et le solde de votre espace Qvard. Tout ce que l'espace client montre comme terminé et que vous montrez comme impayé est une notification manquée.
  • Renvoyez depuis l'espace client ces paiements et laissez le gestionnaire idempotent les appliquer.
  • Alertez quand une série de requêtes reçoit une erreur, pour apprendre en quelques minutes, et non en fin de journée, qu'un point de terminaison est cassé.

Le reste de l'intégration, y compris la création du paiement, se trouve dans comment accepter les paiements crypto sur un site web et API de paiement crypto pour les développeurs.

FAQ

Combien de fois Qvard réessaie-t-il un webhook ?

Cinq tentatives au total en environ six minutes. Ensuite Qvard s'arrête, et vous pouvez renvoyer la notification du paiement depuis l'espace client.

Quel code de statut mon point de terminaison doit-il renvoyer ?

Exactement 200. Tout autre statut, y compris 204, un délai dépassé ou une redirection compte comme une livraison échouée et est réessayé.

Le même webhook peut-il arriver deux fois ?

Oui, par exemple quand votre serveur a traité la première livraison mais que la réponse s'est perdue. Rendez le gestionnaire idempotent et répondez quand même 200 aux répétitions.

Reçois-je un webhook pour un paiement insuffisant ou rejeté ?

Non. Le webhook est envoyé lorsqu'un paiement est terminé. Vérifiez les autres paiements dans l'espace client.

Articles similaires

Articles similaires