Discord Webhook Custom Avatar and Username: Per-Message Overrides
Set a Discord webhook custom avatar and username per message with username and avatar_url: name rules, image requirements, why GIFs stay static, curl examples.
On this page
- How a webhook’s name and avatar work
- Send a message with a custom username and avatar
- Username rules
- Avatar image requirements
- Why GIF avatars stay static
- One webhook, many senders
- Change the default in Server Settings
- Edge cases worth knowing
- Common errors
- FAQ
- Can a Discord webhook use an animated GIF avatar?
- Are username and avatar_url saved after I send a message?
- Why does Discord reject my webhook username?
- Can I change the username of a message I already sent?
- Next steps
How a webhook’s name and avatar work
Every webhook has a default name and avatar. You set them in Discord when you create the webhook (Server Settings → Integrations → Webhooks), and that is what a message shows when your payload says nothing about who is sending it.
On top of that, every execute request can override both with two optional JSON fields:
username— the display name for this one messageavatar_url— a publichttps://image URL for this one message
That is the whole mechanism. There is no profile to register, no bot token, no extra permission. The override is applied when Discord renders the message and it lives with that message only. The next request that omits the fields falls back to the defaults from Server Settings.
Why it matters: a single webhook can look like a deploy bot at 10:00, an alerting system at 10:05 and a changelog feed at 10:10, and readers of the channel see three distinct “senders” without you creating three webhooks. If you have never created one, start with the setup guide and come back.
Send a message with a custom username and avatar
The minimal payload:
{
"content": "Build #482 passed on main ✅",
"username": "CI Pipeline",
"avatar_url": "https://example.com/avatars/ci.png"
}
With curl:
curl -H "Content-Type: application/json" \
-d '{
"content": "Build #482 passed on main ✅",
"username": "CI Pipeline",
"avatar_url": "https://example.com/avatars/ci.png"
}' \
https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN
A successful request returns 204 No Content. Add ?wait=true to the URL if you want the created message back as JSON: the author object in the response carries the overridden name, which is a quick way to confirm the override was accepted.
curl -s -H "Content-Type: application/json" \
-d '{"content": "ping", "username": "CI Pipeline"}' \
"https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN?wait=true"
Both fields are independent. Send only username and the message keeps the default avatar; send only avatar_url and it keeps the default name. They also work alongside everything else in the payload: embeds, files, polls, a thread_name for forum posts, Components V2.
Username rules
Discord validates username before it accepts the message:
- Maximum 80 characters. Longer names get a
400 Bad Request. - It may not contain “clyde” or “discord”. The check is case-insensitive and matches substrings, so
Discord Alerts,MyDiscordBotandCLYDEall fail with a 400 whose JSON body points at theusernamefield. - Plain text only. Markdown is not rendered inside names:
**Deploy**shows the asterisks literally. Unicode emoji work; custom emoji syntax like<:name:id>does not. - Mentions do not ping. A name is never parsed as message content, so mention syntax inside it notifies nobody.
Spaces, punctuation and non-Latin scripts are fine. Discord trims surrounding whitespace and applies its general naming restrictions on top, so if an unusual name is rejected, check the Discord developer docs. Pick a name that still reads well when Discord truncates it on a narrow mobile screen: short and specific beats long and descriptive.
The same rules apply to the webhook’s default name.
Avatar image requirements
avatar_url has fewer explicit rules than username, but more ways to silently not work:
- Public HTTPS URL. Discord’s servers fetch the image, so
localhost, private network addresses and anything behind a login or a signed token will not load. There is no way to pass credentials. - A direct link to an image file, not an HTML page that contains one. PNG, JPG and WebP are safe choices; a “share” link from a cloud drive is not.
- Square works best. Discord shows the avatar as a circle; a non-square image gets cropped, so keep the subject in the centre.
- Stable URL. Discord attachment links (
cdn.discordapp.com/attachments/...) carry expiring query parameters, so an avatar hosted that way can stop loading later. Host the file somewhere you control: your own domain, an object store, a GitHub raw URL.
Discord checks that avatar_url is a well-formed URL, but it does not check that an image is actually behind it. If the URL is unreachable or is not an image, you do not get an error: the message simply shows the webhook’s default avatar. That is the first thing to check when a custom avatar “does nothing”.
Why GIF avatars stay static
Animated avatars are a feature of user profiles, not of webhooks. When you pass a GIF as avatar_url, Discord takes a single frame and shows it as a still image. There is no flag, payload field or setting that changes this, so do not spend time converting formats. If motion matters, put the GIF in the message body instead, as an embed image or as an uploaded file, where it animates normally.
One webhook, many senders
Because the override travels with each request, one webhook is enough to represent every service that posts into a channel. Keep a small map of identities and fill in the fields before each send:
#!/bin/bash
WEBHOOK_URL="https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN"
send_as() {
local sender="$1" message="$2" name avatar
case "$sender" in
deploy) name="Deploy Bot"; avatar="https://example.com/avatars/deploy.png" ;;
alerts) name="Alerts"; avatar="https://example.com/avatars/alerts.png" ;;
release) name="Release Notes"; avatar="https://example.com/avatars/release.png" ;;
*) name="System"; avatar="https://example.com/avatars/system.png" ;;
esac
curl -s -H "Content-Type: application/json" \
-d "$(jq -n --arg c "$message" --arg u "$name" --arg a "$avatar" \
'{content: $c, username: $u, avatar_url: $a}')" \
"$WEBHOOK_URL"
}
send_as deploy "api v2.3.1 is live in production 🚀"
send_as alerts "🔴 Error rate above 5% on /checkout"
send_as release "v2.3.1 — fixes checkout timeout, adds CSV export"
jq builds the JSON, so quotes and newlines in the message cannot break the payload.
Two things to keep in mind with this pattern:
- Rate limits are per webhook, not per name. Five “senders” sharing one URL share one budget. If they post in bursts, you will hit
429and need to honourretry_after; see the rate limits guide for the practical numbers and backoff. - Discord still knows it is one webhook. Members cannot block or mute a single persona, the audit log shows one integration, and anyone with the URL can post under any name. If different teams own different senders, give each team its own webhook so a leaked URL only exposes one.
If you would rather not write JSON at all, the visual builder at discord-webhook.com lets you type a name, paste an avatar URL and see the rendered result before sending, then exports the same payload for your scripts.
Change the default in Server Settings
When one identity should apply to every message from a webhook, change the defaults instead of overriding each time:
- Open Server Settings → Integrations → Webhooks (or the channel’s settings → Integrations).
- Click the webhook to expand it.
- Change the Name and click the avatar to upload a new image.
- Click Save Changes.
The new defaults apply to messages sent from now on. Messages already in the channel keep the name and avatar they were posted with, and any request that still sends username or avatar_url keeps winning over the defaults. That is a common source of confusion: someone changes the name in settings, “it did not work”, and the reason is a script that hard-codes a different one.
The defaults can also be changed programmatically with a PATCH to the webhook URL itself, using a name field and an image-data avatar; check the Discord developer docs for the exact payload shape before relying on it.
Edge cases worth knowing
Overrides are not stored. Discord does not remember the last username you sent. Every request that wants a custom identity must include it again, which is why the map-of-senders pattern above keeps the values in your code rather than expecting Discord to hold them.
Edits cannot change the identity. The edit endpoint (PATCH .../messages/{message_id}) accepts content, embeds, components and attachments, but username and avatar_url are ignored there. A message keeps the sender it was created with; to “rename” it, delete and resend.
Forum and thread posts respect the override. When thread_name creates a forum post, the starter message shows the custom name and avatar. Posting into an existing thread with ?thread_id= works the same way.
The reply contains the resolved identity. With ?wait=true, author.username in the returned message is your override (or the default if you sent none). Useful for logging which persona a message went out as.
Bots are different. A bot’s name and avatar belong to the application; changing them per message is not possible. If you need per-message identity, webhooks are the right tool. The bot vs webhook comparison covers the rest of that trade-off.
Common errors
400 Bad Request mentioning username. Either the name is over 80 characters or it contains clyde or discord (any case, anywhere in the string). Rename and resend.
Custom avatar not showing, no error. The URL is not publicly reachable, returns an HTML page, or needs authentication. Open the URL in a private browser window: if it does not render the bare image, Discord cannot fetch it either.
Avatar worked yesterday and is blank today. The image URL expired or was moved, which is typical for Discord attachment links and temporary storage. Re-host on a stable URL.
GIF avatar is not animating. Expected behaviour, see above.
Changed the name in settings, messages still show the old one. The sending script includes username in the payload. Remove the field to use the default.
404 with {"message":"Unknown Webhook","code":10015}. The URL or token is wrong, or the webhook was deleted. Copy the URL again from Server Settings.
429 Too Many Requests. Too many messages through one webhook in a short window; multiple personas do not get separate limits. Wait retry_after seconds and retry.
For the full list of status codes and what each one means, see webhook errors.
FAQ
Can a Discord webhook use an animated GIF avatar?
No. Discord renders webhook avatars as still images; a GIF passed as avatar_url shows a single frame. Put the GIF in an embed or attach it as a file if you need animation.
Are username and avatar_url saved after I send a message?
No. Both fields apply only to the message they are sent with. Omit them on the next request and Discord uses the defaults from Server Settings.
Why does Discord reject my webhook username?
The name is over 80 characters or contains “clyde” or “discord” as a substring, in any case. Discord Status, discordbot and Clyde2 all fail; Server Status passes.
Can I change the username of a message I already sent?
No. The edit endpoint ignores username and avatar_url. Delete the message and send it again with the identity you want.
Next steps
Set up the names and avatars once, and every notification your webhook sends can look like it came from the right service. To try combinations without touching a terminal, open the free Discord Webhook Builder: set the username and avatar, preview the message, then export the JSON or code.
Related reading:
- Discord webhook setup guide — creating the webhook and finding its URL
- Edit and delete webhook messages — what you can and cannot change after sending
- Discord bot vs webhook — when per-message identity is not enough