Webhooks
Aucune livraison, signature toujours invalide ou même événement reçu trois fois.
Les problèmes de webhook sont particulièrement simples à diagnostiquer, car Wajub enregistre chaque tentative de livraison et la réponse de votre endpoint. Commencez par ouvrir Konsole, Webhook Tester. Cet écran indique si Wajub a essayé et ce que votre serveur a répondu. Ces deux informations vous orientent vers la bonne partie de cette page.
Aucun webhook n'arrive
Si Konsole n'affiche aucune tentative, l'événement n'a jamais ciblé votre endpoint. Si des tentatives ont échoué, le problème se trouve de votre côté.
| Affichage dans Konsole | Cause | Solution |
|---|---|---|
| Aucune tentative | Aucun endpoint enregistré dans cet environnement | Enregistrez-le, la sandbox et le mode live sont distincts |
| Tentative, connexion refusée | L'URL n'est pas accessible depuis Internet | localhost ne l'est pas, utilisez wajub listen |
Tentative, 404 ou 405 | La route existe pour GET, pas pour POST | Les webhooks utilisent toujours POST |
Tentative, 5xx | Votre handler a levé une erreur | Consultez la dernière section de cette page |
Le développement local nécessite un tunnel, fourni par la CLI.
wajub listen --forward-to localhost:3000/webhooks/wajubRecevoir des webhooks en local présente les autres options. Déclencher des événements permet de produire un événement précis sans effectuer de paiement réel.
La signature reste toujours invalide
Il existe quatre causes. La première explique la plupart des cas.
Votre framework a analysé le corps avant le calcul du hash. La signature couvre les octets exacts envoyés par Wajub. Après l'analyse et la resérialisation par un middleware JSON, un ordre de clés ou une espace différente suffit à invalider le hash, même si l'objet reste identique. Récupérez le corps brut sur la route du webhook avant l'exécution de tout analyseur.
app.post('/webhooks/wajub', express.raw({ type: '*/*' }), (req, res) => {
const event = wajub.webhooks.constructEvent(
req.body,
req.headers['x-wajub-signature'],
req.headers['x-wajub-timestamp'],
);
res.sendStatus(200);
});
app.use(express.json());L'ordre de ces deux lignes est important. Si express.json() est monté en premier, il consomme le
corps de chaque route et votre handler brut reçoit un buffer vide.
Vous avez transmis le secret comme quatrième argument. constructEvent reçoit le payload, la
signature, l'horodatage et éventuellement une tolérance en secondes. Le secret de signature provient
du client construit avec webhookSecret. Le transmettre en quatrième position ne lève aucune erreur.
Il remplace la tolérance par une chaîne, ignore la vérification du décalage et désactive la protection
contre les répétitions.
Vous avez calculé le hash de la mauvaise chaîne. Le payload signé est
{timestamp}.{raw_body}, avec un point littéral entre les deux valeurs. L'en-tête contient un
préfixe v1= à retirer avant la comparaison.
Votre comparaison a levé une erreur au lieu de renvoyer false. crypto.timingSafeEqual lève une
RangeError lorsque les buffers ont des longueurs différentes, précisément le résultat d'une
signature tronquée ou mal formée. Comparez d'abord les longueurs, puis utilisez une comparaison en
temps constant. Vérification de signature
présente l'implémentation manuelle.
event.type vaut undefined
Le nom de l'événement se trouve dans event, pas dans type.
Adaptez votre traitement à event.event. Si une autre plateforme utilisait type, cette habitude
provoque l'erreur. Les SDKs Node et PHP typent actuellement l'événement analysé sous la forme
{ type, data }, ce qui ne correspond pas aux données reçues et sera corrigé. Fiez-vous au JSON
ci-dessus.
Le même événement arrive plusieurs fois
Ce comportement est prévu. Une livraison qui ne répond pas avec 2xx en 10 secondes est réessayée
cinq fois après 30 secondes, 1 minute, 5 minutes, 10 minutes et 1 heure. Un déploiement pendant cette
période ou un handler momentanément lent produit des doublons.
Dédupliquez avec id, qui reste identique lors de chaque nouvelle tentative de la même livraison.
Enregistrez-le dans un emplacement qui survit aux redémarrages. Un Set interne au processus se vide
pendant le déploiement qui a justement provoqué le retard à traiter.
CREATE TABLE processed_events (
event_id TEXT PRIMARY KEY,
processed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
INSERT INTO processed_events (event_id)
VALUES ($1)
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;Une ligne est renvoyée la première fois et aucune lors d'une répétition, sans concurrence entre la vérification et l'écriture.
Mon handler est lent ou lève une erreur
Le délai entre la connexion et la réponse est de 10 secondes. Toutes les opérations de livraison de la commande, comme une écriture en base de données, un PDF ou un e-mail, doivent avoir lieu après l'accusé de réception.
Vérifiez la signature, enregistrez l'identifiant de l'événement, répondez 200, puis placez
l'événement dans une file. Si le traitement échoue, réessayez depuis la file. Répondre 500 après
une erreur de livraison demande à Wajub de renvoyer l'événement. Le handler échoue encore et un bug
se reproduit cinq fois.
Le seul cas qui doit produire une réponse autre que 2xx est une signature invalide. Renvoyez 400 :
la requête ne vient pas de Wajub et aucune nouvelle livraison n'est nécessaire.
Un événement attendu ne s'est jamais déclenché
Comparez son nom au Catalogue des événements avant de conclure à
un bug. Deux erreurs sont fréquentes : invoice.paid n'existe pas et le résultat d'un remboursement
arrive sous la forme refund.succeeded, pas sur le paiement.
Si l'événement existe et que votre endpoint y est abonné, Konsole indique s'il a été généré. Un événement jamais généré signifie que le paiement n'a jamais atteint cet état. Consultez alors Paiements.
Pages associées