◆ docs

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:

  • delete fires for git push --delete, for the delete buttons on the branches and tags pages, and for a ref deleted through the API. Engines older than the delete event cannot report git push --delete at all — those deletions are silent until the engine is upgraded (the buttons and the API always report).
  • repository's deleted action 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 with sha256=.

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.

Heads up

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) and issue_comment (created) — subscribe to one, not both, unless you want two deliveries per comment. issue_comment covers merge request comments too; issues' commented covers only issues.
  • A comment retraction fires issues/merge_request (comment_deleted) and issue_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 create AND push for the same event; deleting one fires only delete.

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.

Was this page helpful?