Restaq
Plugins

GitHub Plugin

Receive and verify GitHub webhooks with full type safety.

The @restaq/github plugin handles GitHub webhook verification and provides a fully-typed event catalog for all GitHub webhook events.

Installation

pnpm add @restaq/github
import { github } from '@restaq/github';

Setup

// relay.ts
import { restaq as createRestaq } from 'restaq';
import { github } from '@restaq/github';

export const restaq = createRestaq({
  plugins: [
    github(), // reads GITHUB_WEBHOOK_SECRET automatically
  ],
});
  1. Go to your repository (or organization) → Settings → Webhooks → Add webhook.
  2. Set the Payload URL to /api/webhook/github on your deployed app, content type to application/json.
  3. Enter a Secret and set the same value as GITHUB_WEBHOOK_SECRET. See Validating webhook deliveries for why a secret matters.

Pass webhookSecret explicitly instead if you'd rather not rely on the env var:

github({ webhookSecret: 'your-secret' }),

Configuration

OptionTypeDefaultDescription
webhookSecretstringprocess.env.GITHUB_WEBHOOK_SECRETThe secret you configured on your GitHub repository's webhook settings

Signature verification

The plugin verifies the x-hub-signature-256 header using HMAC-SHA256, per GitHub's own signature validation guide:

  1. Extracts the sha256=... signature from the header
  2. Computes HMAC-SHA256 of the raw request body with your webhook secret
  3. Compares signatures using constant-time comparison to prevent timing attacks

Event types

GitHub events are namespaced as github.<event-name> or github.<event-name>.<action> for action-qualified events:

  • Simple events (no action): github.push, github.release, etc.
  • Action-qualified events: github.pull_request.opened, github.issues.closed, etc.

Register handlers in relay.handlers.ts, not relay.ts — see Organizing handlers:

// relay.handlers.ts
import type { AppRelay } from './relay';

export function registerHandlers(relay: AppRelay): void {
  relay.on('github.push', async (event, ctx) => {
    const { ref, commits } = event.data;
    ctx.log.info('pushed to', { ref, count: commits.length });
  });

  relay.on('github.pull_request.opened', async (event, ctx) => {
    const { action, pull_request } = event.data;
    ctx.log.info('pr opened', { title: pull_request.title, action });
  });
}

The plugin automatically determines whether an event has an action by reading the x-github-event header and checking the payload's action field. See the full list of webhook events and payloads GitHub publishes.

Deduplication

Each GitHub webhook includes an x-github-delivery header (a UUID) that uniquely identifies the delivery. The plugin uses this as the event ID for dedup — if GitHub retries a delivery, it gets the same ID and won't trigger a duplicate execution.

Testing locally

Use relay trigger to simulate a signed GitHub webhook:

relay trigger github push --data '{"ref":"refs/heads/main","commits":[]}'
relay trigger github pull_request.opened --data '{"title":"New feature"}'

Make sure GITHUB_WEBHOOK_SECRET is set to the same value on both relay dev and relay trigger, or unset on both — see the Local Development gotcha for why.

To debug real deliveries, GitHub's webhook settings page shows a Recent Deliveries tab with the exact request/response for every attempt, including a Redeliver button — useful once you're testing against a real repository instead of relay trigger.

On this page