Webhooks
Webhooks are in beta. Event coverage and payload schemas may evolve; the supported events and delivery limits are described below.
Outbound webhooks notify receivers of supported issue, pull-request, catalog, CI and Git changes. Subscribe
to individual names below or *. Other syntactically valid subscription names
remain accepted for configuration compatibility but produce no deliveries.
Provider issue/PR imports do not emit local lifecycle events. Supported native Git settlement paths are listed below. Issue deletion,
transfer and comment moderation events are not available. No historical backfill is provided. Administrators can queue signed pings and
bounded manual redelivery as described below.
Supported catalog, repository and CI events
| Event | Committed change |
|---|---|
label.created, label.updated, label.deleted | Local label lifecycle |
milestone.created, milestone.updated, milestone.deleted | Local milestone lifecycle, including state changes |
repo.default_branch_changed | Native-confirmed default-branch metadata settlement |
ci.run_updated | Accepted authenticated Buildkite run observation |
Catalog events include the resulting public snapshot; deletion retains the last
snapshot. Updates also include previous values from the owning transaction.
These events describe the catalog object rather than emitting an event for every
assigned issue or PR. Default-branch events include the old/new branch and resulting
configuration generation. CI events include the run/check identities, version,
commit, ref, state, context, external URL and execution timestamps. They omit a
human actor because a provider observation does not establish one. Duplicate or
stale provider observations and replayed default-branch requests emit nothing.
Supported Git activity events
| Event | Successful native publication |
|---|---|
git.push | Public branch/tag ref changes from a local push, branch operation or merge |
git.ref_created | A batch of public branches/tags created by that operation |
git.ref_deleted | A batch of public branches/tags deleted by that operation, including committed merge cleanup |
Git activity includes HTTP and SSH pushes, browser branch creation, deletion and restoration, committed local merges and committed source-branch cleanup. It also includes settled mirror fetches, GitHub native synchronization/imports and restack ref writes. Fork copying and direct upstream synchronization remain excluded until their native effect owners retain durable publication evidence. Capture uses the exact successful native receipt and the database publication transaction; a request being accepted or a native result being uncertain emits nothing. Recovery reaches the same publication owner, and replay adds no event. A committed merge can emit Git activity even if its PR diverged before publication. Unrelated native observations do not emit Git activity. Provider issue/PR metadata imports remain outside local resource lifecycle events.
The git snapshot identifies operationId, source (push, merge,
merge_cleanup, mirror, github_sync or restack) and native authorityOrdinal. Each refUpdates entry contains
the full public ref name, exact nullable beforeOid/afterOid, and
created/deleted flags. forced is present only when the native receipt
establishes that fact; absence does not mean a fast-forward. Creation has a null previous OID; deletion has
a null resulting OID. Commit lists and file changes are not enumerated.
Each event includes up to 100 matching public ref updates. refUpdatesCount
counts all matching public updates for that event; refUpdatesTruncated explicitly
indicates an incomplete list. A mixed durable publication emits at most three events,
regardless of ref count. A native GitHub batch can settle several per-ref jobs;
each job publishes its own owned ref changes and has its own operation ID. git.ref_created and git.ref_deleted are batch events,
not one delivery per ref. Internal scratch, retained-deletion and cross-fork pin
refs are omitted before counting; an operation touching only internal refs emits
nothing. Subscribers to * can receive both the push summary and its creation or
deletion batches. Deduplicate individual webhook events by their signed event ID;
use operationId to correlate those distinct events.
Supported pull-request events
| Event | Successful local operation |
|---|---|
pull.created, pull.updated | Create or edit a pull request |
pull.synchronized | An authoritative observation changes the PR head |
pull.closed, pull.reopened | Close or reopen a pull request |
pull.drafted, pull.ready | Mark as draft or ready |
pull.locked, pull.unlocked | Lock or unlock |
pull.reviewers_updated, pull.assignees_updated | Replace reviewers or assignees |
pull.labels_updated, pull.milestone_updated | Replace labels or assign/clear milestone |
pull.linked, pull.unlinked | Upsert or remove an issue link |
pull.comment_created, pull.comment_updated, pull.comment_deleted | Create, edit or delete a published comment |
pull.comment_hidden, pull.comment_unhidden | Hide or unhide a published comment |
pull.comment_resolved, pull.comment_unresolved | Resolve or unresolve a published thread |
pull.reaction_added, pull.reaction_removed | Add or remove a PR or published-comment reaction |
pull.merged | Publish a completed local native merge, including a landed stack member |
pull.review_submitted, pull.review_dismissed | Submit or dismiss a published review |
Pull payloads include the immutable ID, number and title, description, resulting
state/draft/version, author identity, source repository and source/target branches,
and the observed head OID when available. The envelope identifies the performing
actor.id; it can differ from the resource author. Edit events include changes
with the submitted fields. Assignment events include the resulting actor IDs;
label and milestone replacements include their IDs and names/titles. Link events
identify issueId and linkType; the same transaction emits the issue event.
Replayed creation requests and rejected lifecycle transitions emit nothing.
Accepted edits and assignment replacements notify even when values are equal.
Comment events retain commentId and a published comment snapshot including
body, author, version, thread/review identity, inline location, and moderation
state. Deletion retains the comment's pre-deletion content. Review events retain
identity, author actor ID, verdict, reviewed commit OID, published body and version; dismissal
includes its reason. Submission includes inlineCommentCount, inlineCommentIds and bounded
inlineComments with public body/location snapshots. The snapshot batch is
bounded to 64 KiB of serialized JSON; inlineCommentsTruncated marks omitted
content, and individual body truncation remains explicit. Submission emits one
summary for the review and its published draft inline comments, without separate
comment-created events for those comments. Private drafts, review request
replays and duplicate reaction additions emit nothing. Reaction events include
subject identity, emoji, and the reaction/actor identity when available.
Merge events include merge.strategy, merge.sourceOid, merge.targetOidBefore
and merge.resultOid. They notify only after exact native committed proof and
successful PR/operation publication; each landed stack member emits individually.
An accepted merge request, uncertain native result, failed/rolled-back publication,
a diverged PR and completed-operation replay emit nothing. If ownership changes
before publication, recipients use the repository's current locked organization.
Provider merge projections remain unavailable. pull.synchronized includes
synchronization.beforeOid, afterOid, source and the observed authority ordinal
watermark. It describes an observed head change, not proof of the reader causing
that Git write; it omits the actor. Cross-fork destination heads require the
existing authorized native pin/refresh workflow: a source push does not eagerly
notify a PR-only destination subscription. Base-only and unchanged observations emit no
synchronization event.
Supported issue events
| Event | Successful local operation |
|---|---|
issue.created | Create an issue |
issue.closed | Close an open issue as completed or canceled |
issue.updated | Update issue metadata |
issue.reopened | Reopen a closed issue |
issue.locked, issue.unlocked | Lock or unlock an issue |
issue.pinned, issue.unpinned | Pin or unpin an issue |
issue.assignees_updated | Replace assignees |
issue.labels_updated | Replace labels |
issue.milestone_updated | Assign or clear a milestone |
issue.linked, issue.unlinked | Upsert or remove an issue's pull-request link |
issue.comment_created, issue.comment_updated, issue.comment_deleted | Create, edit or delete an issue comment |
issue.reaction_added, issue.reaction_removed | Add or remove a reaction on an issue or issue comment |
Events describe committed mutations, rather than a before/after difference.
Metadata, assignment, link-upsert, comment-edit, lock, unlock and unpin operations
that advance a version still emit when the requested value equals the old value.
An already-pinned issue and an already-present reaction return without inserting
an event. Rejected writes, missing-link removals and rolled-back transactions
produce none. Closing again does not emit issue.closed; reopening has its own
event.
Issue snapshots contain ID, number, title, description, resulting state/version,
priority, due date, milestone ID, lock/pin state and closure/lock reasons. Edit
events include submitted changes; assignment replacements include the resulting
assignee or label IDs, with label names and milestone identity/title where
applicable. Links retain the PR identity and link kind. Comment events include
commentId and the immutable body, author, version and timestamps, including
content removed by deletion. Reactions retain their subject, emoji and identity.
Snapshots describe the resource at capture time, not its current state or a
complete before/after diff. Treat supplied replacement lists as complete values,
including empty lists and explicit null when clearing a milestone. The
performer's actor.id is distinct from authorship. New captures include enriched
fields; previously queued version 1 payloads can omit them and retain their exact
original bytes on retry or manual redelivery. Consumers must tolerate those older
payloads. Bodies include up to 65,536 characters. Larger imported PR descriptions,
comments or reviews retain the first 65,536 characters and set
descriptionTruncated or bodyTruncated; ordinary local content is complete.
Webhook administrators should treat delivered content as repository data,
including deleted or moderated published comments. Credentials and private
review drafts are excluded.
Delivery requires configured encryption and an operator-approved outbound
transport. The default trusted_origins transport supports only exact operator-trusted HTTPS origins
in WEBHOOK_ALLOWED_ORIGINS, a comma-separated list such as
https://hooks.example.com,https://events.example.org. Origins must use port 443
and contain no path, query, credentials, wildcard, IP literal or local hostname.
An empty list disables HTTP delivery execution in this mode. Queued rows remain pending
within their age budget; scheduled expiry still runs while sending is disabled.
Customers can save URLs outside the list, but those destinations cannot be sent
by this transport. Operators must control the trusted domains and their DNS
destinations; this restriction does not establish public-IP connection enforcement
or DNS-rebinding protection. Instances may explicitly configure WEBHOOK_TRANSPORT_MODE=public_https to use
an HTTP/1.1 sender over existing native TLS sockets instead of ordinary fetch.
This mode permits HTTPS domain receivers on port 443 when the origin list is
empty; a nonempty list further restricts it during rollout.
It requires a qualified runtime enforcing the public-only socket destination
policy; private/mixed DNS answers are rejected before connection. Redirects are
not followed, response headers are bounded and connections close after each
attempt. Public mode is opt-in and requires qualification on your instance. An active endpoint alone does not prove
successful delivery. This page describes the implementation contract; deployed
availability and transport qualification require evidence from your instance's
release.
This API is separate from the incoming Buildkite integration.
Access and endpoint paths
Organization administrators use /api/orgs/:slug/webhooks. Repository
administrators use /api/repos/:owner/:repo/webhooks. Credentials also need the
corresponding organization or repository webhooks/read or webhooks/write scope.
An endpoint ID belongs to the organization or repository where it was created.
Each repository or organization can have up to ten endpoints, including inactive ones. Creating another at the limit returns 400 without saving an endpoint or key. Delete an endpoint to free a slot. Existing configurations remain editable even if the owner already exceeds this limit.
The following paths are relative to the chosen base path:
| Operation | Request | Response |
|---|---|---|
| List | GET /?limit=50&after=<next> | { items, next } |
| Read | GET /:id | Endpoint metadata |
| Create | POST / with url, events, optional active and secret | 201 { endpoint, secret } |
| Update | PATCH /:id with expectedVersion and changed url, events, or active | Updated metadata |
| Delete | DELETE /:id with expectedVersion | { id } |
| Rotate secret | POST /:id/rotate-secret with expectedVersion | { endpoint, secret, previousExpiresAt } |
| Deliveries | GET /:id/deliveries?limit=50&after=<next> | { items, next } |
| Ping | POST /:id/ping with requestId, expectedVersion | 202 { requestId, deliveryId, eventId, eventCreatedAt } |
| Redeliver | POST /:id/deliveries/:deliveryId/redeliver with requestId, expectedVersion, eventCreatedAt | Same durable receipt |
| Attempts | GET /:id/deliveries/:deliveryId/attempts?eventCreatedAt=<ISO timestamp> | { items, next } |
Browser setup and inspection
Repository administrators can open /<owner>/<repo>/settings/webhooks;
organization owners and admins can open /orgs/<handle>/settings/webhooks.
Choose Webhooks in Settings. The page requires settings access and scoped
webhook access, even when other repository metadata is publicly readable.
If adding is disabled because signing secrets are not configured, ask the
instance administrator to complete that prerequisite first. Sending may remain
disabled while you save endpoint configuration.
- Choose Add webhook, enter the HTTPS receiver URL and select events.
All events subscribes to the supported catalog. For an initial receiver
check, select only
webhook.ping. Save the configuration. - Copy the shown-once signing secret into your receiver, then dismiss it. Creation and Rotate signing secret show the key locally; reloading or leaving the page loses it. If you lose a key, rotate it and update the receiver.
- Choose Manage for the receiver. Resolve any visible configuration prerequisites; Ready to send means configuration is ready, not that the receiver has accepted a delivery. Expand Configuration checks for details. Choose Edit webhook to change configuration; More actions contains secret rotation and deletion.
- Choose Send signed ping, then inspect Delivery history. A queued receipt confirms durable admission. Refresh deliveries observes progress and View attempts shows retained HTTP outcomes. Pages load more history on request rather than continuously polling.
- For an eligible terminal original, choose Request redelivery. Recovery keeps the original event and body; the limits below still apply. Copies cannot be redelivered through the page.
If a ping or redelivery response is uncertain, choose Retry same request to recover its receipt. This retains the original request identity and expected version. Avoid starting another operation to work around an uncertain result. If endpoint creation is uncertain, refresh the webhook list and check for the saved endpoint before creating another. Creation has no equivalent request-ID replay guarantee.
Edit webhook retains the version originally opened for editing. A background refresh does not silently replace that draft. If another administrator changed the endpoint, cancel and reopen the editor to review the current configuration before saving again. Delete webhook requires confirmation and stops pending deliveries; it cannot retract a request already accepted by the receiver.
Save and update configuration
Provide an HTTPS URL without embedded credentials or a fragment, and a nonempty list of distinct event names. Saving validates configuration and does not contact the receiver. Delivery also depends on destination policy and current source repository/organization identity and liveness. Endpoints are persistent organization/repository administration settings; they can receive private repository events. A visibility change or the creator losing their role does not remove an endpoint. Administrators disable or delete it to stop new sends.
Read the latest endpoint version and send it as expectedVersion when updating,
deleting or rotating a secret. A stale version returns 409. Read the endpoint
again, review the current values, then retry your intended change.
Signing secrets
Creation and rotation return the signing secret once. Save it securely before closing the response; subsequent reads never return it. Supply a secret of 16–128 characters or omit it to generate one.
Rotation replaces the current secret. The immediately preceding encrypted secret
becomes eligible for cleanup after 24 hours; the next rotation removes it sooner.
The response includes previousExpiresAt. Scheduled cleanup is bounded, so
physical removal may lag that time. Every attempt uses the endpoint's current signing
key. Rotation preserves the immutable event body and delivery identity, while
the timestamp, signature and key-version header change on a retry. Install the
new key at your receiver before removing the old key.
If your instance cannot securely store secrets, creation and rotation fail without saving the requested change. Ask the instance operator to configure secret storage. Reading, updating and deleting existing endpoint configuration remain available.
Receive and verify an event
The request is an HTTPS POST with Content-Type: application/json. Version
1 includes an event ID, a supported type, its capture timestamp,
repository and organization IDs, and a resource snapshot. New issue captures
include description text; comment and review captures include published content.
Optional fields may be absent in older retained events. Closure events include issue.state as completed or
canceled. The exact serialized UTF-8 body remains unchanged across
attempts for that delivery.
Read X-GitCafe-Timestamp as Unix seconds and X-GitCafe-Key-Version as the
signing-key generation. Compute HMAC-SHA256 with that key over the timestamp
string, a literal ., and the unchanged request body bytes. Compare
X-GitCafe-Signature with sha256=<lowercase hexadecimal digest> in constant
time. Reject timestamps outside your receiver's replay window. Verify before
parsing or accepting the event.
X-GitCafe-Event matches the body's type. Deduplicate by the signed body's event id
within your receiver subscription. X-GitCafe-Delivery identifies delivery history
and remains stable across retries; headers other than the timestamp are not
covered by the signature. Store acceptance durably before returning a 2xx response.
A receiver may accept a
request whose response is lost, so retries can duplicate accepted requests.
No delivery ordering is guaranteed.
Delivery failures and inspection
Any 2xx response acknowledges acceptance. Network failures, timeouts, 408, 425,
429 and 5xx responses are retryable; redirects and other statuses terminate the
delivery. Attempts stop after eight attempts or 24 hours from event capture.
Backoff is bounded and honors Retry-After up to one hour. Each attempt has a
10-second timeout. Receiver response bodies are cancelled without reading or
retaining them. Missing sender encryption configuration retries within the same
budget rather than reporting success. With trusted-origin execution disabled by an empty
allowed-origin list, no attempt is made. Scheduled maintenance expires at most
twenty aged queued or expired-lease running deliveries per invocation, preserving
live leases and unknown attempt evidence. This is bounded cleanup rather than a
guarantee that an entire backlog terminalizes exactly at 24 hours. Guarded history
pruning follows expiry; see History maintenance below for retention limits.
Use the delivery list to inspect state, attempt count, retry time, HTTP status
and redacted error category. For attempt inspection, pass the delivery's exact
eventCreatedAt; it identifies the partition together with deliveryId.
Attempt outcome started means no conclusive settlement is retained, including
after a worker crash or expired lease. History lists omit payloads, signing headers, receiver bodies and secrets.
Open View payload for a delivery to lazily fetch its original immutable JSON;
this includes published content later deleted or moderated. Inspection requires
current webhook read authority and a live source still belonging to its captured
organization. Moved/deleted sources are unavailable. The response is private and
not cached; signing headers and receiver bodies are never returned.
The API exposes GET .../deliveries/:deliveryId/payload?eventCreatedAt=....
For push-sourced Git events, GET .../deliveries/:deliveryId/ref-updates with the
same timestamp returns retained public ref updates in indexed pages. Supply
after from next and a limit of 1–100. Pages follow public ref name
order; private refs and their positions are excluded. Creation/deletion filtering
can produce an empty public page with a next continuation.
Creation/deletion events return only their matching refs. Mirror, sync, merge and
restack continuation is not supported by this endpoint. Delivery and attempt
history use their own next cursors.
Fix receiver or instance configuration when a delivery is terminal or dead-lettered, then use the bounded recovery procedure below for retained events. Endpoint edits, disabling, deletion or lost source authority can cancel pending deliveries; these actions cannot retract a request already accepted by a receiver. Deleting an endpoint preserves durable delivery evidence but removes that endpoint's public history path.
For management and outgoing request schemas and examples, open /api/openapi
on your instance. The JSON document is available at /api/openapi/json; outgoing
events are modeled under its OpenAPI 3.1 webhooks section.
Inspect readiness and history with Cafe
Pass an organization slug or owner/repository as the webhook target. Inspect
capabilities before selecting events, then check the configured endpoint:
| 1 | cafe webhook capabilities acme/project |
| 2 | cafe webhook readiness acme/project <webhook-id> |
| 3 | cafe webhook deliveries acme/project <webhook-id> --state failed |
| 4 | cafe webhook dead-letter acme/project <webhook-id> |
| 5 | cafe webhook attempts acme/project <webhook-id> <delivery-id> --event-created-at <exact-eventCreatedAt> |
GET /capabilities and GET /:id/readiness are relative to the organization or
repository webhook base path. They require the same webhook read authority as
endpoint management. Capabilities list supported event names and API version,
plus encryption configuration and execution state (disabled, invalid or
configured). Readiness additionally reports endpoint activation, destination
admission, version support and unsupported subscription names. ready means
these configuration checks permit an attempt with at least one supported
subscription; it does not prove network reachability or receiver acceptance.
These reads make no outbound request and do not expose operator origin lists or
signing keys. Unsupported names remain stored for compatibility.
Delivery history accepts an optional state query filter. CLI --cursor carries
the returned next; --limit bounds the page. Dead-letter inspection is a
filtered history read, not a separate queue. Attempt inspection requires the
exact eventCreatedAt from delivery history. Outcome started can represent an
unknown result after a crash; no conclusive receiver acceptance is recorded.
Ping and redelivery return durable admission receipts; use delivery history to
inspect receiver outcomes.
Signed ping and manual redelivery
These operations require organization or repository administrator authority and
webhooks/write scope. The endpoint must be active, its destination permitted,
and instance encryption and sending configured. Read readiness and the current
endpoint version first. You can configure an active endpoint with only the
webhook.ping subscription while testing, then add business subscriptions after
receiver acceptance. Ping does not require a matching subscription and only
notifies the endpoint you select.
Queue a ping with a stable request ID and the observed endpoint version:
| 1 | cafe webhook ping <org-or-owner/repo> <endpoint-id> \ |
| 2 | --request-id receiver-check-1 --expected-version <version> |
The signed webhook.ping body includes its event ID, version, time, endpoint ID
and a strict owner: either { kind: "repo", repoId, orgId } or
{ kind: "org", orgId }. An organization needs no repository to test its
endpoint. Ping uses the same signing, timeout, lease and retry behavior as
business notifications. Each endpoint admits at most one new ping per minute
and cannot admit another while a ping is queued/running.
A 202 response or CLI receipt means the intent was committed, not that the
receiver accepted it. Use deliveryId and eventCreatedAt from the receipt to
inspect deliveries and attempts. Retry a lost response with the same request ID
and exact input to receive the same receipt. Reusing that ID for a different
action, version or original delivery returns a conflict. Recovery keys expire at
the underlying event's thirty-day history horizon; a retired request is not a
promise of indefinite replay.
To redeliver, find the terminal original delivery (deliveryOrdinal: 0, shown
as cycle 0 in CLI history) and its exact eventCreatedAt, then submit:
| 1 | cafe webhook redeliver <org-or-owner/repo> <endpoint-id> <original-delivery-id> \ |
| 2 | --request-id repair-1 --expected-version <version> \ |
| 3 | --event-created-at <exact-history-timestamp> |
Manual redelivery creates a new delivery ID and attempt history while retaining the original event ID, exact body bytes, event timestamp and captured URL. The original state and uncertain attempts remain intact. It signs with the current key; rotation does not regenerate the body. There are at most two additional cycles per event and endpoint, each with eight attempts and a 24-hour budget. Only original deliveries can enroll a cycle; redelivering a copy cannot bypass the cap. Another cycle cannot be admitted while that event has queued/running work. The original event must remain retained and be less than thirty days old.
The current endpoint must still admit that event, use the captured URL and own the live source. Restore a changed URL or subscription before retrying; a moved or retired source cannot be revived through redelivery. A succeeded original may be redelivered, but a receiver deduplicating the stable event ID can acknowledge it without processing it again. This operation cannot force repeated processing.
For a conflict, reread endpoint version, original delivery identity, source and history before deciding whether a new request ID is appropriate. For admission failure, check readiness, endpoint activation, pending work, the ping cooldown and cycle limit. Do not automatically generate new request IDs when retrying an uncertain API response.
History maintenance
The API minute schedule expires aged work and prunes eligible webhook history older than thirty days even when sending is disabled. Pending consumer work, queued/running deliveries and oversized legacy groups keep their evidence. Cleanup selects indexed due events, without enumerating repositories. Guarded groups are deferred for an hour before another check. Each pass examines at most 100 events and processes at most twenty groups with delivery history; each group is limited to sixty deliveries and 480 attempts. Cleanup is gradual; thirty days is an eligibility threshold, not an exact removal deadline. Removed history is no longer available in delivery/attempt inspection.
Operators can inspect API logs for [api] webhook history retirement. The
summary reports selected candidates, retired events, guarded candidates and
removed delivery/attempt rows; it contains no destination, payload or secret.
The summary also counts expired recovery keys and encrypted retiring signing
keys removed by bounded maintenance. If old history stays,
check cron failures, pending consumers and nonterminal deliveries first; expiry
must drain aged work before retirement can remove it. Legacy histories exceeding
sixty deliveries or 480 attempts per event need separately reviewed maintenance.
Do not detach partitions or remove pending evidence as a recovery shortcut.
Row pruning permits vacuum reuse; it does not promise filesystem space recovery.
Before enabling a release, verify a controlled receiver through actual Worker transport and queue/cron execution: successful signed delivery, retry and lost response, lease recovery, key rotation, endpoint/source changes, and expiry and history pruning with sending disabled. Local tests do not establish deployed receiver acceptance or large-backlog scan cost.