Provider guide

GitHub webhooks, kept properly.

GitHub's webhooks are well behaved and well documented, and GitHub even keeps recent deliveries for you to inspect and redeliver. The catch is the word recent, and the fact that the record lives inside the hook you are debugging.

Field notes

How GitHub behaves, and where it bites

Good headers, a delivery id, and a redelivery button that only reaches back so far.

How GitHub sends

GitHub POSTs JSON with the event in X-GitHub-Event (push, pull_request, issues), a unique X-GitHub-Delivery, and an HMAC in X-Hub-Signature-256 prefixed sha256=. A new hook gets a ping first.

Where it bites

  • The delivery log is not an archive. GitHub shows recent deliveries per hook. Older ones age out, and with them the ability to redeliver.
  • Hooks multiply. Repository, organisation and app-level hooks all fire, and tracing which one delivered what means opening each in turn.
  • The event name is in a header, not the body. Anything that logs only the JSON loses the event type, which is the first thing you want when searching.

The vault under it

Point a WebhookVault endpoint at GitHub and every delivery is stored in full: headers verbatim, bodies byte-for-byte, labelled with its event name and searchable. Forward it onward to your own handler if you want the relay pattern, replay any delivery once the bug is fixed, and let a Watchdog notice the day it stops arriving.

01

Create an endpoint

Name it github-ci. Turn on forwarding to your own handler if you want the vault in front of it rather than beside it.

02

Register the capture URL

Repository or organisation Settings → Webhooks → Add webhook. Content type application/json, paste the capture URL, set a secret.

03

Keep everything

Every delivery from here on is on the shelf: searchable, replayable, and watched for the day it goes quiet.

Verify from the record, not just at the door

GitHub signs the raw body with your webhook secret and sends the digest as lowercase hex behind a sha256= prefix. The vault strips the prefix and verifies every capture.

The vault stores the signature headers verbatim with every request, so a delivery from last month can still be checked today. Verdicts are part of Solo and up. How verification works →

captured · pull_request✓ verified
# headers, stored verbatim
"X-GitHub-Event": "pull_request"
"X-GitHub-Delivery": "4a2f8c10-b3d1-…"
"X-Hub-Signature-256": "sha256=8f3a2c41d9e6…"

{
  "action": "opened",
  "number": 142,
  "pull_request": {
    "title": "Cap compute array items",
    "user": { "login": "hannibalza" }
  }
}

Event catalogue

GitHub webhook events you'll see

A few of the events GitHub sends, not the full list: GitHub's own documentation has every one. Each arrives in the vault labelled with its event name.

EventWhat it tells you
pingSent once when the hook is created, to confirm the URL answers.
pushCommits or tags were pushed.
pull_requestA pull request was opened, closed, given new commits or otherwise changed; action says which.
issuesAn issue was opened, edited, closed or labelled.
releaseA release was published, edited or deleted.
workflow_runA GitHub Actions workflow run was requested, started or completed.

Questions

GitHub webhook questions, answered

The three that come up most when GitHub is wired to a new endpoint.

Does GitHub retry failed webhook deliveries?

No. GitHub makes one attempt, expects a response within ten seconds, and records the result. A failed delivery can be redelivered by hand from the hook's Recent Deliveries, or through the REST API, but only for the past three days. With a WebhookVault capture URL in front, every delivery is stored as it lands and can be replayed to your handler at any point in your retention window.

How do I verify X-Hub-Signature-256?

Compute HMAC-SHA256 over the raw body with the webhook secret, hex-encode it, prefix it with sha256= and compare it with the header in constant time. The older X-Hub-Signature header carries a SHA-1 version kept for compatibility. On Solo and up the vault does this at capture and puts the verdict on every row.

What is the ping event?

GitHub sends ping once when a webhook is created, carrying the hook's id and a line of zen, to check that the URL answers. It is an ordinary delivery: the vault stores it like any other, which makes it the quickest proof that a new hook reaches the endpoint.

Keep the deliveries GitHub forgets.

Free tier, no card. A capture URL in a few minutes.