Webhooks#
Statt abzufragen, abonnierst du und lässt die Änderungen zu dir kommen.
Registrier einen Endpunkt auf der Ressource webhook_endpoints — sie braucht webhook_endpoints.write; die genauen Routen stehen in der API-Referenz. Das Anlegen gibt einmalig ein Signatur-Geheimnis (whsec_…) zurück. Speichere es sofort; es lässt sich nicht wiederherstellen.
Ereignisse sind schlank#
Der Body ist bewusst klein:
{
"id": "evt_0194d1c0-…",
"object": "event",
"type": "shift.published",
"created": "2026-07-22T10:04:11Z",
"related_object": {
"id": "0194d1c0-…",
"url": "https://app.shiftavo.com/api/public/v1/shifts/0194d1c0-…/"
}
}
Hol den aktuellen Zustand über related_object.url, statt darauf zu vertrauen, dass das Ereignis ihn mitbringt. So bleibst du richtig, wenn mehrere Änderungen dicht aufeinander folgen.
Die Zustellung erfolgt mindestens einmal und möglicherweise ausser der Reihe, dein Handler muss also idempotent sein — entdopple über die id des Ereignisses.
Jede Zustellung prüfen#
Jedes POST trägt einen Webhook-Signature-Header (dazu Webhook-Id):
Webhook-Signature: t=1753178651,v1=5f3a…
So prüfst du:
Berechne
HMAC-SHA256(secret, "{t}.{raw_body}")neu — über die rohen Body-Bytes, vor jedem JSON-Parsen und erneuten Serialisieren.Vergleich sie in konstanter Zeit mit einem
v1=-Wert.Weise ab, wenn
tausserhalb einer Toleranz von ~5 Minuten liegt.
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(
secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256
).hexdigest()
# A rotation sends several v1= values — accept a match against any of them.
signatures = [v for k, v in (p.split("=", 1) for p in header.split(",")) if k == "v1"]
return any(hmac.compare_digest(expected, sig) for sig in signatures)
Wichtig
Während einer Rotation des Geheimnisses trägt der Header mehrere v1=-Signaturen — alte und neue — für ein Überlappungsfenster. Akzeptier eine Übereinstimmung mit irgendeiner davon, sonst gehen bei Rotationen Zustellungen verloren.
Nach einem Ausfall aufholen#
Fehlgeschlagene Zustellungen werden mit exponentiell wachsendem Abstand wiederholt, und der Verlauf der ausgelieferten Ereignisse bleibt erhalten. War dein Endpunkt ausgefallen, kannst du aus diesem Verlauf abgleichen, statt die Fachressourcen erneut abzufragen.
Den ganzen Lebenszyklus abonnieren#
Der häufigste Anbindungsfehler hier ist, nur den guten Fall zu abonnieren und eine tote Ausschreibung in der eigenen Kopie am Leben zu lassen.
Ereignisse der Schichtbörse kommen paarweise als open_shift.* / swap.* — ein Angebot wird als zwei Ressourcen veröffentlicht (open_shifts/ für Vakanzen, swaps/ für Abgaben und Tausche), und der Ereignistyp sagt dir, welche Sammlung du holen musst. Beide führen die Endübergänge ebenso wie die Vergaben:
open_shift.claimed,open_shift.awarded,swap.approvedopen_shift.rejected,swap.rejected,*.withdrawn,*.auto_rejected,*.expired
Abonnier auch die Endübergänge, nicht nur die Vergaben.
Die vollständige Liste der abonnierbaren Ereignistypen steht bei der Ressource webhook_endpoints in der API-Referenz.