Webhooks#

Au lieu d’interroger, abonne-toi et laisse les changements venir à toi.

Enregistre un point de terminaison sur la ressource webhook_endpoints — cela requiert webhook_endpoints.write ; les routes exactes figurent dans la référence de l’API. La création renvoie un secret de signature (whsec_…) une seule fois. Stocke-le immédiatement ; il est irrécupérable.

Les événements sont minces#

Le corps est délibérément petit :

{
  "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-…/"
  }
}

Récupère l’état courant depuis related_object.url plutôt que de croire que l’événement le porte. Cela te garde juste quand plusieurs changements se succèdent de près.

La livraison est au moins une fois et éventuellement dans le désordre ; ton gestionnaire doit donc être idempotent — déduplique sur l’id de l’événement.

Vérifie chaque livraison#

Chaque POST porte un en-tête Webhook-Signature (plus Webhook-Id) :

Webhook-Signature: t=1753178651,v1=5f3a…

Pour vérifier :

  1. Recalcule HMAC-SHA256(secret, "{t}.{raw_body}") — sur les octets bruts du corps, avant tout parsing JSON ou re-sérialisation.

  2. Compare-la en temps constant à une valeur v1=.

  3. Rejette si t sort d’une tolérance d’environ 5 minutes.

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)

Important

Pendant une rotation de secret, l’en-tête porte plusieurs signatures v1= — l’ancienne et la nouvelle — durant une fenêtre de recouvrement. Accepte une correspondance avec n’importe laquelle, sinon les rotations feront perdre des livraisons.

Rattraper après une panne#

Les livraisons échouées sont réessayées avec un back-off exponentiel, et l’historique des événements distribués est conservé. Si ton point de terminaison était hors service, tu peux te réconcilier depuis cet historique au lieu de réinterroger les ressources métier.

Abonne-toi à tout le cycle de vie#

Le bug d’intégration le plus courant ici consiste à ne s’abonner qu’au chemin heureux et à laisser une annonce morte active dans ta copie.

Les événements de la bourse aux shifts vont par paires open_shift.* / swap.* — une offre est publiée sous forme de deux ressources (open_shifts/ pour les postes vacants, swaps/ pour les cessions et les échanges), et le type d’événement t’indique quelle collection récupérer. Les deux portent les transitions terminales aussi bien que les attributions :

  • open_shift.claimed, open_shift.awarded, swap.approved

  • open_shift.rejected, swap.rejected, *.withdrawn, *.auto_rejected, *.expired

Abonne-toi aussi aux transitions terminales, pas seulement aux attributions.

La liste complète des types d’événements auxquels tu peux t’abonner figure sur la ressource webhook_endpoints dans la référence de l’API.