Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

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's expires_at is approaching (emitted at configurable thresholds).
  • secret.expired — a secret's expires_at has 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.deleted
  • policy.binding.created, policy.binding.deleted
  • policy.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 configured warn_before threshold, then with due_now: true when 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.revoked
  • certificate.expiring — expires in the configured warning window.

Dynamic events

  • dynamic.lease.created
  • dynamic.lease.renewed
  • dynamic.lease.revoked
  • dynamic.lease.expired

Federation events

  • federation.sync.completed
  • federation.sync.failed
  • federation.backend.unreachable

Event envelope#

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "event_id": "evt_01HK7X9Z...",
  "event_type": "secret.rotated",
  "timestamp": "2026-04-23T14:00:00.123456Z",
  "tenant_id": "tnt_acme_prod",
  "partner_id": "ptn_acme",
  "identity_id": "system:rotation-scheduler",
  "path": "environments/production/salesforce/oauth",
  "data": {
    "old_version": 3,
    "new_version": 4,
    "rotation_policy_id": "rot_quarterly",
    "grace_period_ends": "2026-04-25T14:00:00Z"
  },
  "request_id": "req_abc123"
}

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, or system:*).

Webhooks#

Register a webhook once:

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
curl -X POST https://scaivault.scailabs.ai/v1/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "rotation-alerts",
    "url": "https://ops.acme.example/scaivault/webhook",
    "secret": "whsec_ReplaceThisWithARandom32ByteString",
    "events": ["secret.rotated", "secret.expiring", "rotation.overdue"],
    "filters": {
      "path_prefix": "environments/production/"
    }
  }'

Every matching event is POSTed to the URL. The request includes headers:

http
1
2
3
4
5
6
7
8
POST /scaivault/webhook HTTP/1.1
Host: ops.acme.example
Content-Type: application/json
X-ScaiVault-Event-Id: evt_01HK7X9Z...
X-ScaiVault-Event-Type: secret.rotated
X-ScaiVault-Signature: sha256=...
X-ScaiVault-Timestamp: 1714478400
User-Agent: ScaiVault-Webhook/1.0

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.

sequenceDiagram participant SV as ScaiVault participant Hook as Your endpoint SV->>Hook: POST event (attempt 1) Hook-->>SV: 502 / timeout Note over SV: wait 30s SV->>Hook: POST event (attempt 2) Hook-->>SV: 502 Note over SV: wait 5min SV->>Hook: POST event (attempt 3) Hook-->>SV: 200 OK Note over SV: delivery succeeded;<br/>recorded in deliveries

See Webhook Signatures for how to verify the HMAC signature — this is required to trust the payload.

Managing webhooks#

  • GET /v1/webhooks — list
  • GET /v1/webhooks/{id} — details including delivery statistics
  • PATCH /v1/webhooks/{id} — update URL, events, filters
  • DELETE /v1/webhooks/{id} — delete
  • POST /v1/webhooks/{id}/test — send a test event
  • GET /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.

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curl -X POST https://scaivault.scailabs.ai/v1/subscriptions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "reporting-sf-rotations",
    "paths": ["integrations/salesforce/*"],
    "events": ["secret.rotated", "secret.updated"],
    "delivery": {
      "type": "polling"
    }
  }'

Then poll:

bash
1
2
curl -H "Authorization: Bearer $TOKEN" \
     "https://scaivault.scailabs.ai/v1/subscriptions/sub_abc/poll?timeout=30"

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:

json
1
2
3
4
5
"filters": {
  "path_prefix": "environments/production/",
  "secret_type": "certificate",
  "tags": ["critical"]
}

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.

python
1
2
3
4
5
6
7
seen_events = set()

def handle_event(event):
    if event["event_id"] in seen_events:
        return  # duplicate
    seen_events.add(event["event_id"])
    process(event)

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 timestamp in a buffer before processing.
  • Re-fetch current state from the API rather than relying purely on event diffs.

What's next#

Updated 2026-05-17 14:30:19 View source (.md) rev 5