Overview
The GitHub webhook endpoint receives events from GitHub when actions occur on your repositories or GitHub App installation. Diffy processes these events to track pull requests and installations.
Endpoint
Authentication
This endpoint uses webhook signature verification for security. All requests must include a valid x-hub-signature-256 header.
Webhook requests are authenticated using HMAC-SHA256 signatures. GitHub signs each webhook payload with your webhook secret, and Diffy verifies the signature before processing events.
Request
HMAC-SHA256 signature of the request body, used to verify the webhook came from GitHubFormat: sha256=<signature>
The type of event being sent. Supported values:
installation - GitHub App installation events
pull_request - Pull request events
Unique identifier for the webhook delivery. Used for idempotency and tracking.
Request Body
The request body structure varies depending on the event type. All webhook payloads include an installation object with an id field.
Security
Signature Verification
Diffy verifies webhook signatures using the following process:
- Extracts the raw request body
- Compares the HMAC-SHA256 hash with the
x-hub-signature-256 header
- Rejects requests with invalid signatures
Source: /home/daytona/workspace/source/src/github/github.controller.ts:52
const isValid = await this.githubService.validateWebhook(
rawBody,
signature,
);
if (!isValid) {
console.log('Invalid webhook');
return { status: 'ERROR' };
}
Never disable signature verification. Invalid signatures return {"status": "ERROR"} and are not processed.
Supported Events
Installation Event
Received when the GitHub App is installed, uninstalled, or modified.
Event Payload Example
{
"action": "created",
"installation": {
"id": 12345678
},
"sender": {
"id": 987654,
"login": "octocat"
},
"repositories": [
{
"id": 1234567,
"name": "my-repo",
"full_name": "octocat/my-repo"
}
]
}
Processing
The action performed. Values:
created - App was installed (processed)
deleted - App was uninstalled (ignored)
suspend - App access was suspended (ignored)
unsuspend - App access was restored (ignored)
The installation ID, linked to the user’s account
GitHub user ID of the person who performed the installation
When action is "created", Diffy:
- Looks up the user by their GitHub ID
- Updates the user record with the installation ID
- Enables webhook processing for that user’s repositories
Source: /home/daytona/workspace/source/src/github/github.controller.ts:65
if (event === 'installation') {
const body = req.body as EmitterWebhookEvent<'installation'>['payload'];
await this.githubService.handleInstallation(
installationId,
body.sender.id,
body.action,
);
}
Pull Request Event
Received when a pull request is opened, closed, edited, or synchronized.
Event Payload Example
{
"action": "opened",
"number": 42,
"pull_request": {
"id": 1234567890,
"number": 42,
"title": "Add new feature",
"body": "This PR adds a new feature to improve performance",
"state": "open",
"html_url": "https://github.com/octocat/my-repo/pull/42",
"diff_url": "https://github.com/octocat/my-repo/pull/42.diff",
"user": {
"id": 987654,
"login": "octocat"
},
"created_at": "2026-03-03T10:00:00Z",
"updated_at": "2026-03-03T10:00:00Z",
"commits": 3,
"additions": 120,
"deletions": 45,
"changed_files": 8
},
"repository": {
"id": 1234567,
"name": "my-repo",
"full_name": "octocat/my-repo"
},
"installation": {
"id": 12345678
},
"sender": {
"id": 987654,
"login": "octocat"
}
}
Processing
The action performed on the pull request:
opened - PR was created (processed)
closed - PR was closed or merged (ignored)
synchronize - New commits pushed (ignored)
edited - PR title/body edited (ignored)
reopened - PR reopened (ignored)
Complete pull request data including metadata, file changes, and author information
Repository where the pull request was created
Installation ID to authenticate API requests
Pull request events are queued for asynchronous processing using BullMQ:
Source: /home/daytona/workspace/source/src/github/github.controller.ts:72
else if (event === 'pull_request') {
const body = req.body as EmitterWebhookEvent<'pull_request'>['payload'];
await this.webhookQueue.add('handle-pull-request', {
installationId,
payload: body,
deliveryId,
});
}
The queue processor:
- Checks if the PR was already processed (using
deliveryId for idempotency)
- Creates a database record with PR metadata
- Triggers analysis workflows
Response
Success Response
"OK" when the webhook was processed successfully
Error Response
"ERROR" when signature verification fails
Status Codes
| Code | Description |
|---|
| 200 | Webhook received and processed successfully |
| 400 | Missing raw body or invalid request format |
Testing Webhooks
Using GitHub’s Webhook Delivery UI
- Go to your GitHub App settings
- Navigate to “Advanced” tab
- Find recent webhook deliveries
- Click “Redeliver” to resend an event
Local Development
Use a tool like ngrok or smee.io to forward GitHub webhooks to your local development environment:
# Using ngrok
ngrok http 3000
# Update your GitHub App webhook URL to:
# https://your-ngrok-url.ngrok.io/github/webhook
Error Handling
If webhook processing fails, check the GitHub webhook delivery logs for details. Failed webhooks are not automatically retried.
Common issues:
- Invalid signature: Check that
GITHUB_APP_WEBHOOK_SECRET matches your GitHub App configuration
- Missing raw body: Ensure your server middleware preserves the raw request body for signature verification
- User not found: The GitHub user must be registered in Diffy before installing the app
Implementation Reference
Key files:
- Controller:
/home/daytona/workspace/source/src/github/github.controller.ts:42
- Service:
/home/daytona/workspace/source/src/github/github.service.ts:115
- Queue processor:
/home/daytona/workspace/source/src/github/processors/pull-request.processor.ts
See Also