Docs /api/webhooks

GitHub Webhook Integration

This document covers how to configure and use the GitHub webhook integration in izri to automatically trigger repository analysis on push events.


Overview

When a GitHub push event is delivered to the webhook endpoint, izri:

  1. Validates the HMAC-SHA256 signature using the project's configured secret.
  2. Checks whether the push matches the configured trigger conditions (branch list, file patterns).
  3. Creates a webhook_events record for audit/retry tracking.
  4. If conditions match, creates a project_analyses record and queues repository analysis.

Webhook endpoint

POST /api/webhooks/github/:projectId
Parameter Description
projectId The izri project ULID (from the UI)

Required headers (sent automatically by GitHub)

Header Description
X-Hub-Signature-256 HMAC-SHA256 signature of the body
X-GitHub-Event Event type (only push is processed)
X-GitHub-Delivery Unique delivery UUID for idempotency
Content-Type application/json

Setting up a webhook in GitHub

  1. Navigate to your GitHub repository → SettingsWebhooksAdd webhook.
  2. Set Payload URL to:
    https://<your-api-host>/api/webhooks/github/<projectId>
    
  3. Set Content type to application/json.
  4. Enter the Secret generated by izri (see Configuring via tRPC below).
  5. Under Which events would you like to trigger this webhook?, choose Just the push event (other events are acknowledged but not acted on).
  6. Click Add webhook.

Configuring via tRPC

All webhook management is exposed through the webhooks tRPC router.

Create / update webhook config

const result = await trpc.webhooks.configure.mutate({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
  branches: ['main', 'develop'],   // empty = all branches
  filePatterns: ['src/**/*.ts'],   // empty = any file
})

// result.secret  ← copy this into your GitHub webhook settings (shown once only)
// result.configId

Get current config (secret excluded)

const config = await trpc.webhooks.getConfig.query({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
})
// { active, branches, filePatterns, configId, createdAt, updatedAt }

Enable / disable webhook

await trpc.webhooks.setActive.mutate({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
  active: false,
})

Rotate secret

If you suspect your secret has been compromised, rotate it. After rotating, update the secret in the GitHub webhook settings.

const { secret } = await trpc.webhooks.rotateSecret.mutate({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
})
// Update GitHub webhook settings with the new secret

List delivery events

const { events } = await trpc.webhooks.listEvents.query({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
  limit: 20,
  // optional: status: 'FAILED' | 'PROCESSED' | 'PENDING' | 'SKIPPED' | 'ABANDONED'
})

Delete webhook config

await trpc.webhooks.delete.mutate({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
})

Trigger conditions

Branch filter

Config Behaviour
branches: [] Trigger on all branches
branches: ['main', 'develop'] Trigger only on those branches

File pattern filter

Patterns use * (single-directory wildcard) and ** (cross-directory wildcard).

Pattern Matches
src/**/*.ts Any .ts file under src/
*.json Any .json file at the root
packages/api/** Any file under packages/api/
(empty array) Any changed file — always triggers

The filter is evaluated against the union of added, modified, and removed files across all commits in the push.


Security

  • Signatures are validated using HMAC-SHA256 (Web Crypto API; no native addon required).
  • Comparison uses a constant-time byte-by-byte XOR check to prevent timing attacks.
  • Deliveries without a valid X-Hub-Signature-256 header are rejected with HTTP 401.
  • Duplicate deliveries (same X-GitHub-Delivery) are idempotent — the second delivery is ignored.

Error handling and retry logic

Failed webhook events are retried up to 3 times with exponential backoff:

Attempt Delay after failure
1st 1 minute
2nd 5 minutes
3rd 15 minutes

After 3 failed attempts the event is marked ABANDONED.

The retry function retryFailedWebhookEvents() should be called by a periodic background job (e.g. every minute):

import { retryFailedWebhookEvents } from '@izri/trpc/services/webhookService'

// In your cron / background worker:
const { retried, abandoned } = await retryFailedWebhookEvents()

Event status reference

Status Description
PENDING Received but not yet processed
PROCESSED Successfully triggered repository analysis
SKIPPED Trigger conditions not met (branch / file filter)
FAILED Processing failed; will be retried
ABANDONED Max retries exceeded

Database tables

webhook_configs

Stores one configuration per project.

Column Type Description
id varchar(191) ULID primary key
project_id varchar(191) References projects.id
secret varchar(255) HMAC secret (stored hashed in future)
branches jsonb Branch allowlist (string[])
file_patterns jsonb Glob pattern allowlist (string[])
active boolean Whether deliveries are processed
created_at timestamp
updated_at timestamp

webhook_events

Audit log of every delivery received.

Column Type Description
id varchar(191) ULID primary key
project_id varchar(191) References projects.id
delivery_id varchar(255) GitHub's X-GitHub-Delivery UUID
event_type varchar(64) e.g. push
payload jsonb Full GitHub webhook payload
status varchar(32) See status reference above
error text Error message (if failed)
attempts integer Number of processing attempts
next_retry_at timestamp When to next retry (if FAILED)
processed_at timestamp When successfully processed
created_at timestamp

Reading this with an agent? /docs/api/webhooks.md serves the raw markdown.

All docs