Provider guide

Discord webhooks, kept properly.

Discord is the odd one out: it signs with a key pair rather than a shared secret, and it checks that you are checking. Get the verification wrong and it refuses to accept your endpoint at all. The vault keeps every interaction it receives and rules on its signature, but it does not answer Discord's registration checks for you.

Field notes

How Discord behaves, and where it bites

Ed25519 rather than HMAC, a PING before anything else, and a validator that tests your rejections.

How Discord sends

Discord POSTs JSON with the interaction in type, signs it with Ed25519 in X-Signature-Ed25519, and sends the timestamp in X-Signature-Timestamp. A new endpoint gets a PING first.

Where it bites

  • It verifies that you verify. During registration Discord sends deliberately invalid signatures and expects them refused. An endpoint that accepts everything is rejected. A capture URL answers every request with the response you configured, so Discord will not save one as its Interactions Endpoint URL.
  • Ed25519 is not HMAC. You hold a public key, not a shared secret, and most webhook tooling assumes the latter.
  • Three seconds to acknowledge. The interaction model expects an immediate response, with the real work done afterwards.

The vault under it

Point a WebhookVault endpoint at Discord and every delivery is stored in full: headers verbatim, bodies byte-for-byte, labelled with its interaction type and searchable. Forward it onward to your own handler if you want the relay pattern, replay any delivery once the bug is fixed, and let a Watchdog notice the day it stops arriving.

01

Create an endpoint

Name it discord-interactions. Turn on forwarding to your own handler if you want the vault in front of it rather than beside it.

02

Register the capture URL

Discord Developer Portal → your application → Interactions Endpoint URL. Discord only saves a URL that answers its PING with a PONG and refuses an invalid signature; a capture URL can be set to answer the PONG but can never refuse an invalid signature (it always sends the response you configured), so register your own handler there. To keep and check what Discord sends, have the handler pass each interaction on to the capture URL unchanged, body and the two signature headers included.

03

Keep everything

Every delivery from here on is on the shelf: searchable, replayable, and watched for the day it goes quiet.

Verify from the record, not just at the door

Discord signs the timestamp followed immediately by the body, with no separator between them, and you verify with the application's public key. Paste that public key into the endpoint and the vault does exactly that on every capture: the row shows verified, or failed when the signature does not match. A failed signature is flagged and filterable, not refused: the request is still stored and the endpoint still sends its configured response. A replay re-sends a stored request and is not captured again, so it gets no new verdict.

The vault stores the signature headers verbatim with every request, so a delivery from last month can still be checked today. Verdicts are part of Solo and up. How verification works →

captured · interaction✓ verified
# headers, stored verbatim
"X-Signature-Ed25519": "3d4f21ac…"
"X-Signature-Timestamp": "1757030400"

{
  "type": 2,  # 1 = PING, 2 = command
  "application_id": "890144…",
  "data": {
    "name": "status",
    "options": [ { "value": "vault" } ]
  }
}

Event catalogue

Discord webhook events you'll see

Discord does not name its interactions: the numeric type in the body tells them apart. These are the five types, and each capture is labelled with its type.

EventWhat it tells you
PING (type 1)Sent when you save the Interactions Endpoint URL; your own handler must answer with a PONG.
APPLICATION_COMMAND (type 2)A user ran one of your slash or context-menu commands.
MESSAGE_COMPONENT (type 3)A user pressed a button or chose from a select menu on one of your messages.
APPLICATION_COMMAND_AUTOCOMPLETE (type 4)A user is typing a command option that offers suggestions.
MODAL_SUBMIT (type 5)A user submitted a modal form.

Questions

Discord webhook questions, answered

The three that come up most when Discord is wired to a new endpoint.

Why won't Discord save my Interactions Endpoint URL?

Discord sends a PING and expects a PONG, then sends requests with deliberately invalid signatures and expects each one refused with a 401. A WebhookVault capture URL always sends the response you configured, so it cannot pass that second check. Register your own handler as the endpoint URL and have it pass each interaction on to the capture URL unchanged.

How do I verify X-Signature-Ed25519?

Concatenate the X-Signature-Timestamp header and the raw body with nothing between them, and verify that message against the hex signature using your application's public key. Refuse a bad signature with a 401. Paste the public key into the endpoint and the vault does the same check on every capture, marking each row verified or failed (Solo and up).

How long do I have to answer an interaction?

Three seconds for the first response. If the work takes longer, answer with a deferred response inside that window and send the real reply afterwards; the interaction token stays valid for 15 minutes for follow-ups.

See what Discord actually sends.

Free tier keeps 3 days while you evaluate; Ed25519 verdicts start on Solo, from $5.