Skip to content
Integration

Consuming GitHub Webhooks: A Practical Integration Guide

Receive and process GitHub webhooks reliably — X-Hub-Signature-256 verification, fast acknowledgement, event filtering with X-GitHub-Event, redelivery, and a debugging checklist.

Published 6 min read
On this page

GitHub webhooks turn repository activity into HTTP requests: a push kicks off a build, an opened pull request runs a policy check, a completed workflow updates a deployment dashboard. They are the backbone of most CI/CD glue and ChatOps, and when they fail the symptom is usually silent — a build that never starts, a status check stuck pending forever. This guide covers consuming GitHub events reliably: verifying them, routing on event type, and recovering the ones that fail.

What GitHub webhooks are

A GitHub webhook is an HTTP POST GitHub sends when a subscribed event happens in a repository, organisation, or app. The event type travels in the X-GitHub-Event header (push, pull_request, workflow_run), a unique delivery id in X-GitHub-Delivery, and the payload is a JSON body describing what changed. The same URL receives many event types, so the header — not the body — is how you route.

Common use cases

  • CI/CD triggers — start a build or deploy on push to a branch, or on a workflow_run completing.
  • Pull request automation — label, assign, or gate merges from pull_request events; enforce checks before merge.
  • Release and deployment tracking — record release, deployment, and deployment_status events against your own systems.
  • ChatOps and notifications — surface issues, push, or workflow_run outcomes in chat.

Configuring the webhook in GitHub

Webhooks are configured per repository or organisation under Settings → Webhooks, or programmatically for a GitHub App. At a high level you provide the payload URL, set the content type (JSON is the sensible default), optionally set a secret used to sign deliveries, and choose which events to receive — everything, a curated selection, or just pushes. GitHub records every delivery with its request, response, and duration so you can inspect and redeliver from the UI.

Exact screens and options evolve, so confirm current details against GitHub’s webhook documentation rather than memorising a particular layout. Set a secret — an unsigned GitHub webhook is an unauthenticated public endpoint anyone can POST to.

GitHub expects a prompt response and records slow endpoints as failures, so acknowledge first and work later:

  1. Capture the raw request body for signature verification before parsing.
  2. Verify X-Hub-Signature-256.
  3. Read X-GitHub-Event and ignore types you do not handle.
  4. Enqueue the work keyed by X-GitHub-Delivery, and return 2xx.
  5. Run the build, check, or update asynchronously.

Filtering on X-GitHub-Event early keeps a busy repository’s noise out of your processing path. A HookWatch endpoint’s event-type allowlist can do this filtering at the edge so your service only receives the types it acts on.

Verifying authenticity

When you configure a secret, GitHub signs each delivery with the X-Hub-Signature-256 header: the literal prefix sha256= followed by the hex-encoded HMAC-SHA256 of the raw request body, keyed by your secret.

X-Hub-Signature-256: sha256=7d38cdd689735b008b3c702edd92eea23791c5f6d3f3d8b7c...

To verify, recompute HMAC-SHA256(secret, raw_body), hex-encode it, prefix it with sha256=, and compare against the header in constant time. Two rules matter: use the raw bytes (a re-serialised body breaks the hash), and compare in constant time to avoid leaking timing information. GitHub also sends an older SHA-1 X-Hub-Signature for compatibility — prefer the SHA-256 header and ignore the SHA-1 one. Worked examples are in how to verify webhook signatures.

HookWatch’s custom inbound signature scheme maps cleanly onto this: header name X-Hub-Signature-256, algorithm SHA-256, hex encoding, prefix sha256=. Configure that and invalid deliveries are rejected before your handler runs.

Common failure scenarios

  • Deliveries showing a red check — GitHub’s Recent Deliveries list marks failed attempts; each one shows the response your endpoint returned, which usually names the cause outright.
  • Signature mismatches — a body parser running before verification, or a mismatched secret after a rotation, produces 401s on otherwise valid events.
  • Routing on the wrong signal — branching on payload fields instead of X-GitHub-Event leads to events silently falling through.
  • Timeouts on heavy handlers — kicking off a synchronous build in the request causes GitHub to record a timeout and the check to hang.
  • Ping confusion — GitHub sends a one-off ping event when a webhook is created; a handler that assumes every delivery is a real event can error on it.

Testing workflow

GitHub’s Recent Deliveries page is the primary tool: it shows each delivery’s headers, payload, and response, and has a Redeliver button that resends the exact payload — invaluable for reproducing a failure after a fix without waiting for the event to happen again. Redelivery reuses the original X-GitHub-Delivery id, so your dedupe logic sees it as the same event.

For local development, expose your handler with a tunnel and point a repository’s webhook at it so real GitHub deliveries hit your machine. See testing webhooks locally; hookwatch tunnel forwards captured deliveries to a local process, letting GitHub reach your laptop through a HookWatch endpoint over public TLS.

Retry behaviour

Unlike payment providers, GitHub does not automatically retry a failed webhook delivery — each delivery is attempted once, and recovery is manual. Practically:

  • A 2xx is success; anything else (or a timeout) marks the delivery as failed in Recent Deliveries, and that is where it stays.
  • A failed delivery is not re-sent unless you redeliver it yourself, from the Recent Deliveries UI or the API — so failures need attention, not patience.
  • Because redelivery repeats the same X-GitHub-Delivery, your handler will see duplicates when you do redeliver.

This makes your own recovery story matter more with GitHub than with providers that retry: a missed deployment or workflow_run event stays missed until someone notices. The design principles behind retry policies for webhook consumers still apply — they just have to live on your side of the wire (or in a capture layer that can replay).

Idempotency

Dedupe on X-GitHub-Delivery, which is unique per delivery and stable across redeliveries and retries of that delivery. Record handled ids and make the effect idempotent — starting the same build twice for one push wastes runners and can race. A duplicate you have already processed should still return 2xx. The patterns are in deduplicating webhook events.

Debugging checklist

  1. Open Recent Deliveries for the webhook and find the failed attempt.
  2. Read the response body and status your endpoint returned on that attempt.
  3. Confirm the handler reads the raw body before any parsing.
  4. Verify the secret matches and X-Hub-Signature-256 recomputes correctly.
  5. Confirm you route on X-GitHub-Event and handle the ping event gracefully.
  6. Check the handler responds inside GitHub’s timeout with work deferred.
  7. Match application logs to the delivery by X-GitHub-Delivery.
  8. Confirm duplicate deliveries are deduped and still answered 2xx.
  9. If the fix is deployed, use Redeliver to reproduce, then verify the effect ran once.

See also GitHub webhooks and a GitHub webhook delivery that failed for GitHub-specific detail, and the general debugging walkthrough.

How HookWatch helps

Route GitHub through a HookWatch endpoint and every delivery is captured whole — headers including X-GitHub-Event and X-GitHub-Delivery, the body, your response, and each attempt — so a failed workflow_run still shows exactly what GitHub sent and what your handler said back. After a fix, replay the captured delivery instead of hunting for the Redeliver button, pause an endpoint during maintenance and resume it, and filter to just the event types you act on. Sustained failures roll up into an incident, so a broken CI hook pages you once with the evidence attached.

Get started

See what happened to every webhook.

HookWatch keeps the request, the response, and every attempt for each delivery — so the debugging, retry, and replay steps in this article are a matter of reading, not reconstructing.