Skip to content

Events and webhooks

View as Markdown

Every accepted action appends one or more events in the same transaction as the change it describes. The log is append-only, ordered by a monotonic seq, and is the only source every downstream surface reads: webhooks, rollups, the live boards, and your integration. There is no change that is not an event.

GET /v1/events returns a page of events, oldest first. Scope: events:read.

ParameterUse
sinceEvents with seq greater than this number. The simplest way to keep your own position.
cursorThe next_cursor from the previous page. Opaque; wins over since if both are set.
correlation_idOnly the events one action invocation appended.
object_typeOne type, or several comma-separated. Matches object events of that type and link events touching it.

Each event carries its seq, its kind (object_created, object_modified, a link established or removed, and the action’s own domain events such as reservation.checked_in.decided), the objects involved, the principal who caused it, and the correlation_id of the invocation.

Polling is the right shape for a batch consumer: durable, replayable, and indifferent to your downtime. Store the last seq you processed; start from it next time.

GET /v1/events/stream holds the connection open and frames each new event as it lands, as Server-Sent Events:

id: 12345
data: {"seq": 12345, "kind": "object_modified", …}

The id of each frame is the event’s seq. A browser EventSource or any SSE client reconnects with Last-Event-ID set to the last one it saw and the server resumes from there; ?since=12345 does the same for clients that do not send the header. The stream opens with a comment frame so a proxy forwards the headers at once, and sends a keepalive comment while nothing is happening. Scope: events:read.

Use the stream for anything that should react within a beat: a board, an agent, a screen.

A webhook subscription asks the kernel to deliver matching events to a URL of yours. Manage subscriptions with webhooks:write and read them with webhooks:read:

RouteWhat it does
POST /v1/webhook-subscriptionsCreate a subscription: the receiver URL, a filter, and a signing key you are shown once.
GET /v1/webhook-subscriptions · GET …/{id}List, or read one, with its status and last_delivered_seq.
POST …/{id}/rotate-keyAdd a secondary signing key. Both keys are valid during the rotation window.
POST …/{id}/promote-keyMake the secondary key primary and retire the old one.
GET …/{id}/deliveriesThe delivery attempts, with their outcomes.

One POST per batch, a CloudEvents 1.0 envelope carrying the events past the subscription’s last_delivered_seq that match its filter. The exact payload is in the API reference under webhooks, as aerandil_event_delivery.

Every delivery carries an Aerandil-Signature header:

Aerandil-Signature: t=1730000000,v1=5f3a…[,v1=9c1b…]

Compute HMAC-SHA256 over the string "{t}.{raw_body}" with your signing key and compare it to a v1 value with a constant-time comparison. Reject a delivery whose t is more than five minutes old. During a key rotation there are two v1 entries, one per key; a match on either is valid. Ignore any other chunk you do not recognise.

  • Return a 2xx within ten seconds. Do the work after you have answered.
  • Anything else, or no answer, marks the batch failed and schedules a retry at 60 · 2ⁿ seconds plus up to thirty seconds of jitter, capped at 24 hours, for up to twelve consecutive failures.
  • After the twelfth failure the subscription moves to dead_letter and deliveries stop until an operator resets it, usually after advancing last_delivered_seq past the batch that could not be processed.
  • Delivery is at least once. De-duplicate on the event id, which is the log’s seq. The same event may arrive twice during a retry.

A receiver that cannot keep up should not slow its answer; it should acknowledge and queue. The log is replayable, so nothing is lost by reading it again from a seq you trust.

You are buildingUse
A nightly sync or a data warehouse loadPoll GET /v1/events from your last seq.
A board, an agent, a screen that reacts nowGET /v1/events/stream.
A system of yours that should be told, without pollingA webhook subscription, with the receiver contract above.