Discord Webhook: Example Request and Responses

A Discord webhook runs the other way from most: you send the request, and Discord posts it in a channel. Here is the exact request, the JSON it accepts and the responses to expect.

No webhook URL yet. Creating one needs no account.

Request details
Requests sent to your webhook URL appear here: method, headers and the body exactly as it arrived.

The button posts the example Discord payload below to your webhook URL from your browser, so you can see how the request is recorded. Nothing is sent to Discord.

Last updated . How this page is checked.

How the webhook URL works. It is a public address that records whatever is sent to it and answers 200. An inbox keeps the 50 most recent requests, accepts bodies up to 256 KB, and is deleted 24 hours after it was created — or sooner if you press Delete URL or our server restarts, because requests are held in memory and never written to disk.

Anyone who has the URL can read what it received. Use sandbox and test-mode accounts, do not route production traffic or customer data through it, and delete it when you are done. Privacy details.

What is a Discord webhook?

A Discord webhook is a URL attached to a channel. Send it an HTTP POST with a small JSON body and the message appears in that channel — no bot user, no gateway connection, no OAuth. It is the standard way to push build results, alerts and form submissions into a server.

The URL has the form https://discord.com/api/webhooks/{webhook.id}/{webhook.token}. The token in the path is the only authentication, which makes the URL itself the secret.

The request you send to Discord

The method, the URL shape and a body that uses the most common fields.

POST /api/webhooks/123456789012345678/WEBHOOK-TOKEN-PLACEHOLDER HTTP/1.1
Host: discord.com
Content-Type: application/json
User-Agent: your-app/1.0
{
  "username": "Deploy Bot",
  "content": "Build **#482** finished.",
  "embeds": [
    {
      "title": "api-service deployed to production",
      "url": "https://example.com/builds/482",
      "description": "All 318 tests passed.",
      "color": 3066993,
      "fields": [
        {
          "name": "Branch",
          "value": "main",
          "inline": true
        },
        {
          "name": "Duration",
          "value": "2m 14s",
          "inline": true
        }
      ],
      "timestamp": "2026-01-01T12:00:00.000Z"
    }
  ],
  "allowed_mentions": {
    "parse": []
  }
}

At least one of content, embeds or a file is required. color is a decimal integer, not a hex string. username and avatar_url override the webhook's defaults for this one message. allowed_mentions with an empty parse list stops text such as @everyone inside user-supplied content from pinging the server — set it whenever the message contains input you did not write.

Headers you send

Unlike the other examples in this section, these are headers you set on your own request.

HeaderExampleWhat it is
Content-Typeapplication/jsonRequired for a JSON body. File uploads use multipart/form-data with the JSON in a payload_json part.
User-Agentyour-app/1.0Optional, but a descriptive value helps if Discord ever needs to identify a misbehaving client.

Header names are case-insensitive. Your framework may show them lower-cased, and HTTP/2 always does.

Authentication: the URL is the credential

There is nothing to sign and no header to add. Whoever holds the webhook URL can post to the channel as that webhook, so treat it exactly like a password: keep it in a secret store, never in client-side code or a public repository, and delete and recreate the webhook if it leaks. Automated scanners look for these URLs.

That also means a browser cannot safely call it — any page that does has just published its own credential. Send from a server, or from a tool where the URL stays with you.

// Node 18+. WEBHOOK_URL comes from your environment, never from source control.
const res = await fetch(process.env.WEBHOOK_URL + '?wait=true', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    content: 'Build **#482** finished.',
    allowed_mentions: { parse: [] },
  }),
});

if (res.status === 429) {
  const body = await res.json();
  // retry_after is in seconds and may be fractional
  await new Promise((r) => setTimeout(r, body.retry_after * 1000));
}

Responses and rate limits

A successful execute returns 204 No Content with an empty body. Append ?wait=true and Discord instead returns 200 with the created message object, which you need if you intend to edit or delete that message later. A malformed body returns 400 with a JSON error describing the offending field, and a deleted webhook returns 404.

Webhooks are rate limited. When you exceed the limit Discord returns 429 with a JSON body containing retry_after, and every response carries X-RateLimit-* headers describing the current bucket. Read those values rather than hard-coding a delay; a client that keeps posting into a 429 can get its address temporarily blocked.

Check your payload before it reaches a channel

  1. Press Create webhook URL at the top of this page.
  2. Point your script, CI job or alerting tool at that address instead of the Discord URL and let it fire.
  3. Read the request in the inbox above: is the body JSON, is Content-Type set, did the template variables expand?
  4. Once it looks right, swap in the real Discord webhook URL.

To create the Discord side: in the server, open the channel's settings, choose Integrations → Webhooks → New Webhook, and copy its URL. To send a request to Discord from the browser, the Discord API tester has the console pre-filled.

To run a caught request against your own code, select it and press Open in API Tester: the method, headers and body are loaded into the request console, where you enter your server's public URL and send it. A body you edit will no longer match its signature — that is the signature doing its job.

Things that catch people out

  • Slack-format and GitHub-format payloads need a suffix. Appending /slack or /github to the URL makes Discord accept those providers' formats; the plain URL expects Discord's own JSON.
  • Limits are per field. content has a character cap, and embeds have caps per title, description, field and in total. An over-long value is rejected with 400, not truncated.
  • This is not the Interactions endpoint. Slash commands and buttons are delivered to your server by Discord and are signed with Ed25519 — a separate mechanism from channel webhooks.

api-tester.com is independent and is not affiliated with Discord. Providers change header names, limits and retry schedules; treat the official Discord documentation as the source of truth.

Related examples and tools

Other webhooks with no signature

References

These links leave api-tester.com, which is independent of the companies and projects they point to.

Discord webhook FAQ

How do I send a message with a Discord webhook?

Send an HTTP POST to the webhook URL with Content-Type: application/json and a body containing content, embeds or both. A successful request returns 204 No Content.

What JSON does a Discord webhook accept?

A message object: content for text, embeds for rich cards, plus optional username, avatar_url, tts, allowed_mentions and thread parameters. At least one of content, embeds or a file is required.

Why does my Discord webhook return 204 with no body?

That is the success response. Add ?wait=true to the URL to receive a 200 with the created message object instead.

Is a Discord webhook URL a secret?

Yes. The token in the URL is the only authentication, so anyone with the URL can post to the channel. Store it as a secret and recreate the webhook if it is exposed.

What does a 429 from a Discord webhook mean?

You hit a rate limit. The JSON response contains retry_after, the number of seconds to wait before sending again.