gitcaskDocsGitHub

Events

One small bridge reads each repository's WAL and posts every committed ref change to your webhook.

From the log, not the push

No writer contains event code. A down webhook adds lag, never latency to a push.

any writer
CAS manifest.pb
POST /_events/notify
log (cursor, head_seq]
POST webhook
2xx
CAS events/cursor.json
A writer commits with a manifest compare-and-swap. The bridge, woken by a notification or a sweep, reads the log from its cursor, posts the events to the webhook, then advances the cursor.

The cursor lives in the bucket at repos/<o>/<r>/events/cursor.json and advances only after your webhook answers 2xx.

The ref event

One event per ref update in the transaction. Only ref events exist.

{
  "action": "update",
  "ref_type": "branch",
  "ref_name": "refs/heads/main",
  "old": "48a0637…",
  "new": "cb38da1…",
  "pusher": "alice@example.com",
  "correlation_id": "d1f916f7-…",
  "repo": "acme/monorepo",
  "_gitcask": { "schema_version": 1, "seq": "42", "entry_kind": "push", "request_id": "d1f916f7-…" }
}
action
create, update or delete. Force is not an action; derive it.
ref_type
branch for refs/heads/, tag for refs/tags/, "" otherwise.
old, new
Always the full zero OID on create and delete, never "".
pusher
The opaque principal: the JWT sub or the trusted forwarded principal. See Authentication.
correlation_id
The push's request id, also in _gitcask.request_id. An incoming x-request-id is honoured.
_gitcask.seq
The WAL sequence, as a JSON string.
_gitcask.entry_kind
push or ref_update; consumers must not care.

Delivery

Each catch-up posts one JSON array of events to events.webhook_url.

Request headers
Content-Type:        application/json
X-Gitcask-Delivery:  <sha1 hex of the body>
X-Gitcask-Signature: sha256=<hex HMAC-SHA256(body, events.webhook_secret)>
Webhook answersBridge does
2xxAdvances the cursor
Anything else, or no answer in 10 sKeeps the cursor and retries the same range on the next wake-up

Guarantees

At least once
The cursor advances only after a 2xx, so whole batches can repeat.
Dedup key
(repo, _gitcask.seq, ref_name), or X-Gitcask-Delivery per batch.
Order
By seq within a repository. Nothing across repositories.
No no-ops
old == new emits nothing; so do HEAD retargets, compactions and checkpoints.
Gaps
Only when the bridge lags behind log retention. Counted in events_bridge_gap_total, never repaired silently.

Run the bridge

Run one instance with the events role.

gitcask.toml
[server]
roles = ["events"]            # or leave roles empty on a one-box install: every role, bridge included
[events]
webhook_url = "https://hooks.example.com/gitcask"
webhook_secret = "…"          # env: GITCASK__EVENTS__WEBHOOK_SECRET
sweep_interval = "5m"
Wake-upRole
POST /_events/notifyBucket notification for a …/manifest.pb: S3 Records[], {"key": …} or {"repo": "o/r"}. Authenticated like every route.
sweep_intervalBackstop and health check, default 5m. A sweep that publishes anything raises events_bridge_sweep_found_total.

Consumer checklist

  1. Verify the signature

    Check X-Gitcask-Signature with a constant-time compare before parsing, if you set a secret.

  2. Dedup

    On (repo, _gitcask.seq, ref_name), or on X-Gitcask-Delivery per batch.

  3. Order within a repository

    Sort by _gitcask.seq. Do not assume order across repositories.

  4. Backfill on a gap

    Read the missed entries from the WAL.

    gitcask wal ls <repo> --from <seq>