Webhook Events
Webhook events allow your app to receive notifications from Discord about events happening in your application, such as entitlement purchases, lobby messages, or app authorizations. Unlike Gateway events, webhook events are sent over HTTP and don’t require maintaining a persistent connection.
What are Webhook Events?
Section titled “What are Webhook Events?”Webhook events are one-way HTTP notifications sent from Discord to your app when specific events occur. They differ from interactions in several key ways:
| Feature | Interactions | Webhook Events |
|---|---|---|
| Direction | User → Your App | Discord → Your App |
| Purpose | Handle user actions | Receive event notifications |
| Response Time | 3 seconds | 3 seconds (acknowledgment) |
| Examples | Slash commands, buttons | Application authorized, Entitlement created |
Setting Up Webhook Events
Section titled “Setting Up Webhook Events”-
Create a WebhookEventHandler
Choose the event type you want to handle from
ApplicationWebhookEventType:import { WebhookEventHandler } from "honocord";import { ApplicationWebhookEventType } from "discord-api-types/v10";const entitlementHandler = new WebhookEventHandler(ApplicationWebhookEventType.EntitlementCreate);entitlementHandler.addHandler(async (c) => {const entitlement = c.var.data;console.log(`New entitlement: ${entitlement.id}`);// Process the entitlement...return c.body(null, 200); // empty response to acknowledge receipt}); -
Load the handler into Honocord
import { Honocord } from "honocord";const bot = new Honocord();bot.loadHandlers(entitlementHandler);export default bot.getApp(); // Webhook available at /webhook -
Configure Discord Developer Portal
- Navigate to your app’s settings
- Go to the Webhooks page
- Enter your public URL:
https://your-domain.com/webhook - Enable Events and select the events you want to receive
- Click Save Changes
Discord will verify your endpoint by sending a PING event.
Standard vs Worker Mode
Section titled “Standard vs Worker Mode”Honocord supports two modes for webhook handlers, optimized for different deployment environments.
Standard Mode (Default)
Section titled “Standard Mode (Default)”In standard mode, your handler must return a Response object. This gives you full control over the response sent to Discord.
const handler = new WebhookEventHandler( ApplicationWebhookEventType.LobbyMessageCreate // Standard mode (default));
handler.addHandler(async (c) => { const message = c.var.data;
// Validate or process... if (!message.content) { return c.json({ error: "No content" }, 400); }
// Must return Response return c.json({ ok: true });});Use standard mode when:
- Running on traditional servers (Node.js, Bun)
- You need custom response codes or bodies
- You want full control over the response
Worker Mode (Cloudflare Workers)
Section titled “Worker Mode (Cloudflare Workers)”In worker mode, you don’t need to return anything. Honocord automatically responds with 200 OK to Discord and processes your handler asynchronously using waitUntil.
const handler = new WebhookEventHandler( ApplicationWebhookEventType.EntitlementCreate, true // Enable worker mode);
handler.addHandler(async (c) => { const entitlement = c.var.data;
// Process asynchronously - no return needed await logToDatabase(entitlement); await notifyUser(entitlement.user_id);
// No return statement required!});Use worker mode when:
- Deploying to Cloudflare Workers
- Processing takes longer than 3 seconds
- You want fire-and-forget processing
- You don’t need custom response codes
Available Event Types
Section titled “Available Event Types”Discord supports various webhook event types. Here are the most common:
Monetization Events
Section titled “Monetization Events”Monitor entitlement lifecycle for in-app purchases:
const entitlementCreate = new WebhookEventHandler(ApplicationWebhookEventType.EntitlementCreate);
const entitlementUpdate = new WebhookEventHandler(ApplicationWebhookEventType.EntitlementUpdate);
const entitlementDelete = new WebhookEventHandler(ApplicationWebhookEventType.EntitlementDelete);Authorization Events
Section titled “Authorization Events”Track when users add or remove your app:
const authHandler = new WebhookEventHandler(ApplicationWebhookEventType.ApplicationAuthorized);
authHandler.addHandler(async (c) => { const auth = c.var.data; console.log(`App authorized by ${auth.user.username}`);
if (auth.guild) { console.log(`Added to guild: ${auth.guild.name}`); }
return c.json({ ok: true });});
const deauthHandler = new WebhookEventHandler(ApplicationWebhookEventType.ApplicationDeauthorized);With custom types:
interface MyEnv { DISCORD_PUBLIC_KEY: string; DATABASE: D1Database;}
interface MyVariables { userId: string;}
// Specify types explicitly - T is inferred from constructor, then Env and Variablesconst authHandler = new WebhookEventHandler<ApplicationWebhookEventType.ApplicationAuthorized, MyEnv, MyVariables>( ApplicationWebhookEventType.ApplicationAuthorized);
authHandler.addHandler(async (c) => { // c.env is now typed as MyEnv const db = c.env.DATABASE; // c.var includes both MyVariables and data const auth = c.var.data; return c.json({ ok: true });});Lobby & Direct Message Events
Section titled “Lobby & Direct Message Events”For apps using the Discord Social SDK:
// Lobby messagesconst lobbyCreate = new WebhookEventHandler(ApplicationWebhookEventType.LobbyMessageCreate);
const lobbyUpdate = new WebhookEventHandler(ApplicationWebhookEventType.LobbyMessageUpdate);
const lobbyDelete = new WebhookEventHandler(ApplicationWebhookEventType.LobbyMessageDelete);
// Game direct messagesconst dmCreate = new WebhookEventHandler(ApplicationWebhookEventType.GameDirectMessageCreate);Complete List
Section titled “Complete List”All available webhook event types:
APPLICATION_AUTHORIZEDAPPLICATION_DEAUTHORIZEDENTITLEMENT_CREATEENTITLEMENT_UPDATEENTITLEMENT_DELETEQUEST_USER_ENROLLMENT(currently unavailable)LOBBY_MESSAGE_CREATELOBBY_MESSAGE_UPDATELOBBY_MESSAGE_DELETEGAME_DIRECT_MESSAGE_CREATEGAME_DIRECT_MESSAGE_UPDATEGAME_DIRECT_MESSAGE_DELETE
Accessing Event Data
Section titled “Accessing Event Data”Event data is available via c.var.data / c.get("data") in your handler:
handler.addHandler(async (c) => { const eventData = c.var.data; // c.get("data") also works
// Type is automatically inferred based on event type // For EntitlementCreate, eventData is an Entitlement object console.log(eventData.id); console.log(eventData.sku_id); console.log(eventData.user_id);
return c.json({ ok: true });});The context object also provides:
c.env- Environment variables (includingDISCORD_PUBLIC_KEY)c.req- The original request objectc.set()/c.get()- Custom context variables
Security
Section titled “Security”Honocord automatically handles request verification for you:
- Signature Validation - Verifies
X-Signature-Ed25519header using yourDISCORD_PUBLIC_KEY - Timestamp Validation - Checks
X-Signature-Timestampto prevent replay attacks - PING Responses - Automatically responds to Discord’s PING verification
All you need to do is provide DISCORD_PUBLIC_KEY in your environment variables.
Standalone Usage
Section titled “Standalone Usage”You can use WebhookEventHandler independently of Honocord (standard mode only):
import { Hono } from "hono";import { WebhookEventHandler } from "honocord";
const handler = new WebhookEventHandler(ApplicationWebhookEventType.EntitlementCreate);
handler.addHandler(async (c) => { // Process event return c.json({ ok: true });});
// Option 1: As a fetch handlerexport default { fetch: handler.fetch.bind(handler),};
// Option 2: Mount in Hono appconst app = new Hono();app.route("/webhooks", handler.getApp());export default app;Typings
Section titled “Typings”The types for WebhookEventHandler are designed to provide strong type safety,
however it gets a littlemore complex when you start adding custom bindings and variables.
If you want to use custom Bindings and Variables, you need to specify the first 4 type parameters, even if you want to use the defaults for some of them. For example:
const handler = new WebhookEventHandler<ApplicationWebhookEventType.ApplicationAuthorized, MyEnv, MyVariables, false>( ApplicationWebhookEventType.ApplicationAuthorized);I’m working on improving the ergonomics of this, but this is a limitation of how TypeScript infers types from constructor arguments. If you have any suggestions on improving this, please let me know!
Troubleshooting
Section titled “Troubleshooting”Endpoint Not Verifying
Section titled “Endpoint Not Verifying”Ensure:
- Your URL is publicly accessible
DISCORD_PUBLIC_KEYis correctly set- Your app responds within 3 seconds
- You’re returning proper status codes (if using standard mode)
Events Not Being Received
Section titled “Events Not Being Received”Check:
- Events are enabled in Developer Portal
- Specific event types are selected
- Your endpoint hasn’t been disabled due to too many failures
- Request signatures are being validated correctly
Events being received multiple times in a short time
Section titled “Events being received multiple times in a short time”This is likely due to your handler not responding in time. If using standard mode, make sure to return a response within 3 seconds. If processing takes longer and you are on Workers, switch to worker mode to avoid timeouts and retries:
If using standard mode on CF Workers, switch to worker mode:
// Before (may timeout)const handler = new WebhookEventHandler(ApplicationWebhookEventType.EntitlementCreate);
// After (won't timeout)const handler = new WebhookEventHandler( ApplicationWebhookEventType.EntitlementCreate, true // Worker mode);