Skip to content
TryWebhook
New URL

Messaging · Free · No signup

Test and debug Discord webhooks and interactions

Capture Discord's PING validation and real webhook events on a throwaway URL, and see the Ed25519 signature headers that Discord checks you are checking before it will save your endpoint.

No signup · No email · Ready in about a second

At a glance

Discord webhooks in brief

Category
Messaging
Where to configure
Discord Developer Portal → your application → Webhooks → Endpoint URL
Signature header
X-Signature-Ed25519
Secret
Public key (General Information → Public Key)
Retries
A small number of attempts. Sustained failures disable the event subscription in the Developer Portal, and you re-enable it by hand.
Response deadline
3 seconds for interactions. Webhook events should be acknowledged immediately and processed afterwards.
Treated as success
204 with an empty body for webhook events. Interactions expect 200 with a JSON response object, and an invalid signature must be answered with 401.

Cross-checked against Discord's own webhook documentation. Providers change these; if something here is stale, tell us.

The payload

What Discord actually sends

Shape-accurate, with obviously fake identifiers. These are the same samples the inspector's Send test webhook panel fires, so you can read one here and then send the identical bytes to your own URL.

POST/w/{your-bin-id}308 B

A webhook event. Discord signs it with Ed25519, not an HMAC.

2 headers
x-signature-timestamp
1750000000
user-agent
Discord-Webhook/1.0
Body · application/json · 1 line
{"version":1,"application_id":"1000000000000000000","type":1,"event":{"type":"APPLICATION_AUTHORIZED","timestamp":"2026-06-15T10:04:00.000000+00:00","data":{"integration_type":0,"scopes":["applications.commands"],"user":{"id":"2000000000000000000","username":"ada","discriminator":"0","global_name":"Ada"}}}}

No sample carries a signature header. A valid one can only be produced with your own signing secret, and a fabricated one would make the Signature tab report a mismatch that isn't real. Send a genuine delivery when you want to test verification.

Verification

Verifying a Discord signature

Signed payload

An Ed25519 signature over the `X-Signature-Timestamp` header concatenated directly with the raw body, verified with your application's public key. Not an HMAC — there is no shared secret involved.

TryWebhook cannot verify this one. Every built-in verifier here is a keyed hash, and Discord uses Ed25519 public-key signatures — a different primitive, needing the curve rather than an HMAC. Use `discord-interactions`, `tweetnacl`, or your language's Ed25519 verify. Everything else on this page works: capture the delivery, read the payload, confirm both headers arrived.

Events

Events worth subscribing to first

Not the full catalogue — the handful that carry most integrations. Subscribing to everything is the fastest way to a handler that times out.

EventFires when
PINGSent when you save an endpoint URL, and occasionally afterwards. `type: 0` for webhook events, `type: 1` for interactions. Discord will not save the URL until this is answered correctly.
APPLICATION_AUTHORIZEDA user or guild installed your app. The payload carries `integration_type`, the granted `scopes`, and the user.
ENTITLEMENT_CREATEA user bought or was granted a premium SKU. The event monetised apps provision access from.
Interaction: APPLICATION_COMMANDA slash command was used. Arrives at the Interactions Endpoint URL, not the webhook events URL, and needs a reply within three seconds.
Interaction: MESSAGE_COMPONENTA button was pressed or a select menu used. Same endpoint and same deadline as a command.

Capturing the PING

Create a URL above, then open the Discord Developer Portal → your application → Webhooks, paste the URL into Endpoint URL, and press save. Discord immediately POSTs:

{"version":1,"application_id":"000000000000000000","type":0}

type: 0 is the PING. It will show up here within a second — and Discord will then refuse to save the URL, because a capture bin returns its own acknowledgement instead of a bare 204. That rejection is expected and the capture is still worth having: it proves Discord can reach the URL and shows exactly what you have to answer.

Discord’s validation is stricter than most, in a way worth knowing about before you write the handler. It sends two requests: the PING, which must get 204, and a request with a deliberately corrupted signature, which must get 401. You cannot pass by acknowledging everything.

if (!verifyEd25519(publicKey, timestamp, rawBody, signature)) {
  return new Response('invalid signature', { status: 401 });
}
if (payload.type === 0) return new Response(null, { status: 204 });

Both branches are required. Once your own endpoint has them, point Discord at it and use this page to read the event bodies.

Ed25519, not HMAC

Two headers arrive on every request:

X-Signature-Ed25519: 0000000000000000...
X-Signature-Timestamp: 1750000000

The signed data is the timestamp string followed immediately by the raw body, with nothing between them. The signature is a hex-encoded Ed25519 signature, and the key that verifies it is the public key on your application’s General Information page.

import nacl from 'tweetnacl';

const ok = nacl.sign.detached.verify(
  Buffer.from(timestamp + rawBody),
  Buffer.from(signature, 'hex'),
  Buffer.from(publicKey, 'hex'),
);

There is no shared secret anywhere in this flow, which is the part that stalls people — they go looking for a signing secret in the portal, do not find one, and try to HMAC with the public key instead. It also means there is nothing to paste into an HMAC verifier, including the one on this site. The upside is real: a compromised web server leaks a key that cannot forge a single event.

Reading a webhook event

{
  "version": 1,
  "application_id": "000000000000000000",
  "type": 1,
  "event": {
    "type": "APPLICATION_AUTHORIZED",
    "timestamp": "2026-06-15T10:04:00.000Z",
    "data": { "integration_type": 0, "scopes": ["applications.commands"], "user": { ... } }
  }
}

The outer type distinguishes a PING (0) from an event (1). The thing you actually switch on is event.type.

integration_type is 0 for a guild install and 1 for a user install — the difference between your app being added to a server and being added to one person’s account, and usually the difference between two quite different onboarding paths.

Discord snowflake ids arrive as strings, and they must stay strings. They exceed 2^53, so parsing one as a JavaScript number silently changes it, and the id you store will not match the id Discord sends next time.

Acknowledge first, work later

Webhook events want a 204 and nothing else. Interactions want a response object within three seconds — and for anything slower than that, a deferred response:

return Response.json({ type: 5 }); // DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE

Then edit the original response through the follow-up endpoint when the work is done. This is the same shape as Slack’s three-second rule: acknowledge inside the deadline, produce the real answer afterwards.

Failure modes

Where Discord integrations usually break

Discord will not save your URL until it answers PING

Saving the endpoint sends a PING and requires the right response — 204 with an empty body for webhook events, or `{"type": 1}` for interactions. A capture bin cannot give either, so use this page to see the PING arrive, then point Discord at your own endpoint once it can reply.

Discord also tests that you reject bad signatures

During validation Discord sends a request with a deliberately invalid signature and expects a 401. An endpoint that verifies nothing and returns 204 to everything fails setup — which is unusual, and a genuinely good design decision on Discord's part.

It is Ed25519, not an HMAC

The value in the portal is a public key. It cannot sign anything, so no HMAC library will help. This is the single most common reason a Discord verification attempt goes nowhere: people look for the shared secret and there is not one.

The timestamp is prepended, not separated

The signed data is `timestamp + rawBody`, with no delimiter. Both the raw bytes and the exact header string matter, so verify before any JSON parsing and never re-serialise the body first.

Interactions and webhook events are different URLs

The Interactions Endpoint URL handles commands and components and must reply with content. The Webhooks Endpoint URL receives events like `APPLICATION_AUTHORIZED` and just acknowledges them. Same signature scheme, different fields, different response requirements.

Answers

Discord webhook questions

Why is Discord rejecting my endpoint URL?+

Because the PING was not answered as required. Webhook event endpoints must return 204 with an empty body; interactions endpoints must return `{"type": 1}`. Discord additionally sends an invalidly signed request and requires a 401 back, so an endpoint that skips verification cannot pass validation.

Can TryWebhook verify a Discord signature?+

No. Discord signs with Ed25519, which is public-key cryptography rather than a keyed hash, and every verifier built in here is an HMAC. Use `discord-interactions`, `tweetnacl.sign.detached.verify`, or `crypto.verify` with the portal's public key.

How do I verify it myself?+

Concatenate the `X-Signature-Timestamp` header with the raw body, hex-decode `X-Signature-Ed25519`, and verify the pair against your application's public key. Do it on the raw bytes, before parsing, and return 401 on failure.

What is the difference between this and a Discord incoming webhook?+

An incoming webhook is a URL Discord gives you so you can post messages *into* a channel — no verification, because you are the sender. This page is about Discord posting out to your application, which is signed and validated.

Why does my bot work locally but fail after deploying?+

Usually a body that is no longer raw. Many frameworks parse and re-serialise JSON before your handler sees it, which changes the bytes and breaks the signature. Capture the raw body first, verify, then parse — and check your platform is not rewriting the timestamp header.

Other providers

Testing something else?

One click

Point Discord at a URL and watch the payload land.

Discord Developer Portal → your application → Webhooks → Endpoint URL — paste the URL, trigger an event, and read exactly what arrived.

No signup · No email · Ready in about a second