Ausgehende Webhooks
Webhooks
Ein Webhook schickt jedes Mal eine Nachricht an ein anderes System, wenn in Ihrem Studio etwas passiert, zum Beispiel eine neue Buchung. Ihre Web-Betreuung kann damit eine Tabelle aktualisieren, in einem Slack-Kanal posten oder ein anderes Tool aktuell halten. Sie brauchen das nur, wenn jemand ein anderes System von Hand anbindet. Wenn Sie Zapier nutzen, legt Zapier seine eigenen Webhooks an. FullClass schickt jede Nachricht als signiertes JSON an eine Webadresse Ihrer Wahl. So muss Ihr System nicht ständig die API abfragen.
Richten Sie sie unter Einstellungen → Webhooks ein. Nur der
Inhaber kann diese Seite öffnen. Fügen Sie einen Endpunkt hinzu (die
Adresse, die die Nachrichten empfängt). Geben Sie ihm einen Namen, eine aus
dem Internet erreichbare https://-Adresse und die Ereignisse,
die er bekommen soll. Sie können bis zu 10 Endpunkte anlegen. Jeder kann
andere Ereignisse bekommen.
Eine Einstellung in diesem Formular ist aus, bis Sie sie einschalten.
Code des Geschenkgutscheins mitsenden braucht eine echte
Entscheidung. Kauft jemand einen Geschenkgutschein oder ein Kursguthaben,
meldet das Ereignis payment.succeeded immer, dass der Verkauf
passiert ist, wer gekauft hat, wer den Gutschein besitzt und über welchen
Betrag. Der Code des Geschenkgutscheins ist etwas anderes. Jeder,
der ihn hat, kann den Gutschein an der Kasse einlösen, genau wie der Käufer.
Schalten Sie die Einstellung nur für einen Endpunkt ein, dem Sie den Code
sicher anvertrauen. Und nur, wenn Ihr System den Code wirklich braucht, zum
Beispiel um ihn selbst per E-Mail weiterzuschicken. Schalten Sie sie wieder
aus, endet das sofort.
| Ereignis | Wird gesendet, wenn |
|---|---|
booking.created |
Eine Buchung bestätigt wird. Eine unbezahlte Buchung, die nie
abgeschlossen wird, sendet keines. Eine Kursreihe sendet eines pro
Termin. Jeder Termin aus demselben Kauf hat dieselbe
course_purchase_id.
|
booking.updated |
Eine Buchung in einen anderen Kurs verschoben oder bearbeitet wird. Die Nachricht listet, was sich geändert hat. |
booking.canceled |
Eine Buchung storniert wird, von Ihnen oder vom Kunden. |
event.created |
Ein Kurs in Ihren Zeitplan kommt. Eine wiederkehrende Serie sendet eines pro Datum, eine Kursreihe eines pro Termin. |
event.updated |
Sie Uhrzeit, Standort, Kursleitung, Preis oder Größe eines Kurses ändern. Die Nachricht listet, was sich geändert hat. Bei einer Zeitänderung ist auch die alte Uhrzeit dabei. |
event.canceled |
Sie einen ganzen Kurs absagen. Die Nachricht sagt, wie viele Buchungen storniert wurden. So können Sie es von einzelnen Stornierungen durch Kunden unterscheiden. |
customer.created |
Ein Kundenkonto zum ersten Mal entsteht, ob der Kunde gebucht hat, sich selbst registriert hat oder von Ihnen angelegt wurde. |
payment.succeeded |
Eine Zahlung eingenommen wird, auch Zahlungen, die Sie von Hand erfassen. Beim Verkauf eines Geschenkgutscheins oder Kursguthabens werden auch der Gutschein, seine Art und die Person genannt, die ihn besitzt. So können Sie eine Willkommens- oder Prämien-E-Mail schicken, ohne etwas nachzuschlagen. |
payment.refunded |
Geld an einen Kunden zurückgeht. Das umfasst eine Stornierung mit Rückerstattung, eine Rückerstattung in Stripe oder Square und eine verlorene Zahlungsanfechtung. |
waitlist.offered |
Ein freier Platz in einem Kurs oder einer Kursreihe der nächsten Person auf der Warteliste angeboten wird. |
Buchungs- und Zahlungsnachrichten enthalten Name und E-Mail-Adresse des Kunden. So können Sie jemandem schreiben oder ihn zu einer Liste hinzufügen, ohne ihn vorher nachzuschlagen. Wurde der Datensatz des Kunden inzwischen gelöscht, fehlen diese Angaben. Die Nachricht wird aber trotzdem gesendet.
Wenn Sie einen Endpunkt hinzufügen, zeigt FullClass sein Signaturgeheimnis einmal an. Jede Nachricht wird damit signiert. So kann Ihr System prüfen, ob die Nachricht wirklich von FullClass kommt. Kopieren Sie es direkt in die Einstellungen Ihres Endpunkts. Wenn Sie es verlieren, holen Sie sich mit Secret erneuern auf der Seite des Endpunkts ein neues. Das alte Geheimnis funktioniert nicht mehr, sobald das neue erscheint. Aktualisieren Sie Ihren Endpunkt also sofort.
Die Signatur prüfen
Dieser Teil ist für Ihre Web-Betreuung. Er zeigt, wie deren Code prüft, ob
eine Nachricht von FullClass kommt. Das Signaturgeheimnis ist kein Passwort,
und FullClass schickt es nie mit. Suchen Sie es nicht in der Anfrage und
vergleichen Sie es mit nichts. Stattdessen hat jede Nachricht einen Header
FullClass-Signature. Ihr Code berechnet ihn aus dem Geheimnis
und dem Nachrichtentext neu:
FullClass-Signature: t=1767225600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
-
tist der Sendezeitpunkt der Nachricht in Unix-Sekunden.v1ist ein HMAC-SHA256, geschrieben als Hexadezimalzahl in Kleinbuchstaben. -
Der HMAC wird über den Zeitstempel, einen Punkt und den unveränderten
Request-Body berechnet, also
<t>.<body>. -
Der Schlüssel ist das ganze Geheimnis genau wie angezeigt, mit dem Präfix
whsec_, als reiner Text. Dekodieren Sie es nicht mit Base64. -
Vergleichen Sie Ihr Ergebnis mit
v1über einen zeitkonstanten Vergleich. Weisen Sie die Nachricht ab, wenntmehr als 5 Minuten von Ihrer eigenen Uhr abweicht, in beide Richtungen.
In Node.js mit 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);
}
Binden Sie diese Route vor jedem app.use(express.json()) ein.
Ein JSON-Body-Parser, der zuerst läuft, ersetzt die Rohbytes durch ein
geparstes Objekt. Dann kann die Signatur nie stimmen. Behandeln Sie einen
fehlenden Header als fehlgeschlagene Prüfung. Schreiben Sie das Geheimnis
oder den Header nie in Ihre Logs.
In Python mit 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)
Jede Nachricht hat außerdem FullClass-Event-Id,
FullClass-Event-Type und FullClass-Delivery-Id.
Ein neuer Versuch wird mit einem neuen Zeitstempel erneut signiert. Seine
Signatur unterscheidet sich also vom ersten Versuch. Nutzen Sie
FullClass-Event-Id, um ein schon verarbeitetes Ereignis zu
erkennen. Endpunkte, die eine App wie Zapier für Sie anlegt, werden auch
signiert, aber ihr Geheimnis wird nie angezeigt. Die Signatur können Sie
also nur bei Endpunkten prüfen, die Sie selbst anlegen.
Die Seite jedes Endpunkts zeigt seine letzten Zustellungen. Sie sehen das Ereignis, wann es gesendet wurde, was Ihr Server geantwortet hat und wie viele Versuche es gab. Eine fehlgeschlagene Zustellung wird von selbst wiederholt, mit jedes Mal längeren Pausen. Sie können auch die Nachricht einer Zustellung öffnen oder sie erneut senden, sobald Ihr Endpunkt repariert ist. Ein Endpunkt, der immer wieder fehlschlägt, wird von selbst pausiert. So staut sich für einen toten Server nichts auf. Reparieren Sie die Empfängerseite, bearbeiten Sie dann den Endpunkt und schalten Sie ihn wieder ein.