Skip to content

Deliver a batch of events (outbound)

POST

Aerandil sends this request to every active subscription’s receiver URL when one or more events past the subscription’s last_delivered_seq match its filter. Stripe-shaped semantics: at-least-once delivery, exponential backoff on non-2xx, dead-letter after ~3 days, dual-key rotation.

Signature header. Aerandil-Signature: t=<unix_ts>,v1=<hex>[,v1=<hex>]. Verify with HMAC-SHA256 of "{t}.{raw_body}" against the subscription’s signing key(s). Receivers MUST use a constant-time comparison and SHOULD reject envelopes whose t is older than 5 minutes (Stripe’s published tolerance — replay protection). During a rotation window both the primary and the secondary key are valid; any v1= entry matching either key satisfies the contract.

Receiver contract. Return 2xx within 10 seconds. Any non-2xx (or no response) marks the batch failed and schedules a retry at now + 60·2^n s + jitter(0..30s), capped at 24h, for up to 12 consecutive failures. After the 12th the subscription transitions to dead_letter and the worker stops attempting further deliveries until the operator resets it via PATCH .../{id} (and, typically, advances last_delivered_seq past the poison batch).

Idempotency. The receiver MUST de-duplicate by the inner event id (which is the event-log seq); at-least- once means the same seq may arrive twice during a retry.

Batch shape. A CloudEvents 1.0 envelope (specversion, delivery_id, delivered_at) carrying one or more event entries — see [WebhookDeliveryBatch]. Type strings follow the aerandil.<kind> namespace (e.g. aerandil.object.created, aerandil.link.deleted).

Aerandil-Signature
required
string
/^t=\d+(,v1=[0-9a-f]+)+(,[^,]+)*$/

HMAC-SHA256 signature envelope: t=<unix_ts>,v1=<hex>[,v1=<hex>]. The signed payload is "{t}.{raw_body}". Multiple v1= entries appear during a key-rotation window — any one valid signature satisfies the contract. Unknown chunks (e.g. a future v2=) MUST be ignored by the receiver for forward-compat.

CloudEvents-shaped batch of events past the subscription’s cursor (after filter narrowing).

Media typeapplication/json

Outbound delivery batch — the request body Aerandil POSTs to every active subscription’s receiver URL. CloudEvents 1.0 envelope carrying a non-empty events array.

object
delivered_at
required
string format: date-time
delivery_id
required

Fresh per attempt — not stable across retries (use the per-event id for at-least-once de-duplication).

string format: uuid
events
required
Array<object>
>= 1 items

One CloudEvents-shaped entry inside a delivery batch. The data field carries the underlying ObjectEvent — same shape returned by GET /v1/events (minus the top-level seq, which lives in id here per CloudEvents conventions). Receivers de-duplicate by id.

object
data
required

The underlying ObjectEvent — same discriminated kind shape as EventRow, minus the top-level seq (which is carried in id here).

object
datacontenttype
required
Allowed value: application/json
id
required

Stringified seq of the underlying event log row. Use this for at-least-once de-duplication on the receiver side.

string
/^\d+$/
principal
required
One of: discriminator: kind
object
kind
required
string
Allowed value: system
Allowed values: system
source
required

Static — the producer is always Aerandil.

Allowed value: aerandil
specversion
required
Allowed value: 1.0
time
required
string format: date-time
type
required

aerandil.<kind> — one of aerandil.object.created, aerandil.object.updated, aerandil.object.deleted, aerandil.link.created, aerandil.link.deleted.

string
Allowed values: aerandil.object.created aerandil.object.updated aerandil.object.deleted aerandil.link.created aerandil.link.deleted
specversion
required

CloudEvents spec version.

Allowed value: 1.0

Receiver accepted the batch. Any 2xx status counts as success; Aerandil reads only the status code, never the response body (except for a 512-byte excerpt persisted to the delivery audit row).

Non-2xx (any status outside 200..=299) — marks the delivery failed and triggers the exponential-backoff retry schedule. After 12 consecutive failures the subscription transitions to dead_letter.