Skip to main content
Webhooks let external services push events to you the moment something happens — a GitHub star, a Slack message, a PR merged. You expose one endpoint. Corsair verifies the signature, identifies the plugin, and calls your handler. Three steps: ngrok → register → react.

Step 1 — Expose a public URL

GitHub and Slack can’t reach localhost. Use ngrok to tunnel your local server:
# Install ngrok — https://ngrok.com/download
ngrok http 3000
You’ll get a URL like https://abc123.ngrok-free.app. Copy it.
Get a stable URL (recommended) Free ngrok accounts get a random URL on every restart — meaning you’d have to re-register your webhook every time. Claim a free static domain at dashboard.ngrok.com/domains and it’ll never change.

Step 2 — Create the webhook endpoint

Add one route to your server. All plugins share this single URL:
app/api/webhook/route.ts
import { processWebhook } from 'corsair';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { corsair } from '@/server/corsair';

export async function POST(request: NextRequest) {
    const headers: Record<string, string> = {};
    request.headers.forEach((value, key) => { headers[key] = value; });

    const body = request.headers.get('content-type')?.includes('application/json')
        ? await request.json()
        : await request.text();

    // Include tenantId if you're using multi-tenancy
    const tenantId = new URL(request.url).searchParams.get('tenantId') ?? undefined;

    const result = await processWebhook(corsair, headers, body, { tenantId });

    if (!result.response) {
        return NextResponse.json({ success: false }, { status: 404 });
    }

    return NextResponse.json(result.response);
}
processWebhook handles everything: it identifies which plugin sent the event, verifies the signature, updates your local database, and runs your hooks.

Step 3 — Register and react

Register in GitHub:
  1. Go to your repo → Settings → Webhooks → Add webhook
  2. Set Payload URL: https://your-ngrok-url.ngrok-free.app/api/webhook
  3. Set Content type: application/json
  4. Add a Secret — save it
  5. Choose events (or “Send me everything”)
  6. Click Add webhook
Store the secret:
await corsair.github.keys.set_webhook_signature(process.env.GITHUB_WEBHOOK_SECRET!);
React to events:
corsair.ts
github({
    webhookHooks: {
        starCreated: {
            after: async (ctx, result) => {
                console.log(`⭐ ${result?.data?.sender?.login} starred ${result?.data?.repository?.full_name}`);
            },
        },
        pullRequestOpened: {
            after: async (ctx, result) => {
                const pr = result?.data?.pull_request;
                console.log(`PR opened: "${pr?.title}" by ${pr?.user?.login}`);
            },
        },
        push: {
            after: async (ctx, result) => {
                const branch = result?.data?.ref.replace('refs/heads/', '');
                console.log(`${result?.data?.commits?.length} commit(s) pushed to ${branch}`);
            },
        },
    },
})

How it works

Incoming webhook
    → Corsair reads headers to identify the plugin
    → Verifies signature against stored secret
    → Updates local database
    → Runs your webhookHooks.after() handler
Every plugin shares the same endpoint. You never write routing logic.

What’s next

Workflows

Chain webhook events into multi-step automations.

Multi-Tenancy

Scope incoming webhooks per user with ?tenantId= param.

GitHub Webhooks

All available GitHub events and payload shapes.

Slack Webhooks

All available Slack events and payload shapes.