Webhooks
Webhooks are in beta: event coverage and payload fields may still change. A webhook sends a signed HTTPS request to your server when something happens in a repository or organization, such as a push, a new issue or a merged pull request.
Add a webhook
Repository admins open Settings → Webhooks in the repository; organization owners and admins open Webhooks in the organization's settings.
- Choose Add webhook.
- Enter your receiver's HTTPS URL and select events. All events
subscribes to every supported event. To test a new receiver first, select
only
webhook.ping. - Save, then copy the signing secret into your receiver. It is shown only once.
- Choose Manage, then Send signed ping, and check Delivery history to confirm your receiver accepted it.
- Edit the webhook to add the events you need.
A repository or organization can have up to ten webhooks.
From the terminal, pass an organization slug or owner/repo as the target:
| 1 | cafe webhook create acme/project --url https://hooks.example.com/gitcafe \ |
| 2 | --event issue.created --event pull.merged |
| 3 | cafe webhook ping acme/project <webhook-id> --request-id first-ping --expected-version <version> |
cafe webhook capabilities lists the supported events, and
cafe webhook readiness checks whether a webhook is ready to send.
Events
| Events | Sent when |
|---|---|
git.push | Branches or tags change by push, merge or sync |
git.ref_created, git.ref_deleted | Branches or tags are created or deleted |
repo.default_branch_changed | The default branch changes |
issue.created, issue.updated, issue.closed, issue.reopened | An issue is created, edited, closed or reopened |
issue.locked, issue.unlocked, issue.pinned, issue.unpinned | An issue is locked, unlocked, pinned or unpinned |
issue.assignees_updated, issue.labels_updated, issue.milestone_updated | An issue's assignees, labels or milestone change |
issue.linked, issue.unlinked | A pull request is linked to or unlinked from it |
issue.comment_created, issue.comment_updated, issue.comment_deleted | An issue comment changes |
issue.reaction_added, issue.reaction_removed | A reaction is added or removed |
pull.created, pull.updated, pull.closed, pull.reopened | A pull request is opened, edited, closed or reopened |
pull.synchronized | A pull request's source branch gets new commits |
pull.merged | A pull request merges in GitCafe |
pull.drafted, pull.ready, pull.locked, pull.unlocked | Draft or lock state changes |
pull.reviewers_updated, pull.assignees_updated | Reviewers or assignees change |
pull.labels_updated, pull.milestone_updated | Labels or milestone change |
pull.linked, pull.unlinked | An issue is linked or unlinked |
pull.review_submitted, pull.review_dismissed | A review is submitted or dismissed |
pull.comment_created, pull.comment_updated, pull.comment_deleted | A pull request comment changes |
pull.comment_hidden, pull.comment_unhidden | A comment is hidden or shown |
pull.comment_resolved, pull.comment_unresolved | A review thread is resolved or reopened |
pull.reaction_added, pull.reaction_removed | A reaction is added or removed |
label.created, label.updated, label.deleted | A repository label changes |
milestone.created, milestone.updated, milestone.deleted | A milestone changes |
ci.run_updated | A CI run's state changes |
webhook.ping | You send a test ping |
Events describe changes made in GitCafe. Issues and pull requests imported
from GitHub do not send issue.* or pull.* events, and past activity is not
sent when you add a webhook.
The request
Each delivery is an HTTPS POST with a JSON body. The body has an event id,
a type, a timestamp, the repository and organization IDs, the actor who
made the change, and a snapshot of the resource after the change. Edit events
include the fields that changed. Text bodies longer than 65,536 characters are
cut and flagged as truncated.
| Header | Contains |
|---|---|
X-GitCafe-Event | The event type, same as the body's type |
X-GitCafe-Delivery | The delivery ID, the same across retries |
X-GitCafe-Timestamp | When this attempt was signed, in Unix seconds |
X-GitCafe-Key-Version | Which signing secret was used |
X-GitCafe-Signature | sha256= followed by the hex HMAC |
The full payload schemas are in the webhooks section of the
OpenAPI description.
Verify the signature
Before you trust a request:
- Read
X-GitCafe-Timestampand reject it if it is too old for your liking. - Compute HMAC-SHA256 with your signing secret over the timestamp, a literal
., and the raw request body bytes. - Compare
sha256=<lowercase hex digest>withX-GitCafe-Signatureusing a constant-time comparison.
| 1 | import { createHmac, timingSafeEqual } from "node:crypto"; |
| 2 | |
| 3 | function verify(secret, timestamp, body, signature) { |
| 4 | const expected = `sha256=${createHmac("sha256", secret) |
| 5 | .update(`${timestamp}.`) |
| 6 | .update(body) |
| 7 | .digest("hex")}`; |
| 8 | return ( |
| 9 | expected.length === signature.length && |
| 10 | timingSafeEqual(Buffer.from(expected), Buffer.from(signature)) |
| 11 | ); |
| 12 | } |
Respond and handle retries
Return any 2xx status once you have stored the event. GitCafe retries on
network errors, timeouts (10 seconds), 408, 425, 429 and 5xx, honouring
Retry-After up to an hour, for up to eight attempts within 24 hours. Other
statuses and redirects end the delivery.
Deliveries can arrive more than once and in any order. Deduplicate by the
body's event id.
Inspect and redeliver
Under Manage, Delivery history shows each delivery's state, attempts and HTTP status; View attempts and View payload show the details. For a delivery that finally failed, fix your receiver, then choose Request redelivery to send the same event again. Each event can be redelivered twice, within 30 days.
| 1 | cafe webhook deliveries acme/project <webhook-id> --state failed |
| 2 | cafe webhook redeliver acme/project <webhook-id> <delivery-id> \ |
| 3 | --request-id retry-1 --expected-version <version> --event-created-at <timestamp> |
Delivery history is kept for 30 days.
Rotate the signing secret
Choose Rotate signing secret under More actions, or run
cafe webhook rotate-secret <target> <webhook-id> --yes. The new secret is
shown once. From then on every attempt, including retries of earlier events,
is signed with the new secret, so update your receiver straight away.
X-GitCafe-Key-Version tells you which secret signed a request.
Stop a webhook
Disable it to pause deliveries, or delete it under More actions. Both stop pending deliveries. Changing a repository's visibility or removing the person who created a webhook does not remove it; webhooks can carry private repository data, so review them when access changes.