Events and Webhooks
ScaiVault emits events for everything important that happens — secrets written, rotated, accessed; policies changed; certificates issued or expiring; dynamic leases granted or revoked. You consume them via webhooks (HTTP POST to your endpoint) or subscriptions (long-polling channel scoped to specific paths).
Delivery mechanisms#
| Mechanism | Use when |
|---|---|
| Webhooks | You have a reachable HTTPS endpoint. Deliveries retried with backoff, signed with HMAC. |
| Subscriptions | You want events filtered to specific secret paths, or you need long-polling (behind firewalls that can't receive inbound HTTP). |
Both deliver the same event payloads. Pick whichever fits your deployment.
Event catalog#
Secret events
secret.created— new path written for the first time.secret.updated— new version written on an existing path.secret.deleted— soft or hard delete.secret.rotated— rotation completed (a new version was written by a rotation policy).secret.expiring— a secret'sexpires_atis approaching (emitted at configurable thresholds).secret.expired— a secret'sexpires_athas passed.secret.accessed— a read happened. Noisy; use subscriptions with a filter or consume from the audit log instead.
Policy events
policy.created,policy.updated,policy.deletedpolicy.binding.created,policy.binding.deletedpolicy.violation— an access was denied by a policy evaluation. Useful for alerting on anomalies.
Rotation events
rotation.due— a rotation is approaching or due. Fires at each configuredwarn_beforethreshold, then withdue_now: truewhen the rotation fires.rotation.overdue— a rotation that required manual intervention (auto_generate: false) missed its window.rotation.completed— rotation successful.rotation.failed— rotation tried and errored; included error details.
Certificate events
certificate.issued— new cert minted (internal CA or ACME).certificate.renewed— ACME auto-renew succeeded.certificate.revokedcertificate.expiring— expires in the configured warning window.
Dynamic events
dynamic.lease.createddynamic.lease.reneweddynamic.lease.revokeddynamic.lease.expired
Federation events
federation.sync.completedfederation.sync.failedfederation.backend.unreachable
Event envelope#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Fields:
event_id— unique, monotonic-ish. Use for idempotency.event_type— the catalog name above.path— relevant resource path (secret, cert, etc.).data— event-type-specific payload. See reference for per-event schemas.identity_id— who triggered the event (user, service account, orsystem:*).
Webhooks#
Register a webhook once:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Every matching event is POSTed to the URL. The request includes headers:
1 2 3 4 5 6 7 8 | |
Your endpoint must return a 2xx within 10 seconds to count as delivered. Non-2xx or timeout triggers retry with exponential backoff: 30s, 5min, 30min, 2h, 8h, 24h, then marked as failed.
See Webhook Signatures for how to verify the HMAC signature — this is required to trust the payload.
Managing webhooks#
GET /v1/webhooks— listGET /v1/webhooks/{id}— details including delivery statisticsPATCH /v1/webhooks/{id}— update URL, events, filtersDELETE /v1/webhooks/{id}— deletePOST /v1/webhooks/{id}/test— send a test eventGET /v1/webhooks/{id}/deliveries— per-delivery history
Subscriptions#
Subscriptions scope to specific paths (or path prefixes) and deliver via webhook or long-polling. Use them when you care about a subset of paths and don't want global webhook noise.
1 2 3 4 5 6 7 8 9 10 11 | |
Then poll:
1 2 | |
The request holds until an event arrives or the timeout elapses, whichever is first. On response, the events are returned. Your next poll uses since_event_id to continue.
Polling is useful behind firewalls that can't accept inbound HTTP, or in cases where pull semantics are simpler than managing a webhook receiver.
Event filtering#
Both webhooks and subscriptions accept filters:
1 2 3 4 5 | |
Available filter keys depend on the event type. See Webhooks Reference for the full list.
Idempotency#
Webhook delivery is at least once — you may receive the same event more than once if your endpoint times out after receiving. Use event_id to deduplicate.
1 2 3 4 5 6 7 | |
In production, persist event_id in a store with a reasonable TTL (e.g. Redis with 24h expiry).
Ordering#
Events are delivered roughly in timestamp order, but under load order is not guaranteed. Consumers that care about ordering (e.g., secret.updated followed by secret.rotated) should either:
- Order by
timestampin a buffer before processing. - Re-fetch current state from the API rather than relying purely on event diffs.
What's next#
- Webhook Signatures — HMAC verification.
- Webhooks Reference — endpoint details.
- Subscriptions Reference — long-polling API.