Skip to main content
POST
/
github
/
webhook
GitHub Webhook Handler
curl --request POST \
  --url https://api.example.com/github/webhook \
  --header 'Content-Type: <content-type>' \
  --header 'x-github-delivery: <x-github-delivery>' \
  --header 'x-github-event: <x-github-event>' \
  --header 'x-hub-signature-256: <x-hub-signature-256>' \
  --data '
{
  "action": "<string>",
  "installation.id": 123,
  "sender.id": 123,
  "pull_request": {},
  "repository": {}
}
'
{
  "status": "<string>"
}

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

POST /github/webhook

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

Headers

x-hub-signature-256
string
required
HMAC-SHA256 signature of the request body, used to verify the webhook came from GitHubFormat: sha256=<signature>
x-github-event
string
required
The type of event being sent. Supported values:
  • installation - GitHub App installation events
  • pull_request - Pull request events
x-github-delivery
string
required
Unique identifier for the webhook delivery. Used for idempotency and tracking.
Content-Type
string
required
Must be application/json

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:
  1. Extracts the raw request body
  2. Compares the HMAC-SHA256 hash with the x-hub-signature-256 header
  3. 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

action
string
required
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)
installation.id
number
required
The installation ID, linked to the user’s account
sender.id
number
required
GitHub user ID of the person who performed the installation
When action is "created", Diffy:
  1. Looks up the user by their GitHub ID
  2. Updates the user record with the installation ID
  3. 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

action
string
required
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)
pull_request
object
required
Complete pull request data including metadata, file changes, and author information
repository
object
required
Repository where the pull request was created
installation.id
number
required
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:
  1. Checks if the PR was already processed (using deliveryId for idempotency)
  2. Creates a database record with PR metadata
  3. Triggers analysis workflows

Response

Success Response

{
  "status": "OK"
}
status
string
"OK" when the webhook was processed successfully

Error Response

{
  "status": "ERROR"
}
status
string
"ERROR" when signature verification fails

Status Codes

CodeDescription
200Webhook received and processed successfully
400Missing raw body or invalid request format

Testing Webhooks

Using GitHub’s Webhook Delivery UI

  1. Go to your GitHub App settings
  2. Navigate to “Advanced” tab
  3. Find recent webhook deliveries
  4. 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

Build docs developers (and LLMs) love