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, gardeznext_cursoret demandez la fois suivante?cursor=avec cette valeur. next_cursorfigure 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 avectypes. S'il n'y a rien de nouveau, vous recevez une page vide et le mêmenext_cursor; redemandez plus tard.typeslimite le flux à certains types :?types=directory.membership.removed.limitvaut 100 au plus. Une page peut contenir moins d'événements quelimitalors quenext_cursoravance : vous ne voyez que ce que vous avez le droit de voir.410signifie 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
secrettout de suite. OS ne montre le secret que dans cette réponse, jamais plus. kindsvide signifie tous les types, y compris ceux qui s'ajouteront plus tard.actor_membership_idest 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.