DocuGardenerDocs

Webhooks

DocuGardener receives GitHub webhook events at POST /webhooks/github. This page explains event handling, security, and how to test locally.

Handled Events

EventActionWhat happens
pull_requestopenedEnqueues a new PR analysis job. Quota checked first.
pull_requestsynchronizeNew commits pushed to an existing PR. Re-enqueues analysis.
pull_requestreopenedTreated the same as opened.
pull_requestclosed / mergedIgnored — no analysis runs on close.

All other event types (push, issues, etc.) are accepted with a 200 OK response and silently ignored. This is intentional — GitHub retries non-2xx responses, so always returning 200 prevents noise in the GitHub App delivery log.

Payload Structure

DocuGardener reads the following fields from the GitHub webhook payload:

{
  "action": "opened",
  "number": 42,
  "pull_request": {
    "number": 42,
    "title": "feat: rename API endpoint",
    "head": {
      "sha": "abc123",           // used for cloning + check run
      "ref": "feat/rename-api"
    },
    "base": {
      "ref": "main",
      "repo": {
        "full_name": "acme/backend",
        "private": false
      }
    }
  },
  "installation": {
    "id": 12345678               // maps to tenant via GitHub App installation
  },
  "repository": {
    "full_name": "acme/backend"
  }
}

Signature Verification

Every request from GitHub includes an X-Hub-Signature-256 header containing an HMAC-SHA256 digest of the raw request body, keyed with your GITHUB_WEBHOOK_SECRET. DocuGardener verifies this before processing. Requests with missing or invalid signatures return 403.

Never skip signature verification in production. Without it, anyone who knows your webhook URL can trigger arbitrary analysis jobs against your quota.

Rate Limiting

A per-installation token-bucket rate limiter allows 20 webhook requests per minute (burst: 20). Requests over the limit return HTTP 429 with a Retry-After header. GitHub will retry the delivery automatically.

GitHub Check Run Lifecycle

After receiving a webhook, DocuGardener immediately creates a check run in queued state. As the analysis progresses, the check run transitions:

  1. queued → job accepted, waiting for worker
  2. in_progress → worker started analysis
  3. completed / success — drift score below threshold
  4. completed / failure — drift score at or above threshold
  5. completed / neutral — analysis error or quota exceeded

The check run update always runs in a finally block, so it resolves even if the analysis fails mid-way.

Local Testing

GitHub cannot reach localhost. For local development, use a proxy to forward webhooks:

# Install smee client globally
npm install -g smee-client

# Create a new channel at https://smee.io/new then run:
smee --url https://smee.io/YOUR_CHANNEL --target http://localhost:8000/webhooks/github

Set your GitHub App's webhook URL to the smee.io channel URL. In production, set it directly to https://your-domain.com/webhooks/github.

You can replay past deliveries from the Recent Deliveries tab in your GitHub App settings without needing to open a real pull request.