Aller au contenu

Webhooks et flux via l'API

Cette page s'adresse à qui fait suivre à un programme propre ce qui change dans OS. Elle montre comment lire le flux, enregistrer un webhook, vérifier une livraison et que faire quand quelque chose tourne mal. Ce que sont les événements et les webhooks figure dans Événements et webhooks. Comment faire une requête à OS (adresse, connexion, pages, erreurs) figure dans L'API d'accès.

Lire le flux

curl -s "$OS/api/v1/organisations/$ORG/events?limit=100" -H "Authorization: Bearer $TOKEN"
{"items": [
   {"id": "evt_...", "type": "directory.invitation.created", "organisation_id": "org_...",
    "cursor": "41", "created_at": "2026-10-17T09:14:03Z", "refs": {"invitation_id": "inv_..."}}
 ],
 "next_cursor": "41"}
  • Sans cursor, vous commencez à l'événement le plus ancien qu'OS conserve encore. Traitez la page, gardez next_cursor et demandez la fois suivante ?cursor= avec cette valeur.
  • next_cursor figure aussi sur une page courte : il pointe alors au-delà de tout ce qu'OS a enregistré jusqu'ici, y compris les types que vous avez écartés avec types. S'il n'y a rien de nouveau, vous recevez une page vide et le même next_cursor ; redemandez plus tard.
  • types limite le flux à certains types : ?types=directory.membership.removed.
  • limit vaut 100 au plus. Une page peut contenir moins d'événements que limit alors que next_cursor avance : vous ne voyez que ce que vous avez le droit de voir.
  • 410 signifie que les événements après votre curseur ne sont plus conservés. Relisez l'état et repartez sans curseur.

Il vous faut see sur event.

Enregistrer un webhook

curl -s -X POST "$OS/api/v1/organisations/$ORG/webhooks" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"url": "https://crm.example.com/os-events", "label": "CRM",
       "kinds": ["directory.membership.created", "directory.membership.removed"],
       "actor_membership_id": "mem_..."}'
{"id": "whk_...", "label": "CRM", "url": "https://crm.example.com/os-events",
 "kinds": ["directory.membership.created", "directory.membership.removed"],
 "actor_membership_id": "mem_...", "state": "active", "consecutive_failures": 0,
 "allowed_actions": ["see", "write", "delete"], "secret": "whsec_..."}
  • Gardez secret tout de suite. OS ne montre le secret que dans cette réponse, jamais plus.
  • kinds vide signifie tous les types, y compris ceux qui s'ajouteront plus tard.
  • actor_membership_id est le compte au nom duquel le webhook reçoit : un membre actif de votre organisation que vous pouvez modifier et dont vous avez tout ce qu'il a.
  • Une organisation a 20 webhooks au plus.
Réponse Signification
201 Enregistré ; Location pointe vers le webhook.
403 Vous n'avez pas tout ce que le compte a.
409 Les webhooks ne sont pas activés dans cet environnement, ou votre organisation en a déjà 20.
422 L'adresse n'est pas en https, nomme une adresse IP au lieu d'un nom d'hôte, contient un utilisateur, un mot de passe ou #, ou l'hôte mène à une adresse privée, interne ou bloquée (webhook_host_refused) ; un type n'existe pas ; le compte n'est pas un membre actif.

Modifier, remplacer, supprimer

Requête Ce qu'elle fait
GET .../webhooks les webhooks, sans secret
GET .../webhooks/{id} un webhook, avec ETag
PATCH .../webhooks/{id} modifier label, url, kinds ou actor_membership_id, avec If-Match: "<version>" ; une nouvelle adresse et un nouveau compte, OS les vérifie comme à l'enregistrement
DELETE .../webhooks/{id} le webhook et son journal disparaissent ; les événements restent dans le flux
POST .../webhooks/{id}/rotate-secret {"overlap_hours": 24} : un nouveau secret, uniquement dans cette réponse ; l'ancien continue de signer pendant ce temps (0 à 168 heures, 0 veut dire tout de suite)
POST .../webhooks/{id}/enable un webhook défaillant ou désactivé redevient actif ; 409 tant que le compte n'est pas actif
POST .../webhooks/{id}/test envoie un événement events.webhook.tested à ce seul webhook, dans n'importe quel état ; 202 avec delivery_id

Toutes ces requêtes demandent write sur webhook (supprimer delete, lire see).

Recevoir une livraison

OS envoie chaque événement en POST, avec l'événement en JSON et ces en-têtes :

En-tête Valeur
Content-Type application/json
User-Agent artific-os
Artific-Signature t=<secondes-unix>,v1=<hex>, et pendant un remplacement un second v1
Idempotency-Key l'identifiant de la livraison, le même à chaque tentative
Artific-Event-Id l'identifiant de l'événement

Répondez par un 2xx dès que vous avez enregistré l'événement en sûreté ; faites le travail ensuite. OS attend votre réponse 10 secondes au plus et n'en lit pas le contenu. Un autre statut, une redirection ou l'absence de réponse compte comme un échec, et OS réessaie.

Vous pouvez recevoir la même livraison plus d'une fois, par exemple si votre réponse s'est perdue en route. Écartez une livraison dont vous avez déjà traité l'Idempotency-Key.

Vérifier la signature

La signature est le HMAC-SHA256 de l'heure, d'un point et du contenu exact, avec le secret, en hexadécimal. Acceptez une livraison lorsqu'une des valeurs v1 est correcte et que l'heure ne s'écarte pas de plus de cinq minutes :

import hashlib, hmac, time

def is_from_os(secret: str, header: str, body: bytes) -> bool:
    parts = [part.split("=", 1) for part in header.split(",")]
    timestamp = next(value for key, value in parts if key == "t")
    expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
    signed = any(hmac.compare_digest(expected, value) for key, value in parts if key == "v1")
    return signed and abs(time.time() - int(timestamp)) <= 300

Vérifiez sur le contenu brut, tel que vous l'avez reçu, pas sur du JSON reconstruit. La même fonction vérifie aussi les webhooks des fonctions d'IA, qui sont signés de la même manière.

Pendant un remplacement avec chevauchement, chaque livraison porte deux valeurs v1, la nouvelle d'abord. Mettez le nouveau secret dans votre programme quand cela vous convient, tant que le chevauchement dure.

Le journal des livraisons

curl -s "$OS/api/v1/organisations/$ORG/webhooks/$WEBHOOK/deliveries?state=failed" \
  -H "Authorization: Bearer $TOKEN"

Chaque livraison a state (queued, sending, delivered, failed ou skipped), attempts, last_status, last_error (timeout, connect, tls, host_refused, redirect), et pour une livraison ignorée skipped_reason :

skipped_reason Signification
not_visible le compte n'a pas le droit de voir l'événement ou l'élément
actor_inactive le compte n'est plus actif ; le webhook est désactivé
webhooks_off les webhooks ne sont pas activés dans cet environnement

POST .../deliveries/{delivery_id}/redeliver renvoie une livraison terminée, avec la même Idempotency-Key, tant que l'événement est conservé ; 409 si elle attend encore ou est en cours d'envoi. Si vous voulez toute une période de nouveau, lisez le flux à partir de votre dernier curseur.