Send Discord Webhooks from JavaScript (Node.js & Browser)
Learn how to send Discord webhook messages using JavaScript. Covers Node.js, browser fetch API, embeds, files, and error handling with practical examples.
Why Use JavaScript for Discord Webhooks?
JavaScript is the natural choice for Discord webhook integrations because:
- Universal: Works in Node.js servers, browser clients, and serverless functions
- Native JSON support: Discord webhooks use JSON, which JavaScript handles natively
- Async-friendly: Modern async/await syntax makes webhook requests clean and readable
- Rich ecosystem: Libraries like Axios and node-fetch provide powerful HTTP clients
This guide covers both Node.js and browser environments with practical examples you can use immediately.
Node.js: Basic Webhook Request
Node.js 18+ includes the native fetch API. For older versions, install node-fetch:
npm install node-fetch
Send a simple text message:
const WEBHOOK_URL = "https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN";
async function sendMessage(content) {
const response = await fetch(WEBHOOK_URL, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ content })
});
if (response.ok) {
console.log("Message sent successfully");
} else {
console.error(`Error: ${response.status} ${response.statusText}`);
}
}
sendMessage("Hello from Node.js!");
A successful webhook request returns status 204 No Content.
Node.js: Send Rich Embeds
Embeds provide formatted messages with colors, fields, and images:
async function sendEmbed() {
const embed = {
title: "Server Status Report",
description: "All systems operational",
color: 5763719, // Green
fields: [
{
name: "CPU Usage",
value: "23%",
inline: true
},
{
name: "Memory",
value: "4.2 GB / 16 GB",
inline: true
},
{
name: "Uptime",
value: "14 days, 6 hours",
inline: false
}
],
footer: {
text: "Monitoring Bot"
},
timestamp: new Date().toISOString()
};
const response = await fetch(WEBHOOK_URL, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ embeds: [embed] })
});
return response.ok;
}
sendEmbed();
The embeds property accepts an array of up to 10 embed objects.
Node.js: Send Files and Attachments
To send files, use FormData with multipart/form-data:
import { FormData, File } from "node-fetch"; // Node.js 18+
import fs from "fs";
async function sendFile(filePath, message) {
const formData = new FormData();
// Add the message payload
formData.append("payload_json", JSON.stringify({
content: message
}));
// Add the file
const fileBuffer = fs.readFileSync(filePath);
const fileName = filePath.split("/").pop();
formData.append("file", new File([fileBuffer], fileName));
const response = await fetch(WEBHOOK_URL, {
method: "POST",
body: formData
});
return response.ok;
}
sendFile("./logs/server.log", "Latest server logs:");
For multiple files, append each with a unique field name (file1, file2, etc.).
Node.js: Error Handling and Retries
Handle rate limits and network errors gracefully:
async function sendWebhook(data, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
if (response.ok) {
return { success: true };
}
if (response.status === 429) {
// Rate limited
const retryAfter = response.headers.get("retry-after");
const delay = retryAfter ? parseInt(retryAfter) * 1000 : 2000;
console.log(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Other error
const error = await response.text();
return { success: false, error: `${response.status}: ${error}` };
} catch (err) {
if (attempt === maxRetries) {
return { success: false, error: err.message };
}
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
return { success: false, error: "Max retries exceeded" };
}
// Usage
const result = await sendWebhook({
content: "Test message with retry logic"
});
if (result.success) {
console.log("Sent successfully");
} else {
console.error("Failed:", result.error);
}
Discord rate limits are:
- 5 requests per 2 seconds per webhook
- 30 requests per 60 seconds per channel
Browser: Send Webhooks from Client-Side JavaScript
Browser environments use the native fetch API:
const WEBHOOK_URL = "https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN";
async function sendFromBrowser(message) {
try {
const response = await fetch(WEBHOOK_URL, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
content: message,
username: "Web App",
avatar_url: "https://example.com/avatar.png"
})
});
if (response.ok) {
console.log("Message sent");
} else {
console.error("Failed to send:", response.status);
}
} catch (error) {
console.error("Network error:", error);
}
}
// Trigger from a button click
document.getElementById("sendBtn").addEventListener("click", () => {
sendFromBrowser("Hello from the browser!");
});
Security warning: Exposing webhook URLs in client-side code allows anyone to send messages to your Discord channel. For production apps, proxy webhook requests through your backend server.
Browser: Secure Webhook Proxy Pattern
Instead of exposing the webhook URL, create a backend endpoint:
Backend (Node.js/Express):
import express from "express";
const app = express();
app.use(express.json());
const WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL; // Store securely
app.post("/api/send-notification", async (req, res) => {
const { message } = req.body;
// Validate and sanitize input
if (!message || message.length > 2000) {
return res.status(400).json({ error: "Invalid message" });
}
try {
const response = await fetch(WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content: message })
});
if (response.ok) {
res.json({ success: true });
} else {
res.status(response.status).json({ error: "Discord API error" });
}
} catch (error) {
res.status(500).json({ error: "Server error" });
}
});
app.listen(3000);
Frontend:
async function sendSecure(message) {
const response = await fetch("/api/send-notification", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message })
});
const result = await response.json();
return result.success;
}
sendSecure("Secure message from browser");
This pattern:
- Keeps webhook URLs secret
- Allows server-side validation and rate limiting
- Prevents abuse of your webhook
Using Axios (Alternative HTTP Client)
Axios provides a cleaner API for some developers:
npm install axios
import axios from "axios";
async function sendWithAxios() {
try {
await axios.post(WEBHOOK_URL, {
embeds: [{
title: "Deployment Complete",
description: "Version 2.1.0 deployed successfully",
color: 5763719,
timestamp: new Date().toISOString()
}]
});
console.log("Sent via Axios");
} catch (error) {
if (error.response) {
console.error("Discord error:", error.response.status, error.response.data);
} else {
console.error("Network error:", error.message);
}
}
}
sendWithAxios();
Axios automatically handles JSON serialization and provides better error details.
Real-World Example: Form Submission Notifications
Send Discord notifications when users submit a contact form:
import express from "express";
const app = express();
app.use(express.json());
app.post("/api/contact", async (req, res) => {
const { name, email, message } = req.body;
// Send to Discord
const embed = {
title: "New Contact Form Submission",
color: 5793266,
fields: [
{ name: "Name", value: name, inline: true },
{ name: "Email", value: email, inline: true },
{ name: "Message", value: message, inline: false }
],
timestamp: new Date().toISOString()
};
try {
await fetch(process.env.DISCORD_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ embeds: [embed] })
});
res.json({ success: true, message: "Thank you for contacting us!" });
} catch (error) {
console.error("Failed to send Discord notification:", error);
// Still return success to user - don't expose internal errors
res.json({ success: true, message: "Thank you for contacting us!" });
}
});
app.listen(3000);
TypeScript Support
For TypeScript projects, define types for webhook payloads:
interface WebhookEmbed {
title?: string;
description?: string;
color?: number;
fields?: Array<{
name: string;
value: string;
inline?: boolean;
}>;
footer?: {
text: string;
icon_url?: string;
};
timestamp?: string;
}
interface WebhookPayload {
content?: string;
username?: string;
avatar_url?: string;
embeds?: WebhookEmbed[];
}
async function sendTypedWebhook(payload: WebhookPayload): Promise<boolean> {
const response = await fetch(WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
return response.ok;
}
Best Practices
Store webhook URLs securely: Use environment variables, never hardcode URLs in source code.
Validate input: Sanitize user-provided content before sending to Discord to prevent injection attacks.
Handle rate limits: Implement exponential backoff when you receive 429 responses.
Use async/await: Modern async syntax is cleaner than promise chains for webhook requests.
Log errors: Always log failed webhook requests for debugging, but don’t expose errors to end users.
Test payloads first: Use the Discord Webhook Builder to design and test your embed layouts before coding them.
Troubleshooting
CORS errors in browser: Discord webhooks don’t support CORS. You must proxy requests through your backend.
400 Bad Request: Check your JSON syntax. Common issues include invalid color values (must be decimal integers) or missing required fields.
401 Unauthorized: Your webhook URL is incorrect or the webhook was deleted.
Rate limit errors: Reduce request frequency or implement queuing with delays between messages.
Next Steps
You now know how to send Discord webhooks from JavaScript in both Node.js and browser environments. For complex embed designs, try our free Discord Webhook Builder to visually create messages, then export the JSON for use in your JavaScript applications.
discord-webhook.com also offers scheduled messages, thread and forum support, polls, and interactive buttons with actions — all configurable through the visual builder without writing code.
Related Articles
- How to Send Discord Webhook Messages with Python — A practical guide to sending Discord webhook messages using Python and the requests library
- Complete Guide to Discord Embed Formatting — Master Discord embed builder techniques including fields, colors, images, and character limits
- Send Discord Notifications from GitHub Actions CI/CD — Learn how to send Discord webhook notifications from GitHub Actions workflows
- Discord Webhook Polls Guide — Create interactive polls in Discord channels using webhooks
- Discord Webhook Scheduled Messages — Automate recurring messages and timed notifications with scheduled webhooks
- Thread and Forum Support — Post webhook messages to Discord threads and forum channels
Try it in our tool
Open Discord Webhook Builder