Send Discord Webhooks from Roblox (HttpService + Proxy)
How to send Discord webhook messages from a Roblox game using HttpService. Covers the Roblox IP block, webhook proxies, embeds, and rate limits with Lua examples.
🇷🇺 Also available in Русский
Why Send Discord Webhooks from Roblox?
Roblox developers use Discord webhooks to log in-game events to a Discord channel: player joins, purchases, admin commands, anti-cheat flags, error reports, and game analytics. A webhook turns your Discord server into a real-time dashboard for your game without building any backend.
This guide shows how to send messages from a Roblox Script using HttpService, why direct requests fail on live servers, and how to fix that with a webhook proxy. All examples are plain Lua you can paste into a server-side script.
Step 1: Enable HTTP Requests
Roblox blocks outbound HTTP by default. Turn it on once per game:
- Open your game in Roblox Studio
- Go to Home → Game Settings → Security
- Enable Allow HTTP Requests
- Save and publish
Without this, every request throws Http requests are not enabled.
Step 2: Get a Discord Webhook URL
In Discord: Server Settings → Integrations → Webhooks → New Webhook, pick a channel, then Copy Webhook URL. It looks like:
https://discord.com/api/webhooks/123456789012345678/AbCdEf-gHiJkLmNoPqRsTuVwXyZ
If you are new to webhooks, read the Discord webhook setup guide first.
Security warning: A webhook URL is a secret. Anyone who has it can post to your channel. Only ever use it in server-side
Scriptobjects (underServerScriptService), never in aLocalScript, or exploiters will steal it. See webhook security.
Step 3: The Roblox IP Block (Important)
If you send a request straight to discord.com from a published game, you will get an error like Http 403 (Forbidden). Discord blocks the datacenter IP ranges that Roblox servers run on to stop abuse.
- In Studio, requests use your home IP, so direct calls work — this fools many developers into thinking their code is fine.
- On live servers, the same code fails.
The fix is a webhook proxy: a small service that receives your request and forwards it to Discord from an allowed IP.
Step 4a: Direct Request (Studio Testing Only)
This works in Studio so you can verify your payload, but expect it to fail in production:
local HttpService = game:GetService("HttpService")
local WEBHOOK_URL = "https://discord.com/api/webhooks/ID/TOKEN"
local data = {
content = "Hello from Roblox!",
username = "Game Logger",
}
local success, err = pcall(function()
HttpService:PostAsync(WEBHOOK_URL, HttpService:JSONEncode(data))
end)
if not success then
warn("Webhook failed: " .. tostring(err))
end
JSONEncode turns the Lua table into the JSON body Discord expects, and pcall stops a failed request from crashing your script.
Step 4b: Using a Webhook Proxy (Production)
A proxy accepts your message and relays it to Discord from a non-blocked IP. You can self-host one, use a public proxy, or use the discord-webhook.com relay endpoint, which forwards your payload server-side:
local HttpService = game:GetService("HttpService")
-- Your real Discord webhook URL stays on the server only
local WEBHOOK_URL = "https://discord.com/api/webhooks/ID/TOKEN"
local PROXY_URL = "https://discord-webhook.com/api/webhook/send"
local function sendToDiscord(payload)
local body = HttpService:JSONEncode({
webhookUrl = WEBHOOK_URL,
payload = payload,
})
local success, result = pcall(function()
return HttpService:PostAsync(
PROXY_URL,
body,
Enum.HttpContentType.ApplicationJson
)
end)
if not success then
warn("Discord proxy failed: " .. tostring(result))
end
return success
end
sendToDiscord({
content = "A player just joined the game.",
username = "Game Logger",
})
The relay expects a JSON object with two keys: webhookUrl (your Discord webhook) and payload (a normal Discord message object — content, embeds, username, avatar_url, and so on). It validates the URL, forwards the message, and returns { "success": true, "messageId": "..." }.
Why a proxy and not a public one? Some public Roblox proxies go offline without notice or log your payloads. Whichever proxy you choose, treat the proxy URL as a relay only and keep the real webhook URL on the server.
Step 5: Send a Rich Embed
Plain text is fine for quick logs, but embeds give you titles, colors, fields, and thumbnails. Build the embed as a nested Lua table:
local function logPlayerJoin(player)
sendToDiscord({
embeds = {
{
title = "Player Joined",
color = 5763719, -- green, decimal form of #57F287
fields = {
{ name = "Username", value = player.Name, inline = true },
{ name = "User ID", value = tostring(player.UserId), inline = true },
{ name = "Account Age", value = player.AccountAge .. " days", inline = true },
},
thumbnail = {
url = "https://www.roblox.com/headshot-thumbnail/image?userId="
.. player.UserId .. "&width=420&height=420&format=png",
},
footer = { text = "Live game log" },
},
},
})
end
game.Players.PlayerAdded:Connect(logPlayerJoin)
Discord embed colors are decimal integers, not hex strings — convert #57F287 to 5763719. The embed color reference lists the common decimal values, or design the whole embed visually in the Discord Webhook Builder and copy the field values into your Lua table.
Step 6: Respect Rate Limits
Two separate limits apply:
- Discord: about 30 requests per minute, per webhook. Exceed it and Discord returns
429 Too Many Robotswith aRetry-Aftervalue. See rate limits explained. - Roblox:
HttpServiceallows roughly 500 requests per minute, per game server, across all endpoints.
For a busy game, batch events instead of sending one webhook per action. Queue messages and flush them on a timer:
local queue = {}
local function enqueue(line)
table.insert(queue, line)
end
task.spawn(function()
while true do
task.wait(10) -- flush every 10 seconds
if #queue > 0 then
local batch = table.concat(queue, "\n")
queue = {}
sendToDiscord({ content = string.sub(batch, 1, 2000) })
end
end
end)
Discord caps content at 2000 characters, so the string.sub guard keeps a large batch from being rejected with a 400 error.
Common Use Cases
- Purchase logs — fire a webhook inside
ProcessReceiptto record Robux spending. - Admin actions — log bans, kicks, and commands with the moderator’s name.
- Anti-cheat alerts — send a red embed when your detection flags a player.
- Error reporting — wrap risky code in
pcalland forward the error text. - Live player count — flush a status embed on a timer so staff can watch concurrency.
Troubleshooting
| Symptom | Cause & Fix |
|---|---|
Http requests are not enabled | Turn on Allow HTTP Requests in Game Settings → Security. |
| Works in Studio, fails when published | Roblox IP block — switch to a proxy (Step 4b). |
HTTP 400 | Invalid JSON or content over 2000 chars / embed limits. See error guide. |
HTTP 401 / 404 | Webhook deleted or token regenerated — copy a fresh URL. |
HTTP 429 | Rate limited — batch messages and slow down. |
Next Steps
You can now log any in-game event to Discord from Roblox. Design your embeds visually with the free Discord Webhook Builder, then copy the structure into your Lua tables. For more patterns, see webhook automation and notifications and the embed formatting guide.
Related Articles
- Discord Webhook Setup — Complete Guide in 2 Minutes — Create your first webhook URL
- Discord Webhook Security — Token Leaks, Rotation & Hardening — Keep your webhook URL safe from exploiters
- Discord Webhook Rate Limits Explained — Handle 429s and batching
- Complete Guide to Discord Embed Formatting — Build rich embeds
Try it in our tool
Open Discord Webhook Builder