Library / SDK
lovvskillz/python-discord-webhook avatar
lovvskillz/python-discord-webhook

python-discord-webhook sends Discord messages with one class and one execute call

execute discord webhooks

538 stars85 forksPythonMIT

At a glance

What is it?
discord-webhook is a small Python library for posting to Discord webhooks, with a single DiscordWebhook class that handles rate limit retries, threads, embeds, editing and deleting, and an async mode that swaps requests for httpx.
Who is it for?
discord-webhook fits a script or service that posts notifications to a Discord channel and wants embeds, threads, retries and editing without hand-rolling the request handling. It is the wrong choice for anything needing a full bot, and it is the wrong choice if you need to consume Discord events rather than push messages out.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 68 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One class, one execute call

The whole surface of the library is visible in its shortest example. You import the DiscordWebhook class, construct it with a URL and the content you want to post, and call execute:

python
from discord_webhook import DiscordWebhook

webhook = DiscordWebhook(url="your webhook url", content="Webhook Message")
response = webhook.execute()

The constructor takes keyword arguments for everything the message needs, and execute returns the response so you can inspect the status and the body. There is no session object to build, no decorator to register, and no client to close.

That shape is the reason the library is popular for small scripts. A cron job that should post a status line is three lines of Python, and the alternative, using the requests library directly, means knowing the Discord payload shape and the header format by hand.

The package installs from PyPI under the name discord-webhook, and the import name is discord_webhook. The install is the usual one:

bash
pip install discord-webhook

Multiple URLs mean multiple instances, and there is a batch helper

A webhook is bound to one URL, so using several means using several objects. The library provides a batch constructor for exactly that, and it takes the shared keyword arguments plus a list of URLs, returning one instance per URL:

python
from discord_webhook import DiscordWebhook

# you can provide any kwargs except url
webhook1, webhook2 = DiscordWebhook.create_batch(urls=["first url", "second url"], content="Webhook Message")
response1 = webhook1.execute()
response2 = webhook2.execute()

The restriction that any keyword except url can be provided is the important detail. The URL is what distinguishes one instance from another, so it is supplied per element of the list rather than as a shared argument, and everything else is shared across the batch. The returned objects behave like any other instance, so you can edit or delete one of them later.

The same pattern is used for editing and deleting, and for a second entry point: the README also shows that you can address a message you already sent by providing its id, which is what you need in order to edit or delete it afterwards.

python
webhook = DiscordWebhook(url="your webhook url", id="your webhook message id")

Rate limits are retried rather than raised

Discord rate limits webhook posts, and the library's answer is a flag rather than an exception handler you have to write. Setting rate_limit_retry to true means that if the request is rate limited, the webhook is sent automatically once the limit has been lifted:

python
from discord_webhook import DiscordWebhook

# if rate_limit_retry is True then in the event that you are being rate 
# limited by Discord your webhook will automatically be sent once the 
# rate limit has been lifted
webhook = DiscordWebhook(url="your webhook url", rate_limit_retry=True, content="Webhook Message")
response = webhook.execute()

This changes the failure mode in a way worth thinking about before you switch it on. A plain execute returns a response and lets you decide what a 429 means for your caller. With the retry enabled, the call blocks until Discord lets it through, so a script that posts in a loop can end up waiting rather than reporting a problem.

The example file manage_rate_limits.py in the examples directory covers the same behaviour as a runnable script, alongside basic_webhook.py, multiple_webhook_urls.py, embedded_content.py, send_files.py, timeout.py, use_async.py and use_proxy.py.

Embeds are a builder object with a colour and a layout

Rich messages use a second class, DiscordEmbed, which is created on its own and then attached to the webhook. The constructor takes a title, a description and a colour, and the colour can be given either as a decimal number such as 242424 or as a hex string such as 03b2f8:

python
from discord_webhook import DiscordWebhook, DiscordEmbed

webhook = DiscordWebhook(url="your webhook url")

# create embed object for webhook
# you can set the color as a decimal (color=242424) or hex (color="03b2f8") number
embed = DiscordEmbed(title="Your Title", description="Lorem ipsum dolor sit", color="03b2f8")

# add embed object to webhook
webhook.add_embed(embed)

response = webhook.execute()

The rest of the embed API is a set of setters for the parts Discord renders: set_author with a name, url and icon, set_image, set_thumbnail, set_footer with text and an icon, set_timestamp which defaults to now and accepts an int, a float or a datetime, and add_embed_field for the field rows.

The layout detail is the inline flag. By default the fields sit side by side, and setting inline=False on a field makes it occupy a whole line, which is how you get a field block rather than a column.

python
embed.add_embed_field(name="Field 1", value="Lorem ipsum", inline=False)

Threads are addressed by id or created by name

A webhook can post into a thread rather than the channel it was created in, and the library offers both directions of that. Setting thread_id posts into an existing thread, and setting thread_name creates a new thread in a forum channel:

python
from discord_webhook import DiscordWebhook

# send to an existing thread
webhook = DiscordWebhook(url="your webhook url", thread_id="the thread id")
webhook.execute()

# create a new thread in a forum channel
webhook = DiscordWebhook(url="your webhook url", thread_name="some-thread-name")
webhook.execute()

The distinction matters in practice. A webhook belongs to a channel, and a thread is a sub-conversation inside one, so a forum channel is the case where naming a thread is the only option, since the thread does not exist until the message creates it. For a regular text channel with an existing thread, the id is what you use.

The same constructor also takes a username, which overrides the name the webhook posts under, so a single webhook can appear to come from different sources.

Editing and deleting a message you sent

The library can also act on a message after it has been sent, which is what you need for the common pattern of posting a placeholder and then replacing it with a result.

Editing sets the content on the object and calls edit:

python
from discord_webhook import DiscordWebhook
from time import sleep

webhook = DiscordWebhook(url="your webhook url", content="Webhook content before edit")
webhook.execute()
webhook.content = "After Edit"
sleep(10)
webhook.edit()

Deleting works the same way, with a delete call after the message has been posted:

python
from discord_webhook import DiscordWebhook
from time import sleep

webhook = DiscordWebhook(url="your webhook url", content="Webhook Content")
webhook.execute()
sleep(10)
webhook.delete()

The sleep calls are in the examples because Discord enforces a short delay between operations on the same message, and the examples are demonstrating the sequence rather than a recommended timing. For the edit and delete calls to work, the object needs the id of the message it sent, which is why the same class can be constructed with an id and a URL.

The README also lists sections for removing embeds and files, allowed mentions, message flags, proxies and timeouts, and the examples directory has send_files.py, use_proxy.py and timeout.py for those paths.

Async support is an extra, not a rewrite

The package supports asynchronous use, and the README links to that section from the top. The mechanism is a separate extra rather than a different API, and the packaging explains why.

The project metadata lists requests as a required dependency and httpx as an optional one, with an extra named async that contains httpx. So the synchronous path uses requests, and installing the async extra gives you the httpx backed client.

The extra is installed by name, with the async extra appended to the package name in brackets.

The console script is defined in the packaging too, with the entry point mapping to the module's main function, so the package also installs a command line program called discord_webhook.

The Python requirement is 3.10 or newer, and the development dependencies are small and typical for a library of this size: pytest for the tests, ruff for linting with a line length of 88 and the line length rule relaxed, types-requests for the typed requests stubs, and pre-commit for the hooks. The repository holds a pre-commit configuration, a CHANGES file, a tests directory and an examples directory.

Where the library is the wrong tool

The first limit is that this is a one-way sender. It posts messages to Discord and edits or deletes what it posted. It does not read channel history, does not receive events, and does not connect as a bot, so anything that needs to react to what people say in a channel needs a different library entirely.

The second is the surface area of Discord itself. The library covers what a webhook can do, which is a well-defined slice, but features that belong to the bot API, slash commands, interactions, presence or voice, are not here, and the embeds it builds are the webhook subset of Discord's rich content.

The third is the maintenance rhythm, which is the factor to weigh for anything you depend on. The latest release is 1.4.1 from 2025-03-05, with 1.4.0 the day before and 1.3.1 in January 2024, and the last push to the repository was on 2026-08-16. The project is not archived, so work continues, but a library that posts to a third party API has to be checked against that API's own changes, and a year-long gap between releases is a fact to plan around.

The fourth is thread safety, which the library says nothing about. The synchronous path uses requests, and sharing one DiscordWebhook instance across threads is not a documented pattern, so concurrent posting is better done with one instance per thread or with the async client.

Editorial conclusion

discord-webhook fits a script or service that posts notifications to a Discord channel and wants embeds, threads, retries and editing without hand-rolling the request handling. It is the wrong choice for anything needing a full bot, and it is the wrong choice if you need to consume Discord events rather than push messages out. Before you adopt it, check three things: that your Python is 3.10 or newer, which the package requires, that you install the async extra if you want the httpx backed client rather than requests, and that you read the rate limit behaviour, since the retry option changes a failure into a wait. The licence is MIT, the current version is 1.4.1 from 2025-03-05, and the last push was on 2026-08-16.

Frequently asked questions

How do I send a Discord webhook message from Python?

Install the package with `pip install discord-webhook`, then create a DiscordWebhook with your URL and content and call execute. The constructor takes keyword arguments for the message, and execute returns the response.

How does discord-webhook handle rate limits?

By setting `rate_limit_retry=True` on the webhook. If Discord rate limits the request, the message is sent automatically once the limit has been lifted, so the call waits rather than returning the rate limited response.

How do I post to a Discord thread with discord-webhook?

Set `thread_id` to post into an existing thread, or set `thread_name` to create a new thread in a forum channel. Both are constructor arguments on the DiscordWebhook class.

How do I add an embed and control its layout?

Create a DiscordEmbed with a title, description and colour, use the setters for author, image, thumbnail, footer, timestamp and fields, add it with `webhook.add_embed(embed)`, and pass `inline=False` to add_embed_field when a field should occupy a whole line rather than sit side by side.

Does discord-webhook support async usage?

Yes. Install the async extra, which pulls in httpx, while the synchronous path uses requests. The package also requires Python 3.10 or newer and installs a discord_webhook console script.

Official sources

  1. Issues
  2. License: MIT
  3. lovvskillz/python-discord-webhook on GitHub
  4. README
  5. Releases

Was this useful?