Aller au contenu
StampyWalletDocs
API et webhooks / Webhooks / Webhooks

Webhooks

Bêta

Recevez un appel HTTP signé sur votre serveur, Zapier, Make ou n8n à chaque passage, récompense ou nouvelle carte, et vérifiez sa signature HMAC.

Un webhook est une adresse de votre serveur que StampyWallet appelle à chaque événement du comptoir : un passage, une récompense, une nouvelle carte, une carte cadeau vendue. C'est ce qui relie StampyWallet à une caisse, un CRM, un tableur ou un outil sans code, sans intégration officielle.

Dans l'autre sens ? : pour que votre caisse ajoute un tampon à chaque vente, utilisez POST /v1/passages, ou recevez les ventes de votre caisse directement.

Configurer une adresse

Dans Réglages › Intégrations › Webhooks (le propriétaire et les responsables) :

  1. Ajoutez une adresse https:// publique, et cochez si vous voulez des événements. Aucune case cochée : tous les événements, y compris ceux qui s'ajouteront plus tard.
  2. Copiez le secret de signature, qui commence par whsec_. Il n'est montré qu'une fois ; ensuite l'écran n'en affiche que les quatre derniers caractères.
  3. Cliquez sur « Envoyer un test » : un passage.cree d'exemple, marqué "test": true, part aussitôt vers votre adresse, et l'écran affiche la réponse obtenue.

L'écran montre les 20 derniers envois (statut, code, durée, essai) et un bouton Renvoyer sur ceux qui ont échoué. Dix adresses au plus par commerce ; chacune se désactive, se réactive ou se supprime.

Attention : sont refusées : une adresse qui n'est pas en https://, une adresse qui contient un identifiant (https://moi:secret@…), et toute adresse privée ou locale (localhost, 10.x, 192.168.x…), même obtenue par le DNS. Tous les ports sont acceptés. Les redirections ne sont jamais suivies : une réponse 3xx compte comme un échec.

Ce que reçoit votre serveur

Une requête POST en JSON, avec ces en-têtes :

En-têtes
POST /votre/adresse HTTP/1.1
Content-Type: application/json
User-Agent: StampyWallet-Webhooks/1.0
StampyWallet-Evenement: passage.cree
StampyWallet-Id: evt_WdGHduXPRbExh0vh5eYtCjXo
StampyWallet-Signature: t=1792648365,v1=8f3c…(64 caractères hexadécimaux)

Et ce corps. L'enveloppe est la même pour tous les événements ; seul donnees change (la liste des événements) :

passage.cree
{
  "id": "evt_WdGHduXPRbExh0vh5eYtCjXo",
  "type": "passage.cree",
  "cree_le": "2026-10-22T05:52:45.694Z",
  "commerce": { "id": "9f1cbdfc-df71-4fab-a554-3bdbb7c71ebf", "nom": "Le Café des Crochets" },
  "donnees": {
    "client": { "id": "40de4b86-1d7a-4cc6-add3-b7a83fa43c04", "prenom": "Inès",
                "telephone": "+33611223344", "email": "ines@example.com" },
    "passage": {
      "id": "226fdea1-ba59-49fb-93e6-60a14a765134",
      "quantite": 1,
      "montant": null,
      "devise": "EUR",
      "reference": "caisse-1",
      "source": "scan",
      "piste": "normal",
      "tampons": 1,
      "objectif": 2
    }
  }
}
  • id : evt_ suivi de 24 caractères. Le même pour tous les envois d'un événement (essais, renvois, et d'une adresse à l'autre) : c'est votre clé de dédoublonnage.
  • cree_le : le moment de l'événement (pas celui de l'envoi), en ISO 8601 UTC.
  • test : présent, à true, seulement sur l'envoi du bouton « Envoyer un test ».
  • donnees.client : { id, prenom }, plus telephone et email seulement si le client les a donnés ; null pour une carte cadeau ou un forfait.
  • Les montants (montant, prix) sont en centimes, entiers, avec leur devise (ISO 4217).
  • De nouvelles clés peuvent apparaître : ignorez ce que vous ne connaissez pas.

Vérifier la signature

Vérifiez chaque requête avant de faire confiance à son contenu : sans cela, n'importe qui pourrait appeler votre adresse.

Le calcul
StampyWallet-Signature: t=<horodatage unix en secondes>,v1=<signature>
signature = hex( HMAC-SHA256( clé = secret, message = "<t>.<corps brut>" ) )
  • La clé est le secret entier, préfixe whsec_ compris.
  • Le message est l'horodatage t, un point, puis le corps tel que reçu, en octets. Ne relisez pas le JSON avant de vérifier : un JSON réécrit n'a plus les mêmes octets.
  • Refusez un t à plus de 5 minutes de votre horloge : cela empêche de rejouer une vieille requête.
  • L'en-tête peut porter plusieurs v1= (le jour d'un changement de secret) : il suffit qu'une seule corresponde. Comparez à temps constant.
const crypto = require('node:crypto');

/** corpsBrut : le corps reçu, en Buffer (ou en texte), AVANT tout JSON.parse. */
function verifierStampyWallet(secret, corpsBrut, entete, tolerance = 300) {
  let t = null;
  const v1 = [];
  for (const morceau of String(entete || '').split(',')) {
    const i = morceau.indexOf('=');
    const cle = morceau.slice(0, i).trim();
    const valeur = morceau.slice(i + 1).trim();
    if (cle === 't' && /^\d+$/.test(valeur)) t = Number(valeur);
    if (cle === 'v1') v1.push(valeur);
  }
  if (t === null || v1.length === 0) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > tolerance) return false;
  const attendue = crypto.createHmac('sha256', secret)
    .update(`${t}.`).update(corpsBrut).digest('hex');
  return v1.some((s) => s.length === attendue.length
    && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(attendue)));
}

// Avec Express : garder le corps brut sur cette route.
app.post('/stampywallet', express.raw({ type: 'application/json' }), (req, res) => {
  const ok = verifierStampyWallet(process.env.STAMPYWALLET_WEBHOOK_SECRET,
    req.body, req.get('StampyWallet-Signature'));
  if (!ok) return res.status(400).end();
  const evenement = JSON.parse(req.body.toString('utf8'));
  // dédoublonner sur evenement.id, puis traiter plus tard
  res.status(200).end();
});

Vecteur de test

Pour vérifier votre code : avec ces valeurs et une tolérance infinie (Infinity, float("inf"), PHP_INT_MAX), la fonction doit rendre vrai. Le corps tient sur une seule ligne, sans retour à la ligne final, et contient un « é » exprès : la signature porte sur les octets UTF-8.

Vecteur
secret : whsec_5WZbPq8rLk2N7xYdTf3Hc9JmVg4RsQe1
t      : 1761134400
corps  : {"id":"evt_4fK9mQ2xT7bL1nR8vZ3cW6yH","type":"passage.cree","cree_le":"2026-10-22T12:00:00.000Z","commerce":{"id":"9b2f6c1e-4d3a-4f7b-8e21-5a6c7d8e9f01","nom":"Le Café du Coin"},"donnees":{"client":{"id":"3c1d2e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f","prenom":"Camille"},"passage":{"id":"7e8f9a0b-1c2d-4e3f-9a4b-5c6d7e8f9a0b","quantite":1,"montant":null,"devise":"EUR","reference":null,"source":"scan","piste":"normal","tampons":4,"objectif":10}}}
v1     : 4346dff69a3e9e25937b22ec031a8eaf8cfce3baca4dfb932865682b8f891f1d
en-tête: t=1761134400,v1=4346dff69a3e9e25937b22ec031a8eaf8cfce3baca4dfb932865682b8f891f1d

Répondre, et les réessais

  • Répondez par un code 2xx en moins de 10 secondes. Le corps de votre réponse est ignoré. Répondez vite, et faites le travail long ensuite.
  • Sinon, huit essais au total : le premier tout de suite, puis après 1 min, 5 min, 30 min, 2 h, 5 h, 8 h et 8 h. Le dernier part environ 23 h 30 après le premier ; au huitième échec, l'envoi est abandonné.
  • Après 50 échecs d'affilée sur une adresse, elle est désactivée et le propriétaire du commerce reçoit un e-mail. Réactivez-la une fois votre serveur réparé : son compteur repart à zéro.
  • L'envoi de test n'est essayé qu'une fois et ne compte pas dans les échecs.
  • Renvoyer renvoie le même événement (même id, mêmes octets), avec une signature neuve.
  • Au moins une fois, sans ordre garanti : un événement peut arriver deux fois, dédoublonnez sur StampyWallet-Id ; deux événements proches peuvent arriver dans le désordre, fiez-vous à cree_le.
  • Les envois sont gardés 30 jours dans le journal.
Statut d'un envoiSens
en_attentePas encore essayé
echecLe dernier essai a raté ; un autre est prévu
livreUn 2xx a été reçu
abandonneHuit échecs, ou adresse désactivée, ou remplacé par un renvoi

Sans code : Zapier, Make et n8n reçoivent ces webhooks tels quels : collez l'adresse de leur déclencheur « Webhook » comme adresse. Les pas à pas sont sur Relier votre caisse.

Mis à jour le

Cette page vous a aidé ?