Nomba webhooks

Construisez un endpoint de webhook Nomba sécurisé qui vérifie la signature, déduplique les livraisons et réagit correctement à chaque événement (payment_success, payment_failed, payment_reversal, payout_success, payout_failed, payout_refund). Utilisez-le lorsque l'utilisateur souhaite recevoir, gérer, vérifier ou déboguer les webhooks Nomba ou les "notifications"/"callbacks" de Nomba, dans Express, Next.js, NestJS, Fastify, Flask, FastAPI, Django, Laravel, Symfony ou PHP pur, y compris lorsqu'il dit simplement "confirmer le paiement sur mon serveur", "écouter les virements réussis" ou colle une charge utile avec event_type et les en-têtes nomba-signature. Utilisez-le également lors de la révision d'un gestionnaire de webhook existant.

Qu'est-ce que Nomba webhooks ?

Ce skill fournit une implémentation complète et sécurisée pour gérer les webhooks Nomba. Il permet de vérifier la signature HMAC des requêtes entrantes en utilisant le SDK officiel de Nomba, garantissant que seules les notifications légitimes de Nomba sont traitées. Le skill couvre la déduplication des événements basée sur le `requestId`, empêchant les traitements multiples d'un même événement en cas de relivraison. Il détaille les actions à effectuer pour chaque type d'événement (`payment_success`, `payment_failed`, etc.), notamment la correspondance avec les enregistrements internes via des références uniques, la vérification du montant et de la devise avant d'exécuter des actions critiques (marquage comme payé, remboursement). De plus, il insiste sur la nécessité de ne jamais se fier uniquement aux données non signées (comme le `orderReference`) et de toujours effectuer une requête de confirmation via l'API Nomba avant toute action irréversible. Le skill inclut des exemples de code pour Node.js, Python et PHP, ainsi qu'une checklist de sécurité pour le déploiement en production.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/NombaOSS/skills/tree/HEAD/skills/nomba-webhooks

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait Nomba webhooks ?

Un webhook est la manière dont Nomba informe votre serveur que de l'argent a effectivement été transféré. N'importe qui sur Internet peut envoyer une requête POST à votre URL de webhook, donc le gestionnaire doit prouver que chaque requête provient bien de Nomba avant de marquer une commande comme payée ou de rembourser quiconque. Il doit également gérer les tentatives de relivraison de Nomba.

Utilisez le vérificateur du SDK officiel ; n'implémentez pas le HMAC vous-même. La signature de Nomba couvre des champs spécifiques joints par :, et les SDK bloquent également une astuce de rejeu qu'une implémentation naïve manque.

LangageVérification
Node.js / TS (@nombaoss/nomba-node)verifyWebhook(rawBody, headers, signingKey)
Python (nomba)from nomba.webhooks import verify_signature → verify_signature(raw_body, headers, signing_key)
PHP (nombaoss/nomba-php)Nomba\Webhook::verify($rawBody, $headers, $signingKey)

Les imports exacts et les types d'arguments pour chaque langage se trouvent dans références/sdk-setup.md. Chacun retourne l'événement analysé ou lève une exception (WebhookVerificationError / WebhookVerificationException). Aucun d'eux n'a besoin d'identifiants API, seulement la clé de signature du webhook depuis le tableau de bord Nomba (Développeur → Configuration du Webhook). Lisez-la depuis une variable d'environnement telle que NOMBA_WEBHOOK_SIGNING_KEY. C'est un secret différent du secret client de l'API.

Le gestionnaire, étape par étape

  1. Obtenez le corps brut. Vérifiez les octets exacts que Nomba a envoyés. Si un analyseur de corps JSON s'exécute en premier et que vous resérialisez l'objet, la signature peut cesser de correspondre, ou pire, vous finissez par vérifier autre chose que ce sur quoi vous agissez. Chaque framework a une manière différente d'obtenir le corps brut ; voir références/frameworks.md pour la bonne méthode, et excluez la route de la protection CSRF là où le framework en dispose.
  2. Vérifiez, puis analysez. Utilisez l'événement retourné par le vérificateur. En cas d'échec, répondez 401 (ou 400) et arrêtez-vous. Ne journalisez pas le corps complet ou les en-têtes des requêtes rejetées au niveau info.
  3. Dédupliquez sur requestId. Nomba relivre les événements, donc le même événement peut arriver plusieurs fois. Enregistrez chaque requestId traité avec une contrainte d'unicité et ignorez ceux déjà vus. Retournez 200 également pour les doublons, sinon Nomba continue de relivrer.
  4. Accusez réception rapidement. Retournez 200 rapidement et effectuez les travaux lents (emails, exécution des commandes, mises à jour de comptabilité) dans une tâche en arrière-plan indexée par requestId. Des gestionnaires lents provoquent des dépassements de délai et des relivraisons.
  5. Agissez sur l'événement en utilisant le tableau ci-dessous. Faites correspondre l'événement à votre propre enregistrement par référence, et vérifiez que le montant et la devise correspondent à ce que vous attendiez avant d'exécuter.

Ce que signifie chaque événement

event_typeCorrespondance surAction
payment_successcheckout : data.order.orderReference ; compte virtuel / virement entrant : data.transaction.aliasAccountReference ou transactionIdMarquez comme payé après avoir vérifié data.transaction.transactionAmount (et la devise) par rapport à votre commande. Pour les checkouts tokenisés, stockez data.tokenizedCardData.tokenKey (s'il est présent et non "N/A") avec le client, côté serveur uniquement.
payment_failedidentique à ci-dessusMarquez la tentative comme échouée ; laissez le client réessayer. Rien n'a été débité.
payment_reversalidentique à ci-dessusL'argent que vous avez reçu a été reversé. Annulez l'exécution ou signalez pour révision.
payout_successdata.transaction.merchantTxRefMarquez le virement ou le paiement de facture comme effectué.
payout_faileddata.transaction.merchantTxRefNe remboursez pas encore. Nomba peut réessayer par une autre voie. Marquez-le comme échoué et attendez.
payout_refunddata.transaction.merchantTxRefL'argent est de retour dans votre portefeuille Nomba. Maintenant, il est sûr de rembourser ou de créditer votre client et de marquer le virement comme échoué.

Rembourser sur payout_failed (ou sur un dépassement de délai) au lieu de payout_refund est la manière classique pour les marchands de payer deux fois leurs clients. Pour tout événement que vous ne reconnaissez pas, journalisez-le et retournez 200.

Confirmez les décisions financières avec l'API. La signature de Nomba ne couvre que event_type, requestId, data.merchant.userId, data.merchant.walletId, data.transaction.transactionId, type, time, responseCode et le timestamp. Le montant, orderReference, merchantTxRef et le tokenKey de la carte ne sont pas signés. Par conséquent, avant d'exécuter, rembourser ou stocker un jeton de carte, réinterrogez avec le SDK et agissez sur la réponse de l'API : transactions.get({ orderReference }) pour les paiements par checkout (également checkout.getTransaction en production) et transactions.get({ merchantTxRef }) pour les virements. La déduplication sur le requestId signé empêche une copie modifiée d'un événement déjà traité, et la réinterrogation couvre le reste.

Protection anti-rejeu

La déduplication sur requestId est la défense principale : une relivraison rejouée ou retentée d'un événement déjà traité ne change rien.

Les vérificateurs peuvent également rejeter les timestamps anciens (toleranceSeconds / tolerance_seconds / le quatrième argument en PHP), mais laissez-le désactivé par défaut. Nomba documente nomba-timestamp comme l'heure à laquelle la charge utile a été envoyée, sans préciser si les nouvelles tentatives automatiques en reçoivent un nouveau. S'ils conservent l'original, une tolérance plus courte que la fenêtre de relivraison de Nomba rejetterait les relivraisons légitimes après une panne, et les commandes payées ne seraient jamais confirmées. Activez-le seulement après avoir confirmé dans le bac à sable que les relivraisons portent un nouveau timestamp, puis conservez-le généreux (par exemple une heure) avec les horloges des serveurs synchronisées.

Exemple (Node.js + Express)

import express from "express";
import { verifyWebhook, WebhookEventType } from "@nombaoss/nomba-node";

const app = express();

// Corps brut pour cette route uniquement ; enregistrez-la avant tout app-wide express.json().
app.post("/webhooks/nomba", express.raw({ type: "*/*" }), async (req, res) => {
  let event;
  try {
    event = verifyWebhook(req.body, req.headers, process.env.NOMBA_WEBHOOK_SIGNING_KEY!);
  } catch {
    return res.sendStatus(401);
  }

  // Contrainte d'unicité sur request_id ; retourne false si déjà vu.
  const isNew = await webhookEvents.recordOnce(event.requestId, event.event_type);
  if (isNew) await jobs.enqueue("nomba-webhook", event); // traiter en arrière-plan
  res.sendStatus(200);
});

Python (Flask/FastAPI/Django), PHP (Laravel/plain) et autres frameworks Node sont dans références/frameworks.md.

Test

  • Testez unitairement le gestionnaire avec le vecteur de test documenté du SDK : corps depuis webhook-fixture.json du SDK, clé SampleSignatureKey123, en-tête nomba-timestamp: 2025-09-29T10:51:44Z, nomba-signature: q30yFZDyykoymBf9+mt1wz5UAuLk57raPGPHlGzIv00=. Vérifiez qu'une mauvaise clé, un corps modifié et des en-têtes manquants sont rejetés, et qu'un requestId en double est traité une seule fois.
  • De bout en bout : définissez une URL de webhook de test et une clé de signature dans le tableau de bord, exposez votre serveur local avec un tunnel, et effectuez des paiements dans le bac à sable. Le tableau de bord peut repousser ou déboguer les livraisons (https://developer.nomba.com/docs/api-basics/troubleshoot-webhooks).

Checklist

  • Le corps brut est vérifié avec le SDK, avant tout analyse ou logique métier.
  • La clé de signature provient de l'environnement, pas du code.
  • Les mauvaises signatures obtiennent un 401/400 ; rien d'autre ne se produit.
  • requestId est dédupliqué avec une contrainte d'unicité ; les doublons obtiennent toujours 200.
  • Les travaux lourds s'exécutent en arrière-plan ; le gestionnaire retourne 200 rapidement.
  • Le montant et la devise sont vérifiés par rapport à la commande avant exécution.
  • Les remboursements ont lieu uniquement sur payout_refund (ou une réinterrogation retournant REFUND).
  • La route est exclue de la protection CSRF et utilise HTTPS.

Skills associés