Webhooks
rickub POSTs a signed JSON payload to your URL when things happen — pushes, ref create/delete, merge requests and reviews, issues and comments, CI runs, releases, membership, repository lifecycle — so your own services can react.
What a webhook is
A webhook is a URL you register against a repository or an organization. When a subscribed event happens, rickub POSTs a JSON payload to that URL out of band — your service wakes up and reacts, no polling needed. Common uses: mirroring pushes, pinging a chat room on a release, triggering a deploy after CI goes green, keeping an external tracker in sync with issues.
Webhook management lives in Settings → Webhooks — repository webhooks on the repository, organization webhooks on the org. Setting one up requires admin access to the repository or org. Organization webhooks receive every event from every repository the org owns; repository webhooks only their own.
Events you can subscribe to
A webhook subscribes to any subset of:
| Event | Fires when |
|---|---|
push |
commits are pushed to the repository |
create |
a branch or tag is created (the accompanying push fires too) |
delete |
a branch or tag is deleted |
merge_request |
a merge request is opened, closed, merged, or reopened |
merge_request_review |
a reviewer records a verdict (approved / changes requested) |
issues |
an issue is opened, closed, reopened, or commented on |
issue_comment |
a comment is posted or retracted on an issue or merge request |
ci_run |
a workflow run completes |
release |
a release is published (a draft turning published fires too) |
member |
someone is added to or removed from a repository or organization |
repository |
the repository is renamed, transferred, archived, unarchived, or deleted |
A test ping button on the settings page sends a synthetic ping event so you can verify the wiring before anything real fires.
Two boundaries are worth knowing, because they are deliberate rather than gaps:
deletefires forgit push --delete, for the delete buttons on the branches and tags pages, and for a ref deleted through the API. Engines older than thedeleteevent cannot reportgit push --deleteat all — those deletions are silent until the engine is upgraded (the buttons and the API always report).repository'sdeletedaction reaches organization webhooks only. A repository's own webhooks are deleted with it, so there is nothing of theirs left to deliver to — register an org webhook if you need to hear about deletions across a namespace.
The payload
Every delivery is a JSON envelope naming what happened and who acted:
{
"event": "push",
"action": "published",
"repo": "acme/api",
"org": "acme",
"sender": "octocat",
"timestamp": "2026-09-24T21:44:09Z",
"data": { "…": "…" }
}
repo (or org for organization events) is an "owner/name" string; action appears only on events that have one; sender is the acting user's handle when rickub knows one (a push relayed by CI carries the job, not a person). data carries the event-specific fields. This envelope is rickub's own shape — it is not GitHub's payload schema, and the event name travels in rickub's own headers, so a receiver written against GitHub's webhooks must be adapted (the mapping is one header rename and one body reshape; the per-event reference below is the contract to build against).
Three headers accompany the body:
X-Rickub-Event— which event fired (the same keys as the table above).X-Rickub-Delivery— the delivery's id. It is STABLE across automatic retries and fresh on a manual redelivery, so your receiver can de-duplicate the first and treat the second as a deliberate replay.X-Rickub-Signature-256—sha256=<hmac>of the raw body, keyed with the webhook's signing secret. Verify it before trusting a payload: HMAC-SHA256 over the exact bytes received, prepended withsha256=.
If you chose the application/x-www-form-urlencoded content type, the same JSON arrives wrapped in a payload= form field — unwrap that field and everything else on this page applies unchanged.
The signing secret is shown once, at creation — store it somewhere safe. Rotating it issues a new secret the same way. A receiver that verifies signatures should refuse deliveries that are missing the header: a request with no signature is not from rickub.
Per-event payload reference
The data object's fields, per event. This table is pinned to the code by a conformance test — if a field here is wrong, that is a bug.
| Event | action values |
data fields |
|---|---|---|
push |
— | ref (full ref), after (the new tip sha) |
create |
— | ref (full ref), ref_type (branch or tag) |
delete |
— | ref (full ref), ref_type (branch or tag) |
merge_request |
opened · closed · reopened · merged · comment_deleted |
number, title, base, head |
merge_request_review |
approved · changes_requested |
number, title |
issues |
opened · closed · reopened · commented · comment_deleted |
number, title, state (on the state changes; a comment carries no state) |
issue_comment |
created · deleted |
number, title, merge_request (true when the comment is on a merge request) |
ci_run |
the run's conclusion (success, failure, …) |
workflow, run_number, conclusion |
release |
published |
tag, title, prerelease |
member |
added · removed · role_changed |
member, plus permission (repository) or role (organization) |
repository |
renamed · transferred · archived · unarchived · deleted |
from, to — full "owner/name", on the move actions only |
ping |
— | zen, webhook_id |
Notes on the overlaps, so a receiver can pick the right subscription:
- A comment on an issue fires BOTH
issues(commented) andissue_comment(created) — subscribe to one, not both, unless you want two deliveries per comment.issue_commentcovers merge request comments too;issues'commentedcovers only issues. - A comment retraction fires
issues/merge_request(comment_deleted) andissue_comment(deleted), and never carries the comment's body — the point of a retraction is that the text stops being republished. - Creating a branch or tag fires
createANDpushfor the same event; deleting one fires onlydelete.
Deliveries: logs, retries, redelivery
Every delivery is one row in the webhook's log on the settings page, with its response status, duration, and — for a failed one — the error, so "the receiver answered 500" and "the hostname did not resolve" look different. A delivery times out after 10 seconds; a receiver that does slow work should acknowledge fast and process asynchronously.
A delivery that the receiver failed — a non-2xx response, a timeout, a connection reset — is retried automatically: up to 4 attempts total (the first try plus three retries), waiting 10 seconds, then 30 seconds, then 90 seconds between them. The whole automatic window is about two minutes, sized for a receiver that is redeploying or briefly saturated — not for one that is down for the day. Each retry re-POSTs the same payload bytes under the same delivery id, signed afresh; the log row's attempt n/4 counter is how you see how far it got.
Outcomes rickub itself refused are never auto-retried (re-sending cannot fix them): a webhook whose signing secret could not be read, or whose stored URL is unusable. A receiver that is still failing after the fourth attempt keeps its row in the log — fix the receiver and click Redeliver on the logged attempt to send the same payload again under a fresh delivery id.
Deliveries are dispatched out of band through a bounded queue; under sustained saturation an event can be shed rather than delivered; a shed delivery is logged with the reason, and the Redeliver button is its remedy.
URLs are validated
Payload URLs must be public HTTPS (or HTTP) endpoints: loopback, link-local and private-network addresses are refused at save time, so a webhook cannot be pointed at rickub's own internals or at your cluster's metadata service. Point webhooks at a host the public internet can reach.