Corsair
PluginsDiscord

Discord Webhooks

All available Discord webhook events

The Discord plugin handles incoming interaction events from Discord. Discord sends interaction payloads (slash commands, buttons, modals) to your webhook URL and verifies them using Ed25519 signatures.

New to Corsair? Learn about webhooks, hooks, and multi-tenancy.

Full Implementation: See the Discord plugin source code.

Setup

Set your Discord app's Interactions Endpoint URL in the Developer Portal to your Corsair webhook endpoint:

app/api/webhook/route.ts
import { processWebhook } from "corsair";
import { corsair } from "@/server/corsair";

export async function POST(request: Request) {
    const headers = Object.fromEntries(request.headers);
    const body = await request.json();

    const result = await processWebhook(corsair, headers, body);
    return result.response;
}

Configure your public key for webhook signature verification:

corsair.ts
discord({
    publicKey: process.env.DISCORD_PUBLIC_KEY, 
})

For multi-tenancy, include the tenant ID in the URL:

https://your-domain.com/api/webhook?tenantId=tenant-123

Available Webhooks

ping

interactions.ping

Event Type: Ping

Discord sends this to verify your endpoint is live. Corsair handles the response automatically.

When it fires: During initial endpoint configuration

Payload:

{
    type: 1,  // PING
}

Note: You don't need to handle this event manually — Corsair responds automatically.


applicationCommand

interactions.applicationCommand

Event Type: Application Command (slash command or context menu)

Fires when a user invokes a slash command or context-menu action.

When it fires:

  • User runs a /slash-command
  • User selects an app from the message or user context menu

Payload:

{
    type: 2,  // APPLICATION_COMMAND
    id: "interaction-id",
    application_id: "app-id",
    guild_id: "guild-id",
    channel_id: "channel-id",
    member: {
        user: { id: "user-id", username: "username" },
        roles: ["role-id"],
    },
    data: {
        id: "command-id",
        name: "command-name", 
        options: [{ name: "param", value: "value" }],
    },
}

Example Usage:

discord({
    webhookHooks: {
        interactions: {
            applicationCommand: {
                after: async (ctx, result) => {
                    await inngest.send({
                        name: "discord/slash-command",
                        data: {
                            tenantId: ctx.tenantId,
                            commandName: result.data.data?.name,
                            userId: result.data.member?.user?.id,
                        },
                    });
                },
            },
        },
    },
})

Key Fields:

FieldTypeDescription
data.namestringCommand name
data.optionsarrayCommand parameters
member.user.idstringUser who ran the command
guild_idstringServer where command was run
channel_idstringChannel where command was run

messageComponent

interactions.messageComponent

Event Type: Message Component (button click or select menu)

Fires when a user clicks a button or selects an option from a menu.

When it fires:

  • User clicks a button in a message
  • User selects an option from a select menu

Payload:

{
    type: 3,  // MESSAGE_COMPONENT
    id: "interaction-id",
    channel_id: "channel-id",
    member: { user: { id: "user-id" } },
    message: { id: "message-id" },
    data: {
        custom_id: "button-action", 
        component_type: 2,  // BUTTON
    },
}

Example Usage:

discord({
    webhookHooks: {
        interactions: {
            messageComponent: {
                after: async (ctx, result) => {
                    await inngest.send({
                        name: "discord/button-clicked",
                        data: {
                            tenantId: ctx.tenantId,
                            customId: result.data.data?.custom_id,
                            userId: result.data.member?.user?.id,
                            messageId: result.data.message?.id,
                        },
                    });
                },
            },
        },
    },
})

Key Fields:

FieldTypeDescription
data.custom_idstringCustom ID set when creating the component
data.component_typenumberComponent type (2=Button, 3=SelectMenu)
message.idstringID of the message containing the component

modalSubmit

interactions.modalSubmit

Event Type: Modal Submit

Fires when a user submits a modal dialog form.

When it fires:

  • User fills out and submits a modal

Payload:

{
    type: 5,  // MODAL_SUBMIT
    id: "interaction-id",
    channel_id: "channel-id",
    member: { user: { id: "user-id" } },
    data: {
        custom_id: "modal-id", 
        components: [{
            type: 1,
            components: [{
                type: 4,
                custom_id: "field-name",
                value: "User input", 
            }],
        }],
    },
}

Example Usage:

discord({
    webhookHooks: {
        interactions: {
            modalSubmit: {
                after: async (ctx, result) => {
                    const fields = result.data.data?.components
                        ?.flatMap(row => row.components ?? [])
                        ?? [];

                    await inngest.send({
                        name: "discord/modal-submitted",
                        data: {
                            tenantId: ctx.tenantId,
                            modalId: result.data.data?.custom_id,
                            fields: Object.fromEntries(
                                fields.map(f => [f.custom_id, f.value])
                            ),
                        },
                    });
                },
            },
        },
    },
})

Key Fields:

FieldTypeDescription
data.custom_idstringModal ID set when opening the modal
data.componentsarrayRows containing input fields