# How to Post to Discord with an API (2026 Developer Guide) > Post to a Discord channel with a webhook URL and no bot, no OAuth and no approval. Working code for content, embeds and files, plus the 2,000 character limit and rate limit handling. **Source:** https://posteverywhere.ai/blog/post-to-discord-api **Author:** Jamie Partridge **Published:** 2026-09-01 --- _Last updated: September 2026._ The Discord API has a shortcut most platforms do not offer. You can post to a channel with a single HTTP request, no bot, no OAuth, and no application review, using a webhook URL that a server admin can create in about thirty seconds. That makes Discord one of the two easiest networks to publish to programmatically, alongside [Telegram](/blog/post-to-telegram-api). It also has the tightest character limit of any major platform at 2,000, which is worth knowing before you pipe content into it. This guide covers webhooks, the bot alternative, embeds, file uploads, and the rate limit behaviour that catches people out at volume. ## Table of Contents 1. [The 30-Second Answer](#the-30-second-answer) 2. [Webhook or Bot: Which Do You Need](#webhook-or-bot-which-do-you-need) 3. [Create a Webhook URL](#create-a-webhook-url) 4. [Post a Message](#post-a-message) 5. [Rich Embeds](#rich-embeds) 6. [Uploading Files](#uploading-files) 7. [Discord Limits and Media Specs](#discord-limits-and-media-specs) 8. [Rate Limits and Retries](#rate-limits-and-retries) 9. [Posting to Forum Channels](#posting-to-forum-channels) 10. [When to Use a Publishing API Instead](#when-to-use-a-publishing-api-instead) 11. [FAQ: Discord API Posting](#faq-discord-api-posting) ## The 30-Second Answer To post to a Discord channel programmatically, create a webhook in the channel settings, then send a POST request to `https://discord.com/api/webhooks/{webhook.id}/{webhook.token}` with a JSON body containing `content`. Discord's documentation states you must provide a value for at least one of `content`, `embeds`, `components`, `file`, or `poll`. Message content supports up to 2,000 characters, and a message can include up to 10 embed objects. No OAuth, no app review, and no per-message fee applies. ## Webhook or Bot: Which Do You Need This is the first decision and most people over-engineer it. **Use a webhook** if you only need to post messages into one channel. A webhook is a URL with an embedded token. Anyone holding it can post to that channel, and nothing else. It cannot read messages, cannot see the member list, and cannot act anywhere else in the server. For publishing content, that limited scope is exactly what you want. **Use a bot** if you need to read messages, respond to commands, manage roles, react to events, or post across many channels with one identity. A bot requires creating an application, inviting it to the server with a permissions scope, and handling a gateway connection if you need real-time events. For the job of "publish our content to a Discord channel on a schedule", a webhook is correct and a bot is unnecessary complexity, in the same way a [scheduling tool](/social-media-scheduler) beats building a queue for most teams. The rest of this guide focuses on webhooks, and notes where the bot path differs. Our [comparison of social media APIs](/blog/best-social-media-apis) covers where Discord sits relative to other networks. ## Create a Webhook URL In Discord, open the target channel, choose Edit Channel, then Integrations, then Webhooks, then New Webhook. Give it a name and avatar, both of which appear on the messages it posts, and copy the webhook URL. If you manage several communities, our [multi-account tooling](/multi-account-management) keeps the destinations separate. The URL has the form `https://discord.com/api/webhooks/{webhook.id}/{webhook.token}`. That token is the entire authentication story, so treat the URL as a secret. Anyone who obtains it can post to your channel as that webhook until it is deleted or regenerated. Store it in an environment variable and never expose it in client-side code. Creating a webhook requires the Manage Webhooks permission in the server, so if you are building for someone else's community, a server admin has to do this step and hand you the URL. ## Post a Message The [Execute Webhook endpoint](https://docs.discord.com/developers/resources/webhook) takes `POST /webhooks/{webhook.id}/{webhook.token}`. The minimal request: ```bash curl -X POST "$DISCORD_WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d '{ "content": "New post is live: https://example.com/post" }' ``` In Node.js, with the error handling you actually need: ```js const res = await fetch(process.env.DISCORD_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: 'New post is live: https://example.com/post', username: 'Content Bot', }), }) if (res.status === 429) { const { retry_after } = await res.json() throw new Error(`Rate limited, retry after ${retry_after}s`) } if (!res.ok) { throw new Error(`Discord error ${res.status}: ${await res.text()}`) } ``` A successful execute returns `204 No Content` with an empty body by default, so do not try to parse JSON from it. If you need the created message back, append `?wait=true` to the URL and Discord returns the [message object](https://docs.discord.com/developers/resources/message) instead. The `username` and `avatar_url` fields override the webhook's configured name and avatar per message, which is useful if one webhook serves several content sources. > **Publishing to more than Discord?** One endpoint reaches 11 networks including Discord, Telegram, Instagram, LinkedIn and X, with per-platform limits applied for you. [See the API docs](/developers). ## Rich Embeds Plain `content` is limited to 2,000 characters and renders as plain text. Embeds are the better format for content distribution: they render as a card with a title, description, colour bar, image and footer. ```bash curl -X POST "$DISCORD_WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d '{ "embeds": [{ "title": "How the Instagram Algorithm Works in 2026", "description": "What actually drives reach this year, with sources.", "url": "https://example.com/post", "color": 5814783, "image": { "url": "https://example.com/hero.jpg" }, "footer": { "text": "Published 1 September" } }] }' ``` A message can carry up to 10 embed objects. The `color` field is a decimal integer rather than a hex string, which is a common source of confusion: `#58B9FF` becomes `5814783`. Embed fields have their own separate character limits which are more generous than the 2,000 for `content`, so if you are pushing long-form summaries, the description field of an embed is the place to put them rather than the message body. ## Uploading Files Discord's documentation directs you to its [uploading files reference](https://docs.discord.com/developers/reference) for attachments, which use `multipart/form-data` rather than JSON. ```bash curl -X POST "$DISCORD_WEBHOOK_URL" \ -F 'payload_json={"content":"This week in numbers"}' \ -F 'files[0]=@chart.png' ``` The JSON body goes in a `payload_json` part and each file gets its own part. You can reference an uploaded file from an embed using the `attachment://filename.png` scheme, which is how you get an image into an embed without hosting it publicly first. For most publishing workflows, passing a public image URL in an embed is simpler and avoids the multipart handling entirely. Our [publishing layer](/social-media-publishing) does the same thing across networks so the media path is consistent. ## Discord Limits and Media Specs These are the constraints our own platform rules enforce for Discord, which is to say the ones that cause real failures: | Constraint | Value | |---|---| | Message character limit | 2,000 | | Embeds per message | 10 | | Image file size | 25 MB | | Image formats | JPG, PNG, WebP, GIF | | Video file size | 25 MB | | Video max duration | 3,600 seconds | | Video formats | MP4, MOV, WebM (H.264) | The 2,000 character limit is the lowest of any network we publish to. The [Telegram Bot API](https://core.telegram.org/bots/api) allows 4,096, LinkedIn 3,000, Instagram 2,200 and X 25,000. If you are [cross-posting](/cross-posting) one piece of copy everywhere, Discord is almost always the constraint that forces a truncation, so write to its limit or maintain a separate short variant. File size limits are also affected by the server's boost level, which can raise the ceiling above the base figure. Do not assume a larger upload will succeed just because it worked on a boosted server. ## Rate Limits and Retries Discord is unusually transparent about rate limiting, and building against it properly is straightforward if you read the headers. Per its [rate limit documentation](https://docs.discord.com/developers/topics/rate-limits), all bots can make up to 50 requests per second globally, independent of per-route limits. Exceeding this consistently can result in temporary IP bans, so this is not a limit to probe. Responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `X-RateLimit-Reset-After` and `X-RateLimit-Bucket`. On a 429 you also get `X-RateLimit-Scope`, whose value is `user`, `global` or `shared`. The correct handling is to rely on the `Retry-After` header or the `retry_after` field in the body to decide when to retry, rather than a fixed backoff of your own design. Reading `X-RateLimit-Remaining` and pausing before you hit zero is better still, since it avoids the 429 entirely. The `X-RateLimit-Bucket` value matters if you post to many channels: limits are tracked per bucket rather than globally per route, so a queue that treats all sends as one limit will either be too slow or too aggressive. Our [guide to API posting automation](/blog/automate-social-media-posting-api) covers queue design that handles this pattern. ## Posting to Forum Channels Forum and media channels behave differently and this catches people out. Per Discord's webhook documentation, posting to one requires either a `thread_id` query parameter to post into an existing thread, or `thread_name` in the request body to create a new one. Omit both and the request fails. If your community uses forum channels for announcements, which many do, use `thread_name` so each post creates its own discussion thread, and see our [scheduling API guide](/blog/social-media-scheduling-api-guide) for how to store that alongside pending posts: ```json { "thread_name": "September product update", "content": "Here is what shipped this month." } ``` ## When to Use a Publishing API Instead For Discord alone, use the webhook. It is free, takes minutes, and has no meaningful downside. Building a whole abstraction over a single webhook call would be silly. The calculation changes once Discord is one destination among several. Each network you add brings a different auth model, a different media pipeline and a different set of limits. Telegram uses a static bot token, Discord uses a webhook URL, Meta requires OAuth plus app review, TikTok requires approval, and [X now charges per call](/blog/x-twitter-api-pricing). Nothing transfers between them except your own scheduling code. A [unified publishing API](/social-media-api) turns that into one endpoint and one auth model, with per-platform formatting handled for you, including the 2,000 character truncation Discord will otherwise impose without warning. Scheduling becomes a parameter rather than infrastructure you build, run and monitor. If an assistant rather than a service drives your publishing, our [connectors](/connectors) wire Claude, ChatGPT and Cursor directly into posting, and the [agent framework comparison](/blog/ai-agent-frameworks-for-social-media) covers building it yourself. > **Eleven networks, one integration.** PostEverywhere handles auth, media, per-platform limits and retries. From $9/mo, 7-day trial, card required. [See pricing](/pricing). ## FAQ: Discord API Posting ### How do I post to a Discord channel with an API? Create a webhook in the channel settings, then send a POST request to `https://discord.com/api/webhooks/{webhook.id}/{webhook.token}` with a JSON body containing `content`. No bot, OAuth flow or application review is required, and there is no per-message fee. ### Do I need a bot to post to Discord? No. A webhook is sufficient for posting messages into a single channel and is the simpler option. You need a bot only if you also want to read messages, respond to commands, manage roles, or act across many channels under one identity. ### What is the Discord message character limit? Message content supports up to 2,000 characters, the tightest limit of any major social platform. Embed description fields allow more, so long summaries belong in an embed rather than the message body. A message can include up to 10 embed objects. ### Is the Discord API free? Yes. Discord does not charge for API or webhook access and there is no per-message fee. Rate limits apply, with bots limited to 50 requests per second globally, but no billing is attached to sending messages. ### How do I handle Discord rate limits? Read the `X-RateLimit-Remaining` and `X-RateLimit-Reset-After` headers and pause before the remaining count reaches zero. If you do receive a 429, use the `Retry-After` header or the `retry_after` field in the body to decide when to retry rather than a fixed backoff. Limits are tracked per bucket, indicated by `X-RateLimit-Bucket`. ### Why does my webhook fail on a forum channel? Forum and media channels require either a `thread_id` query parameter to post into an existing thread, or `thread_name` in the request body to create a new one. A request omitting both will fail. Standard text channels do not need either field. ### Can I schedule posts with a Discord webhook? No. The webhook endpoint publishes immediately and has no scheduling parameter, so future-dated posting requires your own cron job or queue plus storage for pending messages. A scheduling platform handles timing as a parameter instead. ### What does a successful Discord webhook request return? By default it returns `204 No Content` with an empty body, so attempting to parse JSON from the response will fail. Append `?wait=true` to the webhook URL if you need the created message object returned instead.