Receiving webhooks
Verify webhook signatures and parse incoming events with the standalone helpers.
verifyWebhook and parseWebhookEvent are standalone exports on each provider subpath — no client
needed. They accept a Web-standard Request (or a { headers, body } pair), so they drop straight into
Next.js route handlers, Hono, and workers.
Import from the provider subpath
Both helpers live on each provider’s subpath — import the pair that matches the provider sending the webhook:
import { verifyWebhook, parseWebhookEvent } from 'repo-sdk/github';
// or 'repo-sdk/gitlab' | 'repo-sdk/bitbucket' | 'repo-sdk/azure-devops' | 'repo-sdk/gitea'
A full handler
The helpers accept a Web-standard Request, so one handler works across Next.js route handlers, Hono, and
Cloudflare Workers:
import { verifyWebhook, parseWebhookEvent } from 'repo-sdk/github';
export async function POST(request: Request): Promise<Response> {
const valid = await verifyWebhook({ request, secret: process.env.WEBHOOK_SECRET! });
if (!valid) return new Response('invalid signature', { status: 401 });
const event = await parseWebhookEvent(request);
switch (event.type) {
case 'push':
console.log(`push to ${event.repo} @ ${event.ref}`, event.commits);
break;
case 'tag_push':
console.log(`tag push to ${event.repo} @ ${event.ref}`);
break;
case 'release':
console.log(`release in ${event.repo}`);
break;
case 'ping':
break; // GitHub sends a ping when a hook is created
}
return new Response('ok');
}
One endpoint for multiple providers
When a single endpoint receives webhooks from more than one provider, detectWebhookProvider (a
standalone export from the core repo-sdk entry) identifies the sender so you can dispatch to the
right per-provider helpers:
import { detectWebhookProvider, type ProviderName } from 'repo-sdk';
import * as github from 'repo-sdk/github';
import * as gitlab from 'repo-sdk/gitlab';
type WebhookHelpers = Pick<typeof github, 'verifyWebhook' | 'parseWebhookEvent'>;
const handlers: Partial<Record<ProviderName, WebhookHelpers>> = { github, gitlab };
export async function POST(request: Request): Promise<Response> {
const provider = await detectWebhookProvider(request);
const helpers = provider === undefined ? undefined : handlers[provider];
if (helpers === undefined) return new Response('unknown sender', { status: 400 });
const { verifyWebhook, parseWebhookEvent } = helpers;
// ... verify and parse with the matched pair
}
It accepts a Request or { headers, body? } and returns the ProviderName or undefined. GitHub,
GitLab, Bitbucket, and Gitea are identified by their event headers (Gitea deliveries also carry a
compatibility x-github-event header — the helper handles that); Azure DevOps sends no identifying
header, so its JSON body is checked for the eventType field, which is why the helper is async.
Verification per provider
verifyWebhook handles each provider’s scheme transparently — you always call it the same way. It
returns false (never throws) on a missing or mismatched signature, and comparisons are constant-time.
| Provider | Method | How it verifies |
|---|---|---|
| GitHub | HMAC-SHA256 | x-hub-signature-256 over the raw body |
| GitLab | shared token | x-gitlab-token compared to the configured secret |
| Bitbucket | HMAC-SHA256 | x-hub-signature (no -256 suffix) over the raw body |
| Azure DevOps | basic auth | Authorization: Basic header (hook registered with the secret) |
| Gitea | HMAC-SHA256 | x-gitea-signature over the raw body (bare hex, no sha256= prefix) |
request?Request
A Web-standard Request. Provide this or headers + body.
Requestheaders?Record<string, string>
Header map, when you pass the body separately.
Record<string, string>body?string
The raw request body — required with headers.
stringsecret?string
The secret the hook was registered with.
stringThe parsed event
parseWebhookEvent normalizes the payload into a ParsedWebhookEvent, regardless of provider:
type?'push' | 'tag_push' | 'release' | 'ping' | 'unknown'
The normalized event type. Unrecognized events map to unknown.
'push' | 'tag_push' | 'release' | 'ping' | 'unknown'repo?string
The repository path, when present in the payload.
stringref?string
The git ref (e.g. refs/heads/main or refs/tags/v1.0.0).
stringcommits?{ sha: string; message?: string }[]
The commits in the delivery, when present.
{ sha: string; message?: string }[]headCommitSha?string
The SHA the ref points to after the push. Undefined when the push deleted the ref.
stringdeliveryId?string
The provider's delivery id, for de-duplication/logging.
stringwebhookId?string
The provider-assigned id of the webhook registration that produced the delivery.
stringraw?unknown
The untouched provider payload.
unknownReference: Provider support
The full feature-by-provider matrix, including webhooks.