Beignet API reference
    Preparing search index...

    Module @beignet/webhooks-github

    @beignet/webhooks-github

    Runtime: Beignet requires Node.js 22.12 or newer. Bun is optional.

    GitHub webhook signature verification for Beignet applications.

    This package adapts GitHub's X-Hub-Signature-256 HMAC verification to @beignet/core/webhooks. Use it when an app receives repository, organization, or GitHub App webhook deliveries.

    bun add @beignet/webhooks-github @beignet/core
    

    Set the webhook secret for the GitHub webhook endpoint you are receiving:

    GITHUB_WEBHOOK_SECRET=...
    

    GitHub sends the event name in X-GitHub-Event, the delivery ID in X-GitHub-Delivery, and the HMAC-SHA256 signature in X-Hub-Signature-256.

    Verifier integrations are wired at the route/server boundary rather than registered in server/providers.ts. Validate GITHUB_WEBHOOK_SECRET in the app's environment schema and remove the dependency when the app does not expose a GitHub webhook endpoint.

    This package does not install Beignet lifecycle ports. It exports createGitHubWebhookVerifier(...), which you pass to createWebhookRoute(...) or verifyWebhook(...) at the route/server boundary.

    The verifier itself is the route-bound adapter.

    This verifier package does not record provider instrumentation directly. createWebhookRoute(...) owns the route response, and your handler should record app-specific audit entries, jobs, outbox messages, or custom devtools events after verification when those side effects matter.

    Keep the event catalog near the feature that owns the workflow:

    // features/integrations/webhooks.ts
    import { defineWebhook } from "@beignet/core/webhooks";
    import { z } from "zod";

    const issuePayloadSchema = z.object({
    action: z.string(),
    issue: z.object({
    number: z.number(),
    title: z.string(),
    }),
    repository: z.object({
    full_name: z.string(),
    }),
    });

    export const githubWebhook = defineWebhook("integrations.github", {
    provider: "github",
    events: {
    issues: issuePayloadSchema,
    },
    });

    The verified Beignet event has provider: "github", id from X-GitHub-Delivery, type from X-GitHub-Event, and the parsed JSON request body as event.payload.

    Wire the verifier at the route/server boundary so feature webhook catalogs stay runtime free. beignet lint treats verifier integrations as server runtime packages, so the verifier attaches through the verify option of createWebhookRoute(...) instead of the webhook definition:

    // app/api/webhooks/github/route.ts
    import { createWebhookRoute } from "@beignet/next";
    import { createGitHubWebhookVerifier } from "@beignet/webhooks-github";
    import { githubWebhook } from "@/features/integrations/webhooks";
    import { handleGitHubWebhookUseCase } from "@/features/integrations/use-cases";
    import { env } from "@/lib/env";
    import { getServer } from "@/server";

    export const runtime = "nodejs";

    const githubWebhookVerifier = createGitHubWebhookVerifier({
    secret: () => env.GITHUB_WEBHOOK_SECRET,
    });

    export const { POST } = createWebhookRoute({
    server: getServer,
    webhook: githubWebhook,
    verify: ({ input }) => githubWebhookVerifier.verify(input),
    handle: async ({ ctx, event }) => {
    await handleGitHubWebhookUseCase.run({
    ctx,
    input: event,
    });

    return {
    status: 200,
    body: { received: true },
    };
    },
    });

    Handlers should key idempotency on event.id. GitHub may redeliver the same delivery, and webhook handlers should update app-owned state or enqueue durable work before acknowledging.

    Generic webhook routes reject verified event types that are not listed in the catalog by default. Set allowUnknownEvents: true only when a broad GitHub endpoint should acknowledge valid event names the app does not handle.

    export const { POST } = createWebhookRoute({
    server: getServer,
    webhook: githubWebhook,
    verify: ({ input }) => githubWebhookVerifier.verify(input),
    allowUnknownEvents: true,
    handle: async ({ event }) => {
    if (event.type !== "issues") {
    return { status: 200, body: { ignored: true } };
    }

    return { status: 200, body: { received: true } };
    },
    });

    The defaults match GitHub's webhook delivery headers:

    createGitHubWebhookVerifier({
    secret: () => env.GITHUB_WEBHOOK_SECRET,
    signatureHeader: "x-hub-signature-256",
    eventHeader: "x-github-event",
    deliveryHeader: "x-github-delivery",
    });

    Most apps should not change these options.

    The verifier follows GitHub's documented validation flow:

    • compute an HMAC-SHA256 hex digest over the exact raw request body
    • compare it to X-Hub-Signature-256, including the sha256= prefix
    • use a timing-safe comparison
    • parse JSON only after the signature is valid

    Invalid signatures, missing headers, and invalid JSON throw Beignet WebhookVerificationError values that createWebhookRoute(...) maps to a 400 response.

    • Missing or invalid GitHub signatures return 400 through createWebhookRoute(...).
    • Handler failures return 500 so GitHub can redeliver the event.
    • Verified duplicate deliveries should be acknowledged after app idempotency confirms the original result.
    • Long-running work should be recorded through Beignet jobs, outbox delivery, listeners, or notifications instead of blocking the route.

    Use a fake verifier for use-case tests and test createWebhookRoute(...) with signed requests when verification behavior matters. Keep webhook catalogs near the feature that owns the workflow so handler tests can assert app-owned idempotency and side effects.

    Configure one secret per GitHub webhook endpoint and keep it out of client env. Unknown but valid GitHub event names fail by default. Set allowUnknownEvents: true only when the endpoint intentionally accepts a wider catalog than the app handles.

    CreateGitHubWebhookVerifierOptions
    GitHubWebhookEvent
    createGitHubWebhookVerifier