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:
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:
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-123Available 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:
| Field | Type | Description |
|---|---|---|
data.name | string | Command name |
data.options | array | Command parameters |
member.user.id | string | User who ran the command |
guild_id | string | Server where command was run |
channel_id | string | Channel 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:
| Field | Type | Description |
|---|---|---|
data.custom_id | string | Custom ID set when creating the component |
data.component_type | number | Component type (2=Button, 3=SelectMenu) |
message.id | string | ID 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:
| Field | Type | Description |
|---|---|---|
data.custom_id | string | Modal ID set when opening the modal |
data.components | array | Rows containing input fields |