Comment vérifier un webhook Qvard

Un guide rapide sur l'en-tête X-Qvard-Signature : ce que c'est et comment le vérifier avec quelques lignes de code.

Qu'est-ce qu'une signature

Lorsqu'un statut de paiement change, Qvard envoie une notification (webhook) à l'adresse de vos paramètres de boutique. N'importe qui peut envoyer une demande à cette adresse, vous avez donc besoin d'un moyen de distinguer une vraie notification d'une fausse.

La signature est cette preuve : un code court que Qvard calcule à partir du message et de votre clé secrète. Seul Qvard et vous connaissez la clé, donc seul Qvard peut produire le bon code.

Ce que vous recevez

Chaque webhook porte un en-tête supplémentaire et un petit corps JSON :

X-Qvard-Signature: t=1760950000,v1=3f2a…c91e {"id":"order_1024","status":"paid"}

t est l'heure à laquelle le message a été envoyé (Unix, secondes). v1 est le code. Pendant 24 heures après avoir renouvelé votre clé secrète, l'en-tête porte deux valeurs v1 : une pour la nouvelle clé et une pour l'ancienne.

Les champs id et status restent inchangés. Quatre champs montant sont ajoutés au même corps signé :

  • amount et currency : le montant et sa devise, tels que vous les avez envoyés dans la requête.
  • amountUsd : le montant comptable en USD.
  • rate : nombre d’unités de la devise pour 1 USD au moment de la création du paiement.
  • Les intégrations existantes continuent de fonctionner : elles lisent id et status et ignorent les nouveaux champs. La signature est vérifiée sur le même corps qu’auparavant.

Comment le vérifier

  1. 1
    Prenez le corps de la demande exactement comme il est arrivé : le texte brut, avant toute analyse JSON.
  2. 2
    Lisez t et chaque v1 de l'en-tête X-Qvard-Signature.
  3. 3
    Calculez HMAC-SHA256 du texte t + "." + corps avec votre clé secrète (qvard_sk_…) et écrivez-le en hexadécimal minuscule.
  4. 4
    Si votre résultat correspond à n'importe quel v1 (comparez en temps constant), le message est authentique.
  5. 5
    Vérifiez que t n'est pas plus vieux que 5 minutes, afin qu'un ancien message ne puisse pas être rejeu.
  6. 6
    Ce n'est qu'alors que vous utiliserez les données et confirmerez le montant payé pour cette commande dans votre propre base de données.

Code prêt

const crypto = require('crypto');

function verifyQvardSignature(rawBody, header, secret, toleranceSec = 300) {
  const parts = header.split(',').map((p) => p.trim());
  const t = Number((parts.find((p) => p.startsWith('t=')) || '').slice(2));
  const sigs = parts.filter((p) => p.startsWith('v1=')).map((p) => p.slice(3));
  if (!t || sigs.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  return sigs.some((s) => {
    const got = Buffer.from(s, 'hex');
    return got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
}

// Use the RAW body: express.raw, not express.json
app.post('/qvard-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verifyQvardSignature(raw, req.get('X-Qvard-Signature') || '', process.env.QVARD_SECRET_KEY)) {
    return res.sendStatus(400);
  }
  const { id, status } = JSON.parse(raw);
  // Confirm the paid amount for order `id` in your own database before fulfilling it.
  res.sendStatus(200);
});

Pendant 24 heures après avoir renouvelé votre clé secrète, l'en-tête porte deux valeurs v1 : acceptez le webhook si l'une d'elles correspond.

Erreurs courantes

  • L'analyse du JSON et sa nouvelle sérialisation modifie le texte. Utilisez toujours le corps brut.
  • Utiliser la clé publique (qvard_pk_…) au lieu de la clé secrète.
  • Une horloge serveur désynchronisée rend la vérification de 5 minutes échec. Gardez-la synchronisée.
  • Comparer avec == au lieu d'une comparaison en temps constant.

Où trouver votre clé secrète

Dans le tableau de bord : votre boutique, Paramètres, Clés API. Conservez-la uniquement sur votre serveur. Si elle fuit, renouvelez-la là : l'ancienne clé reste active pendant 24 heures pour les paiements (pas pour les retraits).

Ouvrir le tableau de bord

Référence API

Anciennes clés

Si votre boutique possède toujours une clé d'ancien format (sans le préfixe qvard_sk_), les webhooks portent également un en-tête x-api-key avec cette clé. Après son renouvellement, cet en-tête disparaît. Passez à la vérification de la signature.

Voulez-vous essayer la fenêtre de paiement ?

Ouvrir la démo