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
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,updateordelete. Force is not an action; derive it.- ref_type
branchforrefs/heads/,tagforrefs/tags/,""otherwise.- old, new
- Always the full zero OID on create and delete, never
"". - pusher
- The opaque principal: the JWT
subor the trusted forwarded principal. See Authentication. - correlation_id
- The push's request id, also in
_gitcask.request_id. An incomingx-request-idis honoured. - _gitcask.seq
- The WAL sequence, as a JSON string.
- _gitcask.entry_kind
pushorref_update; consumers must not care.
Delivery
Each catch-up posts one JSON array of events to events.webhook_url.
Content-Type: application/json
X-Gitcask-Delivery: <sha1 hex of the body>
X-Gitcask-Signature: sha256=<hex HMAC-SHA256(body, events.webhook_secret)>| Webhook answers | Bridge does |
|---|---|
| 2xx | Advances the cursor |
| Anything else, or no answer in 10 s | Keeps 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), orX-Gitcask-Deliveryper batch.- Order
- By
seqwithin a repository. Nothing across repositories. - No no-ops
old == newemits 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.
[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-up | Role |
|---|---|
POST /_events/notify | Bucket notification for a …/manifest.pb: S3 Records[], {"key": …} or {"repo": "o/r"}. Authenticated like every route. |
sweep_interval | Backstop and health check, default 5m. A sweep that publishes anything raises events_bridge_sweep_found_total. |
Consumer checklist
Verify the signature
Check
X-Gitcask-Signaturewith a constant-time compare before parsing, if you set a secret.Dedup
On
(repo, _gitcask.seq, ref_name), or onX-Gitcask-Deliveryper batch.Order within a repository
Sort by
_gitcask.seq. Do not assume order across repositories.Backfill on a gap
Read the missed entries from the WAL.
gitcask wal ls <repo> --from <seq>