How to Test Discord Webhooks Locally
What Webhooks Does Discord Send?
Discord uses an interactions endpoint model for bots and applications. When a user invokes a slash command, clicks a button, selects from a dropdown menu, or submits a modal, Discord sends an HTTP POST to your configured interactions endpoint URL. These payloads include the interaction type, the command or component data, the user and guild information, and a token for responding.
The interaction types are: PING (type 1, used for endpoint verification), APPLICATION_COMMAND (type 2, slash commands), MESSAGE_COMPONENT (type 3, buttons and select menus), APPLICATION_COMMAND_AUTOCOMPLETE (type 4), and MODAL_SUBMIT (type 5).
Discord requires your interactions endpoint to verify requests using Ed25519 signatures. The X-Signature-Ed25519 and X-Signature-Timestamp headers must be validated against your application's public key. Discord sends a PING interaction during setup to verify your endpoint handles this correctly.
Setting Up Discord with ReqPour
In the Discord Developer Portal, go to your application and navigate to the "General Information" page. Set the Interactions Endpoint URL to your ReqPour endpoint, such as https://abc123.reqpour.com/discord.
When you save, Discord immediately sends a PING interaction to verify your endpoint. Since ReqPour responds with a 200 status by default, this verification will succeed and you can see the PING payload in the dashboard.
However, for Discord to fully accept your endpoint, your actual handler needs to respond with the correct JSON ({ "type": 1 }) for PING interactions. Use the ReqPour relay to forward to your local server during development:
npx reqpour relay --to http://localhost:3000/api/discordWith the relay running, Discord's verification ping will reach your local server, which can respond correctly. ReqPour forwards the response back to Discord, completing the handshake.
Inspecting Discord Interactions
Discord interaction payloads contain rich data about the context. For a slash command, you get the command name, options with their values, the user who invoked it, the guild and channel IDs, and a unique interaction token. The ReqPour dashboard displays all of this in a structured JSON view.
When debugging, pay attention to the data.options array — this is where command arguments live. For nested subcommands, options can be nested multiple levels deep. The dashboard's collapsible JSON viewer makes navigating this structure much easier than reading raw logs.
For message component interactions (buttons, select menus), the payload includes a data.custom_id field that matches the ID you assigned when creating the component, plus a message object containing the original message the component was attached to.
Handling Discord Interactions in Code
Here is a handler for Discord interactions using the discord-interactions library:
const { verifyKey, InteractionType, InteractionResponseType }
= require('discord-interactions');app.post('/api/discord', express.json(), (req, res) => { const signature = req.headers['x-signature-ed25519']; const timestamp = req.headers['x-signature-timestamp']; const isValid = verifyKey( JSON.stringify(req.body), signature, timestamp, process.env.DISCORD_PUBLIC_KEY );
if (!isValid) { return res.status(401).send('Invalid signature'); }
const { type, data } = req.body;
if (type === InteractionType.PING) { return res.json({ type: InteractionResponseType.PONG }); }
if (type === InteractionType.APPLICATION_COMMAND) { if (data.name === 'hello') { return res.json({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { content: 'Hello from my bot!' }, }); } }
res.status(400).send('Unknown interaction'); }); ```
Discord expects a response within 3 seconds. For longer operations, respond with a deferred message and follow up with a PATCH request using the interaction token.
Tips for Discord Webhook Development
The 3-second response deadline is strict. If your handler needs to do database queries or external API calls, use a deferred response (type 5) immediately, then send the actual response asynchronously via the webhook URL Discord provides in the interaction token.
During development, ReqPour's replay feature is extremely useful for Discord bots. Instead of typing slash commands in Discord repeatedly, replay the interaction request from the dashboard. This speeds up iteration significantly, especially for complex multi-step interactions.
When testing modals, the MODAL_SUBMIT interaction includes a data.components array with the user's input values. Each component has a custom_id and value. Use ReqPour to capture a real modal submission once, then replay it while refining your handler logic.
Related
Get started with ReqPour
Catch, inspect, and relay webhooks to localhost. Free to start, $3/mo for Pro.