The loop
Create an endpoint and it prints a permanent ingest URL. Everything sent there is captured whole — headers, body, the exact bytes — before anything else happens. Stream those events to a port on your machine, and replay any one of them as often as you like while you fix the handler. Forwarding runs from your own machine, so it isn't a billable delivery.
What's different about Discord
Two unrelated things share the name. The webhook URL an outside service posts to in order to push into Discord is incoming — nothing from Discord ever arrives at your app on it. What you test here is the opposite direction: Webhook Events, which Discord sends to your app. And the URL will not register at all until your handler already clears both halves of Discord's verification gate — the PING acknowledgement and the two signature headers. Verification is the first thing you debug, not the last.
Where the URL goes
The Webhooks page in your app's settings in the Discord Developer Portal is where this lives — left-hand sidebar. Drop the ingest URL into the Endpoint field, flip the Events toggle on, select the events you want your app to receive, and click Save Changes. Nothing flows until that URL is successfully verified. There's an API path too, if you'd rather not click: the docs point you at Edit Current Application.
There's no test button, no resend, no replay control anywhere in the documented flow. The one delivery you can summon is the PING that lands when you add or save the Webhook Events URL — which doubles as your handshake test. Everything else needs the real action behind it: authorize your app somewhere to produce APPLICATION_AUTHORIZED, or drive the real state change behind an ENTITLEMENT_CREATE.
What Discord sends
Names are SCREAMING_SNAKE_CASE, but don't match on the top-level type field — that's a number, 0 for a PING and 1 for everything else. The name you actually care about sits one level down, in event.type, next to a timestamp and, when there is one, the payload.
- APPLICATION_AUTHORIZED
- APPLICATION_DEAUTHORIZED
- ENTITLEMENT_CREATE
- ENTITLEMENT_UPDATE
- LOBBY_MESSAGE_CREATE
- GAME_DIRECT_MESSAGE_CREATE
Worth knowing first
Three seconds, then backoff
Discord expects a 204 with an empty body within three seconds. Miss the window and it retries several times using exponential backoff for up to ten minutes. Fail too often and it stops sending webhook events altogether and tells you by email. Worth knowing before you put a breakpoint in the handler.
Discord probes you with bad signatures
Signature validation isn't advisory. Every request carries X-Signature-Ed25519 and X-Signature-Timestamp, you're expected to check both and answer 401 when they don't hold, and Discord runs routine automated checks that deliberately send invalid signatures. Fail one and it removes your Webhook Events URL, then notifies you by email and System DM.
Ordering is explicitly disclaimed
The very first line of the docs says webhook events, unlike Gateway events, arrive when they arrive and in whatever order they arrive. Reconstruct sequence from the timestamp on the event body rather than from arrival order, and avoid state machines that assume a CREATE lands before its matching UPDATE.
You may not need this at all
The docs put this plainly — if your app is using Gateway events, you don't need to configure a Webhook Events URL. Worth settling before you spend an afternoon on Ed25519 validation for an endpoint you were never going to need.
Signature checking is handled for you once the secret is registered — you can also check a captured signature by hand in the signature verifier.