DWEEB
Developer guide · JavaScript

Send a Discord Webhook with JavaScript

A Discord webhook is a URL that accepts an HTTP POST, and every modern JavaScript runtime already has fetch — so you can send a rich message without a bot, a library or a token. This guide gives you a working script and the details that decide whether it works.

A working script

Save this as an .mjs file, paste your webhook URL and run it with Node 18 or newer; it also runs unchanged in Deno, Bun and a Cloudflare Worker. If you do not have a URL yet, the webhook setup guide takes about a minute.

// Components V2 webhook message — designed in DWEEB (https://dweeb.faizo.net).
// Runs anywhere fetch does: Node 18+, Deno, Bun, a Cloudflare Worker.
// Keep the webhook URL out of browser code — anyone who reads it can post.
const WEBHOOK_URL = "https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN";

const payload = {
  "components": [
    {
      "type": 17,
      "accent_color": 5793266,
      "components": [
        {
          "type": 10,
          "content": "# Server update\nEverything you need in one place."
        },
        {
          "type": 14,
          "divider": true,
          "spacing": 1
        },
        {
          "type": 1,
          "components": [
            {
              "type": 2,
              "style": 5,
              "label": "Read the guide",
              "url": "https://example.com/update"
            }
          ]
        }
      ]
    }
  ],
  "flags": 32768
};

// with_components=true is required, or Discord silently drops the components.
const response = await fetch(`${WEBHOOK_URL}?with_components=true&wait=true`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(payload),
});
if (!response.ok) {
  throw new Error(`Discord returned ${response.status}: ${await response.text()}`);
}

The three details that decide whether it works

  • with_components=true in the query string. A webhook created in Server Settings ignores the components array unless the request opts in. Discord still answers with a success status, so the symptom is a message that arrives empty or not at all.
  • flags: 32768 in the payload. That is the Components V2 flag, 1 << 15. With it set, content and embeds are not accepted in the same payload — visible text goes into type 10 Text Display components.
  • wait=true if you want the message back. Without it Discord answers 204 with no body. With it you get the message object, including the id you need to edit the message later.

Never call a webhook from a web page

A webhook URL is a credential: anyone who can read it can post to your channel, and it cannot be scoped or rate-limited per caller. JavaScript that runs in a visitor's browser is readable by that visitor, so a URL placed there is public the moment the page loads. Call the webhook from a server, a serverless function or a Worker, and keep the URL in an environment variable. The webhook security guide covers what to do if one has leaked.

Uploading a file with the message

To show a local image or attach a document, send a FormData body instead of JSON. The payload goes into a field called payload_json, each file is appended as files[0], files[1] and so on, and an attachments array in the payload maps each part to the filename its component refers to with attachment://. Do not set the Content-Type header yourself — fetch has to add the multipart boundary.

Open a message that uses an uploaded image or a File component in DWEEB and the Code tab writes this variant for you.

Rate limits and errors

  • 429: you are posting too fast. The JSON body carries retry_after in seconds; wait that long and send the same request again. Details are in the webhook limits guide.
  • 400 with a field path such as components.0.components.2: the payload broke one of Discord's rules. The error guide explains how to read it.
  • 401 or 404: the URL is wrong, or the webhook was deleted.
  • A success status but nothing in the channel: with_components=true is missing.

Writing a bot instead?

If the message is sent by a discord.js bot, use its builder classes rather than raw JSON: see Components V2 in discord.js. The component tree is identical; only the way it is sent differs.

Either way, you do not have to write the payload by hand. Build the message in DWEEB's visual editor, check it in the live preview, and the code generator exports it as this exact script.

Put the guide into practice

Open the exact workflow in DWEEB. Nothing posts until you review and confirm it.

Build the message and export the JavaScript →

Primary sources

Keep learning