
GitHub fires webhooks for almost every event in a repository: pushes, pull requests, issues, releases, deployments, workflow runs, security alerts, and more. They're how integrations, bots, and AI agents stay in sync with what's happening in code.
This guide covers what GitHub sends, how to verify it, what to do with it, and what production systems need beyond a basic handler.
What GitHub sends
GitHub webhooks are JSON POST requests. A pull_request event when a PR is merged:
{
"action": "closed",
"number": 42,
"pull_request": {
"id": 1234567890,
"number": 42,
"state": "closed",
"merged": true,
"title": "Add deterministic replay",
"user": { "login": "octocat", "id": 1 },
"head": { "ref": "feature/replay", "sha": "abc123..." },
"base": { "ref": "main", "sha": "def456..." }
},
"repository": {
"id": 9876543210,
"name": "hooksbase",
"full_name": "hooksbase/hooksbase",
"private": true
},
"sender": { "login": "octocat" }
}
Every GitHub webhook includes:
- A
X-GitHub-Eventheader naming the event type (pull_request,push,issues, etc.) - A
X-GitHub-Deliveryheader with a unique UUID (use it for idempotency) - A
X-Hub-Signature-256header with the HMAC of the payload - The full event payload in JSON
GitHub has 30+ event types and many of them have multiple action values (a pull_request event has 30+ actions: opened, closed, synchronize, review_requested, etc.). The full list is in the GitHub developer docs.
GitHub's signature scheme
Every webhook includes:
X-Hub-Signature-256: sha256=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
This is HMAC-SHA256(secret, raw_body), hex-encoded, with sha256= prepended.
Verification:
- Read the raw body before any JSON parsing
- Compute
HMAC-SHA256(your_secret, body)and hex-encode it - Compare constant-time with the value after
sha256=
GitHub does not include a timestamp in the signature, so there's no built-in replay protection — you have to rely on your own idempotency check using the X-GitHub-Delivery UUID.
GitHub's redelivery model
GitHub records webhook deliveries and lets you redeliver failed deliveries manually from the UI or through the API. If you need automatic redelivery for GitHub App webhooks, GitHub's own docs show how to build it as a scheduled script.
This means:
- Acknowledge fast — return 2xx in under one second
- Be idempotent on
X-GitHub-Delivery— duplicates can arrive during manual or API redelivery - Have a recovery story for events that did reach your edge but failed downstream — manual GitHub redelivery is useful, but it is not the same as having a relay-held delivery history and replay path
DIY: minimal GitHub webhook handler in Node
import { createHmac, timingSafeEqual } from 'crypto'
const secret = process.env.GITHUB_WEBHOOK_SECRET!
export async function POST(req: Request) {
const sig = req.headers.get('x-hub-signature-256')!
const event = req.headers.get('x-github-event')!
const delivery = req.headers.get('x-github-delivery')!
const body = await req.text()
const expected =
'sha256=' + createHmac('sha256', secret).update(body).digest('hex')
if (!timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return new Response('Bad signature', { status: 400 })
}
// Idempotency on `delivery` UUID
// ...
const payload = JSON.parse(body)
queueWork(event, payload)
return new Response('ok', { status: 200 })
}
This handles the basics. Production needs more:
- A persistence layer for the
X-GitHub-Deliveryidempotency check - A queue for async work
- A retry policy when
queueWorkfails - A replay mechanism for events older than GitHub's retry window
- A delivery history so you can answer "did we get this PR event?"
- Signature secret rotation
- Filtering — most repositories generate huge volumes of low-value events; you only want a handful
Hooksbase: receive GitHub webhooks without rebuilding the rest
Hooksbase treats GitHub as one of five pre-verified providers. After the request passes Hooksbase ingest auth, signature verification happens at the relay edge before the payload reaches your endpoint.
Setup:
- Create a webhook in Hooksbase with
provider: github - Paste your GitHub webhook secret (encrypted at rest)
- Put a small forwarder at the GitHub webhook URL. It should preserve the raw body and GitHub signature headers, then POST to the Hooksbase ingest URL with
Authorization: Bearer <ingest secret>. If your source is already a custom producer that can attach that header, it can call Hooksbase directly. - Add your application URL as the destination
You get:
- Signature verification before the event reaches your code
- Idempotency for your destination — every dispatch includes a unique
webhook-idheader you can dedupe on across retries. The originalX-GitHub-DeliveryUUID is captured asprovider.sourceIdand queryable on every delivery. - Retries with exponential backoff to your endpoint after Hooksbase accepts the event
- Routing rules by
X-GitHub-Event— sendpull_requestevents to one destination,pushto another,releaseto a third - Payload transforms — extract just the fields your agent needs (a Handlebars template or JSONPath)
- Provider-aware queryable fields —
provider.eventType(the GitHub event),provider.verifiedon every delivery - Deterministic replay — re-run a retained PR event from last week with the same payload bytes
- Delivery history and DLQ — every event is queryable; failures are inspectable
Common GitHub webhook use cases for AI agents
Patterns that work well with this architecture:
- Release notes agent —
releaseevents trigger an agent that drafts release notes from the merged PRs in that release. - PR review agent —
pull_request.openedevents trigger an agent that runs a first-pass review and posts comments via the GitHub API. - Incident response agent —
repository_vulnerability_alertevents trigger an agent that triages the vulnerability, checks affected versions, and pages on-call if critical. - Workflow monitoring agent —
workflow_run.completedevents withconclusion: failuretrigger an agent that classifies the failure (flake vs real bug) and routes accordingly. - Onboarding agent —
pull_request.openedfrom a first-time contributor triggers an agent that posts a welcome comment with relevant docs.
In each case, the relay layer handles ingest, verification, retries, replay, and observability. The agent itself is a focused HTTP handler.
Where to go next
- Verify provider webhooks for the verification model in detail
- Route events to the right agent for the routing model
- How to receive Stripe webhooks reliably for the same pattern with Stripe
- How to build an AI agent for the full agent build path
Start free at app.hooksbase.com, then use Starter+ provider verification when you're ready to receive signed GitHub webhooks through Hooksbase.