Security Headers
GitHub sends these headers with each webhook request.
| Header | Description |
|---|---|
X-GitHub-Event | Event type (push, pull_request, etc.) |
X-Hub-Signature-256 | HMAC SHA-256 signature for verification |
X-GitHub-Delivery | Unique delivery ID |
User-Agent | Always starts with GitHub-Hookshot/ |
Signature Verification Steps
How to verify GitHub webhook signatures securely.
- Read raw request body
- Compute HMAC SHA-256 with your secret
- Compare with
X-Hub-Signature-256header - Use constant-time comparison to prevent timing attacks
Why it matters:
- Prevents unauthorized webhook calls
- Ensures request is from GitHub
- Protects against replay attacks
Common Events
GitHub webhook event types and their triggers.
| Event | Trigger |
|---|---|
push | Code pushed to repository |
pull_request | PR opened, closed, or updated |
issues | Issue created or modified |
release | Release published |
workflow_run | GitHub Actions workflow completed |
create | Branch or tag created |
delete | Branch or tag deleted |
fork | Repository forked |
star | Repository starred |
watch | Repository watched |
Event Payloads
Example payload structures for common events.
Push Event
{
"ref": "refs/heads/main",
"before": "abc123...",
"after": "def456...",
"repository": {
"name": "my-repo",
"full_name": "user/my-repo"
},
"pusher": {
"name": "username",
"email": "user@example.com"
},
"commits": [...]
}
Pull Request Event
{
"action": "opened",
"number": 42,
"pull_request": {
"title": "Fix bug",
"state": "open",
"user": {"login": "username"},
"head": {"ref": "feature-branch"},
"base": {"ref": "main"}
}
}
Testing - ngrok
Expose local server for webhook testing.
# Install ngrok
# Download from https://ngrok.com/download
# Expose local server
ngrok http 3000
# Copy HTTPS URL to GitHub webhook settings
# Example: https://abc123.ngrok.io/webhook
Alternative: Use GitHub webhook delivery tab to redeliver past events
Testing - Manual cURL
Test webhook handler with cURL.
curl -X POST http://localhost:3000/webhook \
-H "X-GitHub-Event: push" \
-H "X-Hub-Signature-256: sha256=YOUR_SIG" \
-H "Content-Type: application/json" \
-d '{"ref":"refs/heads/main"}'
Generate valid signature:
echo -n '{"ref":"refs/heads/main"}' | \
openssl dgst -sha256 -hmac "your_secret"
Testing - Payload Examples
Save test payloads for different events.
push-event.json:
{
"ref": "refs/heads/main",
"repository": {
"name": "test-repo",
"full_name": "user/test-repo"
},
"pusher": {
"name": "testuser"
}
}
Test with file:
curl -X POST http://localhost:3000/webhook \
-H "X-GitHub-Event: push" \
-H "Content-Type: application/json" \
-d @push-event.json
Setup - GitHub Configuration
Configure webhook in GitHub repository.
Steps:
- Go to Settings → Webhooks → Add webhook
- Enter payload URL (HTTPS required in production)
- Set content type to
application/json - Generate and save secret token
- Select events to trigger
Payload URL examples:
- Development:
https://abc123.ngrok.io/webhook - Production:
https://api.example.com/webhook
Setup - Secret Generation
Generate a secure webhook secret.
# Linux/Mac
openssl rand -hex 32
# Or use Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Or use Python
python3 -c "import secrets; print(secrets.token_hex(32))"
Setup Checklist
Complete setup checklist for production webhooks.
GitHub Settings:
- Go to Settings → Webhooks → Add webhook
- Enter payload URL (HTTPS required in production)
- Set content type to
application/json - Generate and save secret token
- Select events to trigger
Code:
- Implement signature verification
- Add event type handling
- Configure deployment script path
- Set up error logging
Security:
- Use HTTPS in production
- Store secret in environment variable
- Add rate limiting
- Set proper file permissions on deploy script
- Add IP allowlist (optional)
Production Tips
Best practices for production webhook deployment.
Run as Service:
# systemd (Linux)
sudo systemctl enable your-webhook.service
# PM2 (Node.js)
pm2 start app.js --name webhook
pm2 startup
pm2 save
Logging:
# Redirect to log file
exec >> /var/log/webhook.log 2>&1
Monitor:
- Check GitHub webhook delivery tab for failures
- Set up alerts for 4xx/5xx responses
- Log all webhook payloads for debugging
GitHub IP Ranges
Optionally restrict webhooks to GitHub IPs.
Get current GitHub IP ranges:
curl https://api.github.com/meta | jq .hooks
Nginx allowlist:
location /webhook {
allow 192.30.252.0/22;
allow 185.199.108.0/22;
allow 140.82.112.0/20;
deny all;
proxy_pass http://localhost:3000;
}
Troubleshooting - Failed Deliveries
Debug failed webhook deliveries.
Check GitHub:
- Go to Settings → Webhooks
- Click on webhook URL
- View “Recent Deliveries”
- Check response code and body
Common Issues:
- Server not reachable (DNS, firewall)
- Invalid SSL certificate
- Signature verification failed
- Server returning 500 error
- Timeout (GitHub waits 10 seconds)
Troubleshooting - Signature Mismatch
Fix signature verification errors.
Checklist:
- Using correct secret
- Reading raw request body
- Using SHA-256 (not SHA-1)
- Comparing with
X-Hub-Signature-256header - Using constant-time comparison
- Not double-parsing JSON
Debug tip:
# Log received signature and computed hash
echo "Received: $signature"
echo "Expected: $computed_hash"
Webhook Retries
GitHub automatically retries failed webhooks.
Retry behavior:
- GitHub retries up to 3 times
- Exponential backoff between retries
- Stops after 3 consecutive failures
Best practices:
- Return 2xx status code quickly
- Process heavy work asynchronously
- Implement idempotency (handle duplicate events)
Rate Limits
Webhook rate limits and quotas.
GitHub limits:
- Max 1000 webhook deliveries per hour per repository
- 10 second timeout per request
- 5 MB max payload size
Your server:
- Implement rate limiting on webhook endpoint
- Queue webhooks for processing
- Don’t block on long-running tasks