GitHub Webhooks: Complete Setup Guide
Set up GitHub webhooks from scratch. Covers event types, payload structure, security configuration, and real-world automation examples.
Ozer
Developer & Founder of HookSense
GitHub webhooks let you build automated workflows that respond to repository events in real time. When someone pushes code, opens a pull request, creates an issue, or performs any of dozens of other actions, GitHub sends an HTTP POST request to your server with details about what happened. This is the foundation of CI/CD pipelines, chat notifications, deployment triggers, and countless other developer tools.
This guide walks you through setting up GitHub webhooks from scratch, covers the most important event types, shows you how to parse payloads, and shares real-world automation patterns.
Setting Up a GitHub Webhook
Repository-Level Webhooks
To create a webhook for a specific repository:
- Navigate to your repository on GitHub.
- Click Settings → Webhooks → Add webhook.
- Enter your Payload URL — the endpoint that will receive events.
- Set Content type to
application/json(recommended over form-encoded). - Enter a Secret — a random string used for HMAC signature verification.
- Choose which events to receive: "Just the push event," "Send me everything," or select individual events.
- Click Add webhook.
GitHub immediately sends a ping event to verify your endpoint is reachable. If your endpoint returns a 2xx status, the webhook shows a green checkmark.
Organization-Level Webhooks
Organization webhooks work the same way but receive events from all repositories in the organization. Navigate to Organization Settings → Webhooks to set them up. This is useful for company-wide CI/CD or audit logging.
Using HookSense During Setup
If you do not have a server ready yet, you can use HookSense to capture and inspect GitHub webhooks immediately:
- Create a free endpoint at hooksense.com.
- Use the HookSense URL as your Payload URL in GitHub settings.
- Trigger an event (e.g., push a commit) and see the payload appear in real time.
- When your server is ready, use the CLI to forward events:
npx hooksense listen -p 3000
Key GitHub Webhook Events
GitHub supports over 40 event types. Here are the ones developers use most frequently:
push
Triggered when commits are pushed to a branch. This is the most common event and powers most CI/CD pipelines.
{
"ref": "refs/heads/main",
"before": "abc123...",
"after": "def456...",
"commits": [
{
"id": "def456...",
"message": "Fix login bug",
"author": { "name": "Ozer", "email": "[email protected]" },
"added": ["src/auth.ts"],
"modified": ["src/login.ts"],
"removed": []
}
],
"repository": { "full_name": "ozers/myapp" },
"pusher": { "name": "ozers" }
}
pull_request
Triggered when a pull request is opened, closed, merged, labeled, or updated. The action field tells you what happened:
{
"action": "opened",
"number": 42,
"pull_request": {
"title": "Add dark mode support",
"user": { "login": "ozers" },
"head": { "ref": "feature/dark-mode" },
"base": { "ref": "main" },
"merged": false
}
}
issues
Triggered when an issue is opened, closed, labeled, assigned, or edited. Useful for project management integrations and chat notifications.
release
Triggered when a release is published, edited, or deleted. Common trigger for deployment pipelines and changelog generation.
workflow_run
Triggered when a GitHub Actions workflow is requested, in progress, or completed. Useful for chaining workflows or external monitoring.
star and fork
Triggered when someone stars or forks your repository. Useful for growth tracking and community notifications.
Payload Structure
Every GitHub webhook request includes these headers:
| Header | Description |
|---|---|
X-GitHub-Event | The event type (e.g., push, pull_request) |
X-GitHub-Delivery | A unique GUID for this delivery |
X-Hub-Signature-256 | HMAC-SHA256 signature for verification |
Content-Type | application/json |
User-Agent | GitHub-Hookshot/... |
The body contains the event-specific payload as JSON. Payloads can be large — a push event with many commits can be several KB.
Securing Your Endpoint
Always verify the X-Hub-Signature-256 header to ensure requests genuinely come from GitHub:
const crypto = require('crypto');
function verifyGitHubSignature(payload, signature, secret) {
const expected = 'sha256=' +
crypto.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
app.post('/webhooks/github', express.raw({ type: '*/*' }), (req, res) => {
const sig = req.headers['x-hub-signature-256'];
if (!sig || !verifyGitHubSignature(req.body, sig, WEBHOOK_SECRET)) {
return res.status(401).send('Unauthorized');
}
const event = req.headers['x-github-event'];
const payload = JSON.parse(req.body);
// Route by event type
handleEvent(event, payload);
res.status(200).send('OK');
});
Use crypto.timingSafeEqual() to prevent timing-based attacks. Never compare signatures with ===.
Real-World Automation Patterns
Auto-Deploy on Push to Main
if (event === 'push' && payload.ref === 'refs/heads/main') {
exec('cd /app && git pull && npm run build && pm2 restart app');
}
Slack Notification on New PR
if (event === 'pull_request' && payload.action === 'opened') {
await slack.post('#dev', {
text: `New PR: ${payload.pull_request.title} by ${payload.pull_request.user.login}`,
url: payload.pull_request.html_url
});
}
Auto-Label Issues
if (event === 'issues' && payload.action === 'opened') {
const title = payload.issue.title.toLowerCase();
const labels = [];
if (title.includes('bug')) labels.push('bug');
if (title.includes('feature')) labels.push('enhancement');
await github.issues.addLabels({
owner, repo,
issue_number: payload.issue.number,
labels
});
}
Trigger External CI on Pull Request
if (event === 'pull_request' && ['opened', 'synchronize'].includes(payload.action)) {
await triggerCIPipeline({
branch: payload.pull_request.head.ref,
sha: payload.pull_request.head.sha,
prNumber: payload.number
});
}
Debugging GitHub Webhooks
GitHub provides a "Recent Deliveries" tab in webhook settings where you can see every delivery attempt, the response code, and resend events. However, this interface is limited — you cannot search, filter, or forward events locally.
For a better debugging experience, point your GitHub webhook at a HookSense endpoint. You get real-time inspection, full payload viewing with syntax highlighting, signature verification badges, and the ability to replay any event. Use the CLI to forward events to your local server while developing.
Common Gotchas
- Payload size limits: GitHub truncates payloads larger than 25 MB. For push events with many commits, only the first 20 commits are included.
- Timeouts: GitHub expects a response within 10 seconds. Use a queue for heavy processing.
- Branch deletion: A push event with
deleted: truehasafterset to all zeros. Do not try to fetch the commit. - Duplicate deliveries: GitHub may retry if your response is slow. Always handle idempotency.
- IP allowlisting: GitHub publishes webhook IP ranges at
api.github.com/metaunder thehookskey.
Summary
GitHub webhooks are straightforward to set up but require careful attention to security and error handling. Always verify signatures, handle events idempotently, respond quickly, and use a tool like HookSense for development and debugging. With these practices in place, you can build reliable automations that respond to your repository events in real time.
Related
Try HookSense Free
Inspect, debug, and replay webhooks in real-time. No credit card required.
Get Started Free