Discord Webhook Limits: Rate, Size and Components
Webhook failures usually trace back to one of three separate ceilings: the size of a single message, the speed you call one webhook, and how fast one channel accepts webhook deliveries. Knowing which ceiling you hit turns a mystery 400 or 429 into a five-minute fix.
Message size and component ceilings
The numbers below are the ones DWEEB validates against before send; the Components V2 rows are generated from the same constants the editor uses, so this table cannot drift from the product.
| What | Limit | Applies to |
|---|---|---|
| Plain message content | 2,000 characters | content field (legacy messages) |
| Combined embed text | 6,000 characters across all embeds | Legacy embeds (max 10 per message, 25 fields each) |
| Components V2 text budget | 4,000 characters across all text-bearing fields | Every Text Display, label and option together |
| Total components | 40 (max 10 top-level) | Includes every nested component |
| Buttons per Action Row | 5 | A select menu takes the whole row |
| Select menu options | 25 | String select options per menu |
| Media Gallery items | 10 | Images/media per gallery |
| Button label | 80 characters | Longer labels are rejected, not truncated |
| Webhook username override | 80 characters | Per-message username field |
Rate limits and HTTP 429
Discord rate-limits per route: every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset-After headers describing the bucket you just spent from, and exceeding it returns HTTP 429 with a retry_after value. Those headers are the only contractual numbers — treat them, not any fixed figure, as the source of truth.
In practice, executing one webhook is bucketed at roughly five requests per two seconds, and Discord has additionally described a delivery cap of around 30 webhook messages per minute into a single channel. Both can change without notice, which is exactly why well-behaved senders react to the headers instead of hardcoding a rate.
HTTP/1.1 429 Too Many Requests
Retry-After: 1
X-RateLimit-Remaining: 0
{ "message": "You are being rate limited.", "retry_after": 0.529, "global": false }Staying under the limits
- Send one rich Components V2 message instead of a burst of small ones — layout blocks replace the multi-message pattern.
- Queue sends to a single webhook serially and sleep for retry_after (seconds) on any 429 before retrying.
- Never fan a loop out over one webhook URL in parallel; the bucket is shared and every request after the first few will 429.
- Schedule non-urgent posts instead of firing them together at the top of the hour.
- Split genuinely long announcements by design (a follow-up message) rather than letting truncation decide.
How DWEEB enforces this before send
The editor tracks the character budget and component ceilings live, itemizes violations in the issue list, and blocks send on error-severity problems — so a 400 invalid form body for an oversized payload is caught before the request exists. The full nesting rules live in the Components V2 guide.
Put the guide into practice
Open the exact workflow in DWEEB. Nothing posts until you review and confirm it.
Validate a message against the limits →