Webhooks en de feed via de API¶
Deze pagina is voor wie een eigen programma laat bijhouden wat er in OS verandert. Ze laat zien hoe je de feed leest, een webhook registreert, een aflevering controleert en wat je doet als er iets misgaat. Wat gebeurtenissen en webhooks zijn, staat in Gebeurtenissen en webhooks. Hoe je een verzoek aan OS doet (adres, aanmelden, pagina's, fouten), staat in De access-API.
De feed lezen¶
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"}
- Zonder
cursorbegin je bij de oudste gebeurtenis die OS nog bewaart. Verwerk de pagina, bewaarnext_cursoren vraag de volgende keer?cursor=met die waarde. next_cursorstaat ook op een korte pagina: dan wijst hij voorbij alles wat OS tot nu vastlegde, ook de soorten die je mettypeswegliet. Is er niets nieuws, dan krijg je een lege pagina en dezelfdenext_cursor; vraag later opnieuw.typesbeperkt de feed tot bepaalde soorten:?types=directory.membership.removed.limitis ten hoogste 100. Een pagina kan minder gebeurtenissen bevatten danlimitterwijlnext_cursordoorloopt: je ziet alleen wat je mag zien.410betekent dat de gebeurtenissen na je cursor niet meer bewaard zijn. Lees de stand opnieuw in en begin zonder cursor.
Je hebt see op event nodig.
Een webhook registreren¶
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_..."}
- Bewaar
secretmeteen. OS laat het geheim alleen in dit antwoord zien, en nooit meer. kindsleeg betekent elke soort, ook soorten die later bijkomen.actor_membership_idis het account waarnamens de webhook ontvangt: een actief lid van je organisatie dat je mag wijzigen en waarvan je alles hebt wat het heeft.- Een organisatie heeft ten hoogste 20 webhooks.
| Antwoord | Betekenis |
|---|---|
201 |
Geregistreerd; Location wijst naar de webhook. |
403 |
Je hebt niet alles wat het account heeft. |
409 |
Webhooks staan niet aan in deze omgeving, of je organisatie heeft er al 20. |
422 |
Het adres is geen https, noemt een IP-adres in plaats van een hostnaam, bevat een gebruiker, wachtwoord of #, of de host komt uit op een privé-, intern of geblokkeerd adres (webhook_host_refused); een soort bestaat niet; het account is geen actief lid. |
Wijzigen, vervangen, verwijderen¶
| Verzoek | Wat het doet |
|---|---|
GET .../webhooks |
de webhooks, zonder geheim |
GET .../webhooks/{id} |
één webhook, met ETag |
PATCH .../webhooks/{id} |
label, url, kinds of actor_membership_id wijzigen, met If-Match: "<versie>"; een nieuw adres en een nieuw account controleert OS zoals bij registreren |
DELETE .../webhooks/{id} |
de webhook en zijn logboek weg; de gebeurtenissen blijven in de feed |
POST .../webhooks/{id}/rotate-secret |
{"overlap_hours": 24}: een nieuw geheim, alleen in dit antwoord; het oude ondertekent nog zo lang mee (0 tot 168 uur, 0 is meteen) |
POST .../webhooks/{id}/enable |
een falende of uitgeschakelde webhook weer actief; 409 zolang het account niet actief is |
POST .../webhooks/{id}/test |
stuurt één gebeurtenis events.webhook.tested naar alleen deze webhook, in elke toestand; 202 met delivery_id |
Al deze verzoeken vragen write op webhook (verwijderen delete, lezen see).
Een aflevering ontvangen¶
OS stuurt elke gebeurtenis als een POST met de gebeurtenis als JSON en deze headers:
| Header | Waarde |
|---|---|
Content-Type |
application/json |
User-Agent |
artific-os |
Artific-Signature |
t=<unix-seconden>,v1=<hex>, en tijdens een vervanging een tweede v1 |
Idempotency-Key |
het id van de aflevering, bij elke poging hetzelfde |
Artific-Event-Id |
het id van de gebeurtenis |
Antwoord met een 2xx zodra je de gebeurtenis veilig hebt opgeslagen; doe het werk daarna. OS
wacht ten hoogste 10 seconden op je antwoord en leest de inhoud ervan niet. Een andere status, een
doorverwijzing of geen antwoord telt als mislukt, en OS probeert het opnieuw.
Je kunt dezelfde aflevering meer dan eens ontvangen, bijvoorbeeld als je antwoord onderweg verloren
ging. Gooi een aflevering weg waarvan je de Idempotency-Key al verwerkte.
Controleer de handtekening¶
De handtekening is de HMAC-SHA256 van de tijd, een punt en de exacte inhoud, met het geheim, als
hexadecimaal. Accepteer een aflevering wanneer één van de v1-waarden klopt en de tijd niet meer
dan vijf minuten afwijkt:
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
Controleer op de onbewerkte inhoud, zoals je hem ontving, niet op opnieuw opgebouwde JSON. Dezelfde functie controleert ook de webhooks van de AI-functies, die op dezelfde manier ondertekend zijn.
Tijdens een vervanging met overlap draagt elke aflevering twee v1-waarden, de nieuwe eerst. Zet
het nieuwe geheim in je programma wanneer het je uitkomt, zolang de overlap duurt.
Het afleverlogboek¶
curl -s "$OS/api/v1/organisations/$ORG/webhooks/$WEBHOOK/deliveries?state=failed" \
-H "Authorization: Bearer $TOKEN"
Elke aflevering heeft state (queued, sending, delivered, failed of skipped),
attempts, last_status, last_error (timeout, connect, tls, host_refused, redirect),
en bij een overgeslagen aflevering skipped_reason:
skipped_reason |
Betekenis |
|---|---|
not_visible |
het account mag de gebeurtenis of het ding niet zien |
actor_inactive |
het account is niet meer actief; de webhook is uitgeschakeld |
webhooks_off |
webhooks staan niet aan in deze omgeving |
POST .../deliveries/{delivery_id}/redeliver stuurt een afgeronde aflevering opnieuw, met dezelfde
Idempotency-Key, zolang de gebeurtenis bewaard is; 409 als ze nog wacht of verstuurd wordt. Wil
je een hele periode opnieuw, lees dan de feed vanaf je laatste cursor.