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/githubnpm install @restaq/githubyarn add @restaq/githubbun add @restaq/githubimport { 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
],
});- Go to your repository (or organization) → Settings → Webhooks → Add webhook.
- Set the Payload URL to
/api/webhook/githubon your deployed app, content type toapplication/json. - 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
| Option | Type | Default | Description |
|---|---|---|---|
| webhookSecret | string | process.env.GITHUB_WEBHOOK_SECRET | The 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:
- Extracts the
sha256=...signature from the header - Computes HMAC-SHA256 of the raw request body with your webhook secret
- 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.