GitHub Integration
Receive and route GitHub webhooks for repositories, organizations, and GitHub Apps.
Setup
1. Create a Source in WebhookRelay
bash
curl -X POST https://api.webhookrelay.com/api/organizations/{orgId}/sources \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub Production",
"slug": "github",
"verificationConfig": {
"type": "github",
"secret": "your-webhook-secret"
}
}'Save your webhook URL:
https://api.webhookrelay.com/ingest/{orgSlug}/github2. Configure GitHub Webhook
Repository Webhook
- Go to your repository on GitHub
- Navigate to Settings → Webhooks
- Click Add webhook
- Enter your WebhookRelay URL as the Payload URL
- Set Content type to
application/json - Enter your Secret (same as configured in the source)
- Select events or choose Send me everything
- Click Add webhook
Organization Webhook
- Go to your organization on GitHub
- Navigate to Settings → Webhooks
- Follow the same steps as repository webhook
GitHub App Webhook
- Go to your GitHub App settings
- Set the Webhook URL to your WebhookRelay URL
- Enter the Webhook secret
- Enable desired events
3. Create Destinations and Routes
bash
# Create a destination
curl -X POST https://api.webhookrelay.com/api/organizations/{orgId}/destinations \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"name": "My App", "url": "https://myapp.com/webhooks/github"}'
# Create a route
curl -X POST https://api.webhookrelay.com/api/organizations/{orgId}/routes \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"name": "GitHub to My App", "sourceId": "src_...", "destinationIds": ["dst_..."]}'Signature Verification
GitHub signs webhooks with HMAC-SHA256. The signature is sent in the X-Hub-Signature-256 header:
X-Hub-Signature-256: sha256=abc123...WebhookRelay automatically verifies this signature when you configure:
json
{
"verificationConfig": {
"type": "github",
"secret": "your-webhook-secret"
}
}Common Events
Push Events
Triggered when code is pushed to a branch.
json
{
"ref": "refs/heads/main",
"before": "abc123",
"after": "def456",
"repository": {
"id": 123456,
"full_name": "owner/repo",
"private": false
},
"pusher": {
"name": "username",
"email": "user@example.com"
},
"commits": [
{
"id": "def456",
"message": "Update README",
"author": {
"name": "User",
"email": "user@example.com"
}
}
]
}Pull Request Events
Triggered for PR actions (opened, closed, merged, etc.).
json
{
"action": "opened",
"number": 42,
"pull_request": {
"id": 789,
"title": "Add new feature",
"state": "open",
"head": {
"ref": "feature-branch",
"sha": "abc123"
},
"base": {
"ref": "main",
"sha": "def456"
},
"user": {
"login": "username"
}
},
"repository": {
"full_name": "owner/repo"
}
}Issue Events
Triggered for issue actions.
json
{
"action": "opened",
"issue": {
"id": 456,
"number": 10,
"title": "Bug report",
"state": "open",
"user": {
"login": "reporter"
},
"labels": [
{"name": "bug"}
]
},
"repository": {
"full_name": "owner/repo"
}
}Transform Examples
Slack Notification for Push
javascript
function transform(payload) {
const commits = payload.commits || [];
const commitList = commits
.slice(0, 5)
.map(c => `• ${c.message.split('\n')[0]}`)
.join('\n');
return {
blocks: [
{
type: "header",
text: {
type: "plain_text",
text: `Push to ${payload.repository.full_name}`
}
},
{
type: "section",
fields: [
{
type: "mrkdwn",
text: `*Branch:*\n${payload.ref.replace('refs/heads/', '')}`
},
{
type: "mrkdwn",
text: `*Pusher:*\n${payload.pusher.name}`
}
]
},
{
type: "section",
text: {
type: "mrkdwn",
text: `*Commits (${commits.length}):*\n${commitList}`
}
}
]
};
}Discord Notification for PR
javascript
function transform(payload) {
const pr = payload.pull_request;
const action = payload.action;
const colors = {
opened: 0x28a745,
closed: 0xd73a49,
merged: 0x6f42c1
};
return {
embeds: [{
title: `PR #${pr.number}: ${pr.title}`,
url: pr.html_url,
color: colors[action] || 0x586069,
fields: [
{ name: "Action", value: action, inline: true },
{ name: "Author", value: pr.user.login, inline: true },
{ name: "Branch", value: `${pr.head.ref} → ${pr.base.ref}`, inline: true }
],
timestamp: new Date().toISOString()
}]
};
}Filter Examples
Only Push to Main
json
{
"name": "Main Branch Pushes",
"logic": "and",
"conditions": [
{
"field": "ref",
"operator": "equals",
"value": "refs/heads/main"
}
]
}PR Opened or Merged
json
{
"name": "PR Opened or Merged",
"logic": "or",
"conditions": [
{
"field": "action",
"operator": "equals",
"value": "opened"
},
{
"field": "pull_request.merged",
"operator": "equals",
"value": "true"
}
]
}Exclude Bot Users
json
{
"name": "Non-Bot Events",
"logic": "and",
"conditions": [
{
"field": "sender.type",
"operator": "not_equals",
"value": "Bot"
}
]
}Headers
GitHub sends these headers with webhooks:
| Header | Description |
|---|---|
X-GitHub-Event | Event type (push, pull_request, etc.) |
X-GitHub-Delivery | Unique delivery ID |
X-Hub-Signature-256 | HMAC-SHA256 signature |
X-GitHub-Hook-ID | Webhook configuration ID |
X-GitHub-Hook-Installation-Target-ID | Installation target |
X-GitHub-Hook-Installation-Target-Type | Target type (repository, organization) |
Troubleshooting
Webhook Not Triggering
- Check GitHub's webhook delivery logs in repository settings
- Verify the payload URL is correct
- Ensure the webhook is active (not disabled)
Signature Verification Failed
- Verify the secret matches exactly (no extra spaces)
- Check if you're using the correct secret for the environment
- Ensure the payload isn't being modified by proxies
Missing Events
- Check which events are selected in GitHub webhook settings
- Verify filters aren't blocking expected events
- Check if the repository/org has required permissions
Best Practices
Use secrets: Always configure a webhook secret for security
Select specific events: Only subscribe to events you need
Use filters: Filter events to reduce unnecessary processing
Monitor deliveries: Check GitHub's webhook logs for failures
Separate by environment: Use different sources for prod/staging/dev