Webhooks

A repository's events, delivered to your endpoint as signed HTTP requests in GitHub's event names, so the bots you already run keep working.

Creating a webhook

Under the repository's Settings → Webhooks, New webhook asks for a payload URL, shows a secret minted in the browser once, and lets you choose events. The secret is stored as the key the signature needs and no response ever carries it back; keep the copy the page showed you. A webhook is created, not edited: to change one, deactivate it and create another. Reading and writing webhooks needs the repository's policy permission, because the URLs customers configure routinely carry tokens.

The Webhooks page of a repository with no webhooks yet: one sentence saying what a webhook does, and a New webhook button
Settings → Webhooks, before any webhook exists
# the id is yours to mint: a UUID
curl -X PUT -H 'Sigbound-Version: 2026-08-14' -H 'Authorization: Bearer $SIGBOUND_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://ci.example.com/hooks/sigbound","secret":"...","events":["pull_request","check_run"],"active":true}' \
  https://api.sigbound.com/orgs/<space>/repos/<repo>/webhooks/<uuid>
# list:        GET    /orgs/<space>/repos/<repo>/webhooks
# deactivate:  DELETE /orgs/<space>/repos/<repo>/webhooks/<uuid>

The URL must be http or https and resolve to a public address: private, loopback and link-local ranges are refused when the hook is saved, and checked again immediately before every connection, so a record changed after configuration cannot redirect a delivery. A secret is at most 256 bytes. A repository holds at most 100 webhooks. A deactivated webhook keeps its delivery history.

Events

Exactly four names exist on the wire, and they are GitHub's; a fifth is refused.

EventSent when
pull_requestAn attempt is parked (opened; a re-park is reopened), lands (closed, merged: true), or is rejected or expires (closed, merged: false).
pull_request_reviewAn attempt is approved (submitted, state approved).
check_runA check run is posted.
pushA branch moves: ref, before, after, repository.full_name, pusher, sender, and the run that moved it when one did.

The body carries the subset real bots read: action, number, pull_request with number, state, merged, head, base and user, repository.full_name, and sender. It is never the whole GitHub document. user.login and sender.login carry the account id, which is stable where a handle is not.

Signed deliveries

Every delivery is an HTTP POST with a JSON body and these headers:

X-GitHub-Event: pull_request
X-GitHub-Delivery: <delivery id, stable across redeliveries>
X-Hub-Signature-256: sha256=<hex of HMAC-SHA256(secret, raw body)>
User-Agent: Sigbound-Hookshot
Content-Type: application/json

The signature is HMAC-SHA256 over the exact bytes of the body, keyed with the webhook's secret, hex-encoded, prefixed with sha256=: the same computation GitHub documents, so a receiver written for GitHub verifies it unchanged. Verify against the raw body before parsing it. Deduplicate on X-GitHub-Delivery: a redelivery carries the same id.

Delivery history and retries

Each webhook lists its deliveries: the event, the state, how many attempts were made, the last HTTP status, an error code, latency, and both bodies, the one sent and the one received. Bodies are kept for 30 days; after that the row remains and says its body is no longer kept.

GET  /orgs/<space>/repos/<repo>/webhooks/<uuid>/deliveries
POST /orgs/<space>/repos/<repo>/webhooks/<uuid>/deliveries/<delivery>/redeliver

A refused delivery is retried with exponential backoff, up to eight attempts, then marked terminal: a dead letter set by the platform, not by a person or the endpoint. A webhook that fails 24 consecutive attempts within an hour is switched off, so a receiver that is merely restarting is not; a receiver that has refused everything for an hour is. Redeliver sends a retained body again as a new row naming the one it retries, never an edit of history, and it does not re-enable a switched-off hook: fix the endpoint, then save the webhook again.