Webhooks sortants
Webhooks
Un webhook envoie un message à un autre système chaque fois que quelque chose se passe dans votre studio, par exemple une nouvelle réservation. La personne qui gère votre site peut s'en servir pour mettre à jour un tableur, publier dans un canal Slack ou tenir un autre outil à jour. Vous n'en avez besoin que si quelqu'un connecte un autre système à la main. Si vous utilisez Zapier, il crée lui-même ses webhooks. FullClass envoie chaque message en JSON signé à une adresse web de votre choix, pour que votre système n'ait pas à interroger l'API sans arrêt.
Configurez-les dans Paramètres → Webhooks. Seul le
propriétaire peut ouvrir cette page. Ajoutez un point de terminaison
(l'adresse qui reçoit les messages) en lui donnant un nom, une adresse
https:// accessible depuis Internet, et les événements à lui
envoyer. Vous pouvez ajouter jusqu'à 10 points de terminaison, et chacun
peut recevoir un ensemble d'événements différent.
Un réglage de ce formulaire reste désactivé tant que vous ne l'activez pas.
Inclure le code de la carte cadeau demande une vraie
décision. Quand quelqu'un achète une carte cadeau ou un carnet de cours,
l'événement payment.succeeded indique toujours que la vente a
eu lieu, qui a acheté, qui détient la carte et pour quel montant. Le
code de la carte cadeau, c'est différent, car toute personne qui
l'a peut utiliser la carte au paiement, comme l'acheteur. Ne l'activez que
pour un point de terminaison à qui vous faites confiance pour le protéger,
et seulement si votre système a vraiment besoin du code, par exemple pour
l'envoyer vous-même par e-mail. Le désactiver arrête l'envoi tout de suite.
| Événement | Envoyé quand |
|---|---|
booking.created |
Une réservation est confirmée. Une réservation non payée qui n'est
jamais terminée n'en envoie pas. Une formation en envoie un par
séance, et toutes les séances d'un même achat ont le même
course_purchase_id.
|
booking.updated |
Une réservation est déplacée vers un autre cours ou modifiée. Le message indique ce qui a changé. |
booking.canceled |
Une réservation est annulée, par vous ou par le client. |
event.created |
Un cours est ajouté à votre planning. Une série récurrente en envoie un par date, et une formation un par séance. |
event.updated |
Vous changez l'horaire, le lieu, l'intervenant, le prix ou la taille d'un cours. Le message indique ce qui a changé. Un changement d'horaire contient aussi l'ancien horaire. |
event.canceled |
Vous annulez un cours entier. Le message indique combien de réservations ont été annulées, pour que vous le distinguiez de clients qui annulent un par un. |
customer.created |
Un compte client existe pour la première fois, qu'il ait réservé, qu'il se soit inscrit lui-même ou que vous l'ayez ajouté. |
payment.succeeded |
Un paiement est encaissé, y compris les paiements que vous enregistrez à la main. Une vente de carte cadeau ou de carnet de cours indique aussi la carte, son type et la personne qui la détient. Vous pouvez ainsi envoyer un e-mail de bienvenue ou de récompense sans rien chercher. |
payment.refunded |
De l'argent est rendu à un client. Cela couvre une annulation avec remboursement, un remboursement que vous faites dans Stripe ou Square, et une rétrofacturation que vous perdez. |
waitlist.offered |
Une place libre dans un cours ou une formation est proposée à la personne suivante sur sa liste d'attente. |
Les messages de réservation et de paiement contiennent le nom et l'adresse e-mail du client. Vous pouvez ainsi écrire à quelqu'un ou l'ajouter à une liste sans le chercher d'abord. Si la fiche du client a été supprimée depuis, ces informations sont omises, mais le message est quand même envoyé.
Quand vous ajoutez un point de terminaison, FullClass affiche une seule fois sa clé de signature. Chaque message est signé avec elle, pour que votre système puisse vérifier que le message vient bien de FullClass. Copiez-la directement dans les réglages de votre point de terminaison. Si vous la perdez, utilisez Renouveler la clé sur la page du point de terminaison pour en obtenir une nouvelle. L'ancienne clé cesse de fonctionner dès que la nouvelle apparaît, alors mettez votre point de terminaison à jour tout de suite.
Vérifier la signature
Cette partie est destinée à la personne qui gère votre site. Elle montre
comment son code vérifie qu'un message vient de FullClass. La clé de
signature n'est pas un mot de passe, et FullClass ne l'envoie jamais. Ne la
cherchez pas dans la requête et ne la comparez à rien. Chaque message porte
plutôt un en-tête FullClass-Signature. Votre code le recalcule
à partir de la clé et du corps du message :
FullClass-Signature: t=1767225600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
-
test le moment de l'envoi, en secondes Unix.v1est un HMAC-SHA256, écrit en hexadécimal minuscule. -
Le HMAC est calculé sur l'horodatage, un point, et le corps brut de la
requête, comme dans
<t>.<body>. -
La clé est la clé entière exactement telle qu'elle est affichée, préfixe
whsec_compris, utilisée comme texte brut. Ne la décodez pas en base64. -
Comparez votre résultat à
v1avec une comparaison à temps constant. Rejetez le message sits'écarte de plus de 5 minutes de votre propre horloge, dans un sens ou dans l'autre.
En Node.js avec Express :
const crypto = require("crypto");
const express = require("express");
const secret = process.env.FULLCLASS_WEBHOOK_SECRET; // "whsec_..."
const app = express();
// express.raw keeps the exact bytes FullClass signed.
app.post("/fullclass", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(req.get("FullClass-Signature") || "", req.body)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
// ... handle event ...
res.sendStatus(200);
});
function verify(header, rawBody) {
if (!Buffer.isBuffer(rawBody)) return false; // a JSON parser ran first
const parts = Object.fromEntries(
header.split(",").map((p) => p.trim().split("=", 2))
);
const t = Number(parts.t);
if (!Number.isInteger(t) || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(parts.t + ".")
.update(rawBody)
.digest();
const given = Buffer.from(parts.v1, "hex");
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}
Montez cette route avant tout app.use(express.json()). Un
analyseur JSON qui s'exécute en premier remplace les octets bruts par un
objet analysé, et la signature ne peut alors jamais correspondre. Traitez un
en-tête absent comme une vérification échouée. N'écrivez jamais la clé ni
l'en-tête dans vos journaux.
En Python avec Flask :
import hashlib, hmac, os, time
from flask import Flask, abort, request
SECRET = os.environ["FULLCLASS_WEBHOOK_SECRET"] # "whsec_..."
app = Flask(__name__)
@app.post("/fullclass")
def fullclass():
raw = request.get_data() # the exact bytes FullClass signed
if not verify(request.headers.get("FullClass-Signature", ""), raw):
abort(400)
event = request.get_json()
# ... handle event ...
return "", 200
def verify(header: str, raw_body: bytes) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
t, v1 = parts.get("t", ""), parts.get("v1", "")
if not t.isdigit() or not v1:
return False
if abs(time.time() - int(t)) > 300:
return False
expected = hmac.new(
SECRET.encode(), t.encode() + b"." + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, v1)
Chaque message contient aussi FullClass-Event-Id,
FullClass-Event-Type et FullClass-Delivery-Id. Une
nouvelle tentative est signée de nouveau avec un nouvel horodatage, donc sa
signature diffère de celle du premier essai. Utilisez
FullClass-Event-Id pour repérer un événement que vous avez déjà
traité. Les points de terminaison qu'une application comme Zapier ajoute
pour vous sont signés eux aussi, mais leur clé n'est jamais affichée. Vous
ne pouvez donc vérifier la signature que sur les points de terminaison que
vous ajoutez vous-même.
La page de chaque point de terminaison montre ses envois récents. Vous voyez l'événement, le moment de l'envoi, ce que votre serveur a répondu et le nombre d'essais. Un envoi en échec est retenté tout seul, avec des intervalles de plus en plus longs. Vous pouvez aussi ouvrir le message d'un envoi, ou le renvoyer une fois votre point de terminaison réparé. Un point de terminaison qui échoue sans cesse est mis en pause tout seul, pour qu'un serveur hors service n'accumule pas de retard. Réparez le côté qui reçoit, puis modifiez le point de terminaison et réactivez-le.