Discord webhook tester
Paste a Discord webhook URL, build a message or a rich embed, and send it straight from your browser. You get the real HTTP status back — a green 200, or the exact error to fix. Unlike a capture-only tool, this actually delivers to your channel, then shows you the response.
Discord webhooks are the highest-volume webhook cluster on the web — the one hobbyists reach for first and the one CI/monitoring pipelines fan out to. This page covers both directions: sending a test here, and capturing what another service (GitHub, UptimeRobot, a cron) sends into Discord.
Send a test message or embed
?wait=true so Discord returns the created message instead of an empty 204.{
"content": "Test message from webhook-toolkit.com ✅",
"embeds": [
{
"title": "Deployment succeeded",
"description": "Build `#1287` shipped to production in 42s.",
"color": 5793266,
"fields": [
{
"name": "Environment",
"value": "production",
"inline": true
},
{
"name": "Commit",
"value": "a1b2c3d",
"inline": true
}
]
}
]
}curl -X POST "https://discord.com/api/webhooks/ID/TOKEN?wait=true" \
-H "Content-Type: application/json" \
-d '{"content":"Test message from webhook-toolkit.com ✅","embeds":[{"title":"Deployment succeeded","description":"Build `#1287` shipped to production in 42s.","color":5793266,"fields":[{"name":"Environment","value":"production","inline":true},{"name":"Commit","value":"a1b2c3d","inline":true}]}]}'await fetch("https://discord.com/api/webhooks/ID/TOKEN?wait=true", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
"content": "Test message from webhook-toolkit.com ✅",
"embeds": [
{
"title": "Deployment succeeded",
"description": "Build `#1287` shipped to production in 42s.",
"color": 5793266,
"fields": [
{
"name": "Environment",
"value": "production",
"inline": true
},
{
"name": "Commit",
"value": "a1b2c3d",
"inline": true
}
]
}
]
}),
});Anatomy of a Discord webhook URL
Every Discord webhook is fully identified by two values baked into its URL. There is no separate API key and no signature — whoever has the URL can post, which is exactly why you should treat it like a password and regenerate it if it leaks.
https://discord.com/api/webhooks/1234567890123456789/aBcDeF...68-char-token
└────────┬────────┘ └──────────┬──────────┘
webhook id (snowflake) webhook token| Part | What it is | Notes |
|---|---|---|
| webhook id | 17–20 digit Discord snowflake | Encodes the creation timestamp; wrong id → 404 Unknown Webhook (code 10015) |
| webhook token | ~68-char secret | Wrong/rotated token → 401 Invalid Webhook Token (code 50027) |
| ?wait=true | Optional query flag | Returns the created message (200) instead of an empty 204 |
| ?thread_id=… | Optional query flag | Posts into a specific thread of the channel |
Where to find it: Server Settings → Integrations → Webhooks → New Webhook, then Copy Webhook URL. You need the Manage Webhooks permission on the target channel. The older discordapp.com host and canary./ptb. subdomains resolve to the same API.
The errors you will actually hit
Discord replies with a numeric HTTP status and a JSON code field. The JSON code is the precise one — two different problems can both surface as a 400.
| HTTP | code | Meaning | Fix |
|---|---|---|---|
| 204 | — | Success, no body returned | Add ?wait=true to see the message object |
| 400 | 50035 | Invalid form body (bad embed) | Check field limits: title 256, description 4096, ≤25 fields, ≤6000 total chars |
| 401 | 50027 | Invalid Webhook Token | Token in the URL is wrong or regenerated — copy a fresh URL |
| 404 | 10015 | Unknown Webhook | The webhook was deleted or the id is wrong — recreate it |
| 429 | — | Rate limited | Read retry_after (seconds) from the body and back off before retrying |
Hard limits (they reject, they don't truncate)
These are enforced by Discord — go over and the whole message is rejected with a 400, nothing is silently cut. The 6000-character total is the one that bites: it sums every text field across all embeds in a single message.
| Field | Limit |
|---|---|
| content (plain text) | 2000 characters |
| embeds per message | 10 |
| all embeds combined | 6000 characters total |
| embed.title | 256 characters |
| embed.description | 4096 characters |
| fields per embed | 25 |
| field.name / field.value | 256 / 1024 characters |
| footer.text | 2048 characters |
| rate limit | ~30 requests / 60s per webhook |
Copy-paste from your own code
The tool above already emits curl and Node for your exact payload. Here are the canonical forms to keep in a script — note the decimal color and ?wait=true.
curl -X POST "https://discord.com/api/webhooks/ID/TOKEN?wait=true" \
-H "Content-Type: application/json" \
-d '{
"content": "Deploy finished",
"username": "CI",
"embeds": [{
"title": "Build #1287",
"description": "Shipped to production in 42s",
"color": 5793266,
"fields": [
{ "name": "env", "value": "production", "inline": true },
{ "name": "commit", "value": "a1b2c3d", "inline": true }
]
}]
}'const res = await fetch(process.env.DISCORD_WEBHOOK + "?wait=true", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: "Deploy finished",
embeds: [{ title: "Build #1287", color: 0x5865f2 }], // hex literal → int
}),
});
if (res.status === 429) {
const { retry_after } = await res.json();
await new Promise((r) => setTimeout(r, retry_after * 1000));
}Capture what flows INTO Discord
Sending is only half the loop. When you wire GitHub → Discord, an alerting service, or a cron to a Discord webhook and the message never shows up, you need to see the exact bytes and headers the source is producing. Point that source at a Receiver URL first, inspect the raw request, then swap in the real Discord URL once it looks right.
Questions fréquentes
Where do I get a Discord webhook URL?
Open Server Settings → Integrations → Webhooks → New Webhook (or pick an existing one) → Copy Webhook URL. You need the Manage Webhooks permission on the channel. The URL looks like https://discord.com/api/webhooks/<id>/<token> where the id is a 17–20 digit snowflake and the token is roughly 68 characters.
Is my webhook URL sent to your server?
No. The test message is POSTed directly from your browser to Discord's API — their webhook endpoint returns permissive CORS headers, so no proxy is needed. The URL never leaves your machine except to reach discord.com.
Why did I get a 204 No Content instead of the message body?
A webhook execution returns 204 with an empty body by default. Append ?wait=true to the URL and Discord returns 200 plus the full created message object (id, timestamp, channel_id). This tool adds ?wait=true automatically.
What does 401 code 50027 mean?
"Invalid Webhook Token" — the token part of the URL is wrong or was regenerated. A 404 code 10015 "Unknown Webhook" means the webhook was deleted (or the id is wrong). Both are permanent: no retry will fix them, you need a fresh URL.
Why is embed.color a number and not #5865F2?
Discord expects color as a decimal integer, not a hex string. #5865F2 (Discord blurple) is 5793266 in decimal. This tool converts it for you; if you build the payload by hand, run parseInt('5865F2', 16).
What are the rate limits?
Each webhook is limited to roughly 30 requests per 60 seconds. Exceeding it returns HTTP 429 with a JSON body containing retry_after (seconds to wait) and the X-RateLimit-Reset-After header. Respect retry_after before sending again — hammering it can get the webhook temporarily blocked.