Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.corsair.dev/llms.txt

Use this file to discover all available pages before exploring further.

Corsair consolidates all incoming webhooks to a single URL. It identifies which integration and service each webhook belongs to, then processes and updates your data automatically.
webhook-handler.ts
import { processWebhook } from "corsair";
import { corsair } from "./corsair";

export async function handleWebhook(req: Request) {
	const url = new URL(req.url);

    const result = await processWebhook(
        corsair, // corsair instance
        Object.fromEntries(req.headers), // headers
        await req.json(), // body
        {
            tenantId: url.searchParams.get('tenantId') // tenant id
        }
    );

    if (result.plugin) {
        console.log(`Handled by ${result.plugin}.${result.action}`);
    }

    return result.response;
}
Note that webhooks are not guaranteed by most senders. If your project requires completely fresh data, add polling for that integration using an api call.

Automatic Routing

Point all your webhooks to a single endpoint. Corsair inspects the headers and payload to determine:
  1. Which integration the webhook is from (Slack, Linear, etc.)
  2. Which event type it represents (message, issue created, etc.)
  3. Which tenant it belongs to (in multi-tenant setups)

Handling Out-of-Order Webhooks

Webhooks don’t guarantee delivery order. You might receive an “updated” event before you’ve processed the “created” event. Most applications fail here — they don’t know about a record, so they can’t process its update. Corsair solves this automatically:
  1. Detects when an update arrives for an unknown record
  2. Fetches the latest data from the API
  3. Creates the record in your database
  4. Processes both the create and update
Your data stays fresh, and no webhooks are lost.

Multi-Tenancy with Webhooks

If you have multi-tenancy enabled, we recommend adding a hashed tenant ID as a query parameter to your webhook URL.
It is recommended you hash the tenant id in the query param so your internal IDs are not publicly known.
https://api.yourapp.com/webhooks?tenant=hashed_tenant_id
This lets Corsair identify which tenant incoming webhook data belongs to, ensuring proper isolation.

Signature Verification

Corsair automatically verifies webhook signatures using your stored webhook credentials. If a signature doesn’t match, the webhook is rejected — protecting you from spoofed requests.

Webhook Hooks

Hooks let you add custom logic that runs every time a webhook is processed. This guarantees your code executes — even when Corsair handles the database update automatically.
corsair.ts
slack({
    authType: "api_key",
    credentials: { botToken: "xoxb-..." },
    webhookHooks: {
        messages: {
            message: {
                before: async (ctx, payload) => {
                    console.log("Incoming message from:", payload.user);
                    return { ctx, payload };
                },
                after: async (ctx, result) => {
                    // This always runs after the webhook is processed
                    await analytics.track("message_received", {
                        channel: result.channel,
                    });
                },
            },
        },
    },
})

Before Hooks

Before hooks run before the webhook is processed. Use them to:
  • Validate the payload
  • Log incoming webhooks
  • Modify the payload before processing
  • Skip processing by throwing an error
Return { ctx, args }, where args is what the handler receives (usually the same request body, optionally changed).
corsair.ts
webhookHooks: {
    messages: {
        message: {
            before: async (ctx, request) => {
                if (yourAppShouldSkipThis(request)) {
                    return { ctx, args: request, continue: false };
                }
                return { ctx, args: request };
            },
        },
    },
}
Optional continue (defaults to true) Set continue: false to stop without running the handler. That is a silent skip, which will not throw.

After Hooks

After hooks run after the webhook is processed and the database is updated. Use them to:
  • Trigger side effects (notifications, syncs)
  • Update related records in your application
  • Send data to external services
  • Log processed webhooks
corsair.ts
webhookHooks: {
    channels: {
        created: {
            after: async (ctx, result) => {
                // Notify your team when a new channel is created
                await sendNotification({
                    title: "New Slack channel",
                    body: `#${result.name} was created`,
                });
            },
        },
    },
    reactions: {
        added: {
            after: async (ctx, result) => {
                // Track reactions for analytics
                await analytics.track("reaction_added", {
                    reaction: result.reaction,
                    channel: result.channel,
                });
            },
        },
    },
}

The passToAfter Argument

You can set passToAfter on the object returned from before. Corsair passes that value through as the third argument to after, unchanged. This is useful when you need to carry a value you only know at before-time — such as an ID you generate or a record you create — into the after hook, where you finalize or clean it up once processing is complete.
The after hook only runs when the webhook handler succeeds. If processing fails, after is skipped and passToAfter is never used.
corsair.ts
googleCalendar({
    webhookHooks: {
        onEventChanged: {
            before: async (ctx, request) => {
                const event = await db.events.create({
                    name: "Google Calendar Event",
                    status: "processing",
                })
                return { ctx, args: request, passToAfter: event.id };
            },
            after: async (ctx, result, passToAfter) => {
                const event = await db.events.update(passToAfter, {
                    name: "Google Calendar Event",
                    status: "successful",
                })
            },
        },
    },
})

Guaranteed Execution

The key benefit of webhook hooks is they always run when that webhook type is processed. Unlike manually handling webhooks where you might forget to add logging or notifications, hooks ensure your logic is centralized and guaranteed to execute.
corsair.ts
webhookHooks: {
    issues: {
        update: {
            after: async (ctx, result) => {
                // This ALWAYS runs when a Linear issue is updated
                // No matter where the webhook comes from
                await syncToYourDatabase(result);
                await notifyAssignee(result);
                await updateProjectMetrics(result);
            },
        },
    },
}
See Hooks for the full hooks documentation.