Discord Webhook with Zapier, Make and n8n: No-Code Guide
Set up a Discord webhook in Zapier, Make and n8n: where the HTTP module goes, exact JSON body with embeds, field mapping, testing and rate-limit pitfalls.
On this page
- Which tool fits your case
- One JSON body for all three tools
- Zapier: Webhooks by Zapier as the action
- Where the action goes
- Mapping fields without breaking the JSON
- Testing the Zap
- Make: HTTP → Make a request
- Where the module goes
- Mapping fields
- Testing in Make
- n8n: Discord node or HTTP Request node
- Where the node goes
- Mapping fields with a Code node
- Testing in n8n
- When to self-host n8n
- Rate limits: the pitfall all three tools share
- Common errors
- FAQ
- Do I need Webhooks by Zapier, or does Zapier’s Discord app work?
- Can I send embeds from n8n without writing JavaScript?
- Why does Make fail with 429 every other run?
- Can I edit or delete a message the automation sent?
- Next steps
A Discord webhook is the cheapest way to connect a no-code tool to your server: no bot, no OAuth, no permission scopes. Zapier, Make and n8n all do the same thing underneath, a POST with a JSON body to https://discord.com/api/webhooks/{id}/{token}; they just hide it behind different modules. Here is where that module sits in each tool, the exact body for an embed, how to map trigger data into it safely, and what to do when Discord answers 429.
Which tool fits your case
- Zapier: the biggest app catalogue and the fastest setup. You pay per task, and Webhooks by Zapier is a premium app that the free plan doesn’t include at the time of writing; check Zapier’s plan page before you build on it.
- Make (formerly Integromat): a visual canvas, cheaper per operation, an HTTP module that takes raw JSON, good aggregation tools.
- n8n: open source and self-hostable, with a Code node that runs real JavaScript. The pick for high volume, private data, loops and retries.
All three post to the same webhook, so nothing locks you in.
One JSON body for all three tools
Create the webhook in Server Settings → Integrations → Webhooks and copy the URL. Anyone who has it can post into your channel, and the only fix for a leak is deleting the webhook and creating a new one.
Send it once with curl first, so you know the URL and payload are fine:
curl -H "Content-Type: application/json" \
-d @- "https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN?wait=true" <<'EOF'
{
"username": "Orders",
"embeds": [{
"title": "New order #1042",
"url": "https://shop.example.com/orders/1042",
"color": 5793266,
"fields": [
{ "name": "Customer", "value": "Jane Doe", "inline": true },
{ "name": "Total", "value": "$129.00", "inline": true },
{ "name": "Items", "value": "2 x Hoodie (M)\n1 x Cap" }
],
"footer": { "text": "Shopify → Discord" },
"timestamp": "2026-09-17T09:30:00Z"
}]
}
EOF
?wait=true makes Discord return the created message as JSON (with its id) instead of an empty 204. Keep it on: you get a response body to inspect, and the id lets you edit or delete the message later.
In every tool, color is a decimal integer, not a hex string, timestamp is ISO 8601, content is capped at 2000 characters, an embed description at 4096 and all embeds of one message at 6000, with up to 10 embeds per message. If you’d rather click than type, design the embed in the visual builder and export the JSON; the full list is in Discord embed limits.
Zapier: Webhooks by Zapier as the action
Where the action goes
Trigger (Shopify, Typeform, Gmail, Schedule…) → Action → Webhooks by Zapier → event Custom Request. Zapier’s native Discord app posts plain text and doesn’t expose embeds.
- Method: POST
- URL: your webhook URL with
?wait=true - Data: the JSON body from above
- Headers:
Content-Type=application/json
Skip the simpler POST event: its Data fields are flat key/value pairs, awkward for embeds, which is an array of objects. Custom Request sends the body verbatim.
Mapping fields without breaking the JSON
Click into Data, put the cursor inside a string and pick the trigger field from the dropdown:
{
"embeds": [
{
"title": "New order #{{Order Number}}",
"color": 5793266,
"fields": [
{ "name": "Customer", "value": "{{Customer Name}}", "inline": true },
{ "name": "Total", "value": "{{Total Price}} {{Currency}}", "inline": true }
],
"timestamp": "{{zap_meta_utc_iso}}"
}
]
}
{{zap_meta_utc_iso}} is Zapier’s built-in ISO timestamp in UTC. The catch: Zapier pastes values verbatim, so a note with a double quote or a line break breaks the JSON and Discord answers 400. For free-text fields, build the body in a Code by Zapier step; JSON.stringify escapes everything:
// Code by Zapier (JavaScript). Input Data: name, total, note, url
const embed = {
title: `New order from ${inputData.name}`.slice(0, 256),
url: inputData.url,
color: 5793266,
description: (inputData.note || '').slice(0, 4096),
fields: [{ name: 'Total', value: inputData.total, inline: true }],
timestamp: new Date().toISOString(),
};
return { body: JSON.stringify({ username: 'Orders', embeds: [embed] }) };
Map the Code step’s body output into Data as its only content. Code by Zapier can also call fetch() and post the message itself; check Zapier’s docs for runtime limits first.
Testing the Zap
Test step on the Custom Request action sends a real request. With ?wait=true the response panel shows the message JSON; without it, an empty body with status 204 is also success. A 400 shows Discord’s error body with the path of the bad field, such as embeds.0.fields.0.value.
Make: HTTP → Make a request
Where the module goes
Add the trigger, then HTTP → Make a request. Make’s own Discord app needs its bot connected to your server; the HTTP module needs nothing but the URL.
- URL: the webhook URL with
?wait=true - Method: POST
- Body type: Raw
- Content type: JSON (application/json)
- Request content: the JSON body
- Parse response: Yes
Turn on Evaluate all states as errors (except for 2xx and 3xx) in the advanced settings; without it a 400 or 429 passes as a normal bundle and never shows up as a failure.
Mapping fields
Request content takes the same body as in Zapier, with {{1.customer_name}} pills. For the timestamp use {{formatDate(now; YYYY-MM-DDTHH:mm:ssZ; UTC)}} rather than a date mapped straight from the trigger: trigger dates are often plain strings in whatever format the source uses, and Discord accepts only ISO 8601. Make doesn’t escape mapped text either, so for free-text fields go through JSON → Create JSON: generate a data structure from the sample body, map the fields and pass its output to the HTTP module; it escapes quotes and newlines.
Testing in Make
Right-click the HTTP module → Run this module only, or run the scenario once. The output bundle shows statusCode and, with Parse response on, the message JSON in data; the execution history keeps every module’s input and output, so a failed run shows which field was wrong.
n8n: Discord node or HTTP Request node
Where the node goes
Two options after the trigger node:
- Discord node with a Webhook credential: paste the URL once into the credential (it’s stored encrypted), then use Message → Send with content, username, avatar and embeds as fields.
- HTTP Request node: Method POST, your URL, Send Body on, Body Content Type JSON, Specify Body: Using JSON. Use it when you need the exact payload.
Mapping fields with a Code node
The cleanest pattern is a Code node that builds the payload, followed by the HTTP Request node. JSON.stringify handles escaping, and you truncate to Discord’s limits in the same place:
// Code node, mode: Run Once for Each Item
const o = $input.item.json;
return {
json: {
username: 'Orders',
embeds: [
{
title: `New order #${o.order_number}`.slice(0, 256),
url: o.order_url,
color: 5793266,
description: (o.note ?? '').slice(0, 4096),
fields: [
{ name: 'Customer', value: String(o.customer_name), inline: true },
{ name: 'Total', value: `${o.total} ${o.currency}`, inline: true },
],
timestamp: new Date().toISOString(),
},
],
},
};
In the HTTP Request node set the JSON body to {{ JSON.stringify($json) }}. Without a Code node, write the body inline: n8n expressions are JavaScript, so {{ $now.toISO() }} gives an ISO timestamp and {{ $json.note.slice(0, 4096) }} a safe description.
Testing in n8n
Test step on the HTTP Request node shows the parsed response; with ?wait=true that’s the message object with its id. In the node’s Settings enable Retry On Fail with a few seconds between tries: that’s your 429 handling with no extra nodes.
When to self-host n8n
n8n Cloud is fine for a handful of workflows. Self-host when:
- Volume: hundreds of messages a day, where per-execution pricing starts to matter; the community edition has no execution limit.
- Data: orders, tickets or user records mustn’t leave your infrastructure.
- Control: secrets in environment variables (
$env.DISCORD_WEBHOOK_URL), custom npm packages in Code nodes, a private network.
docker run -d --name n8n --restart unless-stopped \
-p 5678:5678 \
-v n8n_data:/home/node/.n8n \
-e N8N_ENCRYPTION_KEY="pick-a-long-random-string" \
-e GENERIC_TIMEZONE="Europe/Berlin" \
docker.n8n.io/n8nio/n8n
Put it behind a reverse proxy with TLS and back up the n8n_data volume: it holds your credentials, including the webhook URL.
Rate limits: the pitfall all three tools share
Every run of a Zap, scenario or workflow is one POST. Fine for a form submission, a problem for a bulk import. Discord doesn’t publish one fixed number for webhooks, but in practice it’s roughly 30 requests per minute per webhook and bursts of about 5 per 2 seconds; past that comes 429 with retry_after in the body. Details are in Discord webhook rate limits.
- Zapier: Digest by Zapier collects entries and releases them as one batch on a schedule, so ten orders become one message instead of ten (the digest is one text block; split it into embeds in a Code step if you want one per order). Delay by Zapier spaces out runs; Autoreplay retries failed tasks on paid plans.
- Make: an Array aggregator turns N bundles into one HTTP call with up to 10 embeds. For sequential sends put Tools → Sleep between iterations, and add a Break error handler with retries on the HTTP module so a 429 is retried.
- n8n: HTTP Request → Options → Batching (items per batch, interval in ms) throttles a list; Retry On Fail covers the occasional 429. For long lists use Loop Over Items with a Wait node.
Batching into embeds beats throttling: one message with 10 embeds is one request instead of ten.
Common errors
- 400
The request body contains invalid JSON, code 50109: a quote or newline in a mapped field broke the body. Map through Code (Zapier, n8n) or Create JSON (Make). - 400 on
embeds.0.fields.0.value: a mapped field was empty. Give optional fields a fallback or drop the field when the value is missing. - 400 on
color: you passed"#5865F2". Discord wants a decimal integer:5793266. - 400 on
username: the override contains “discord” or “clyde” in any case, or exceeds 80 characters. - 400 on
timestamp: the mapped date wasn’t ISO 8601 (a trigger field in the source’s own format). Use{{zap_meta_utc_iso}},formatDate(now; YYYY-MM-DDTHH:mm:ssZ; UTC)or{{ $now.toISO() }}. - 404
Unknown Webhook, code 10015: the URL was truncated when pasting or the webhook was deleted. Recreate it and update the credential. - Mentions don’t ping:
<@&roleId>renders, but nobody is notified unlessallowed_mentionspermits it. See Discord webhook mentions.
FAQ
Do I need Webhooks by Zapier, or does Zapier’s Discord app work?
Zapier’s Discord app posts plain text without embeds. For embeds, use Webhooks by Zapier → Custom Request with the JSON from this guide, or a Code by Zapier step that calls fetch().
Can I send embeds from n8n without writing JavaScript?
Yes. The Discord node with a Webhook credential exposes embed fields, and the HTTP Request node accepts a JSON body with {{ }} expressions. The Code node only pays off when you need escaping, truncation or conditional fields.
Why does Make fail with 429 every other run?
The trigger returns several bundles per run and the HTTP module fires once per bundle, in a burst. Aggregate the bundles into one message with several embeds, or add a Sleep module between calls and a Break handler that retries.
Can I edit or delete a message the automation sent?
Yes. Send with ?wait=true, store the returned id, then PATCH or DELETE https://discord.com/api/webhooks/{id}/{token}/messages/{message_id} from another step. See editing and deleting webhook messages.
Next steps
Design the embed once in the free Discord Webhook builder, export the JSON and paste it into whichever tool you use. Then see automation workflows with Discord webhooks for more trigger ideas, Google Sheets to Discord for the spreadsheet version, and Discord webhook notifications for alerts that stay readable.