Start typing to search packages!
rCord
A Roblox Luau library for sending Discord webhook messages. Provides a chainable builder API for constructing messages and embeds, built-in validation against Discord's limits, and automatic rate limit handling.
Features
- Chainable message and embed builders
- Full embed support (fields, images, thumbnails, author, footer, etc.)
- Validation against all Discord character/count limits before sending
- Automatic retry on rate limit (429) responses
Secrettype support for safe URL storageallowed_mentionscontrol to prevent accidental pings
Proxy Requirement
Discord blocks incoming requests from Roblox's servers, so you cannot call the Discord webhook API directly. You must route requests through a proxy server that forwards them to Discord on your behalf.
Set your webhook URL to your proxy's endpoint instead of the Discord URL:
-- Proxy URL — use this instead
local webhook = rCord.Webhook.new("https://your-proxy.example.com/api/webhooks/1234/abcd")
The proxy should forward the request to Discord verbatim, preserving the method, headers, and body. A common self-hosted option is discord-proxy by lewisakura. For a free setup using Cloudflare Workers, see this DevForum tutorial.
Quick Start
local rCord = require(path.to.rCord)
local webhook = rCord.Webhook.new("https://your-proxy.example.com/api/webhooks/...")
-- Simple message
webhook:send("Hello from Roblox!")
-- Message with an embed
local embed = rCord.Embed.new()
:setTitle("Round Over")
:setDescription("The round has ended.")
:setColor(Color3.fromRGB(88, 101, 242))
local message = rCord.Message.new()
:setUsername("Game Bot")
:addEmbed(embed)
local success, response = webhook:send(message)
if not success then
warn(response.error)
end
API Reference
rCord
The top-level table returned by the module.
| Export | Type | Description |
|---|---|---|
Webhook | class | Creates and sends webhook messages |
Message | class | Builds a message payload |
Embed | class | Builds an embed object |
Flags | table | Named constants for message flags |
setDebug(value) | function | Enables/disables debug logging to output |
rCord.Flags
| Constant | Value | Description |
|---|---|---|
SUPPRESS_EMBEDS | 4 | Prevents Discord from generating link previews |
SUPPRESS_NOTIFICATIONS | 4096 | Sends the message silently (no ping sound) |
Webhook
Webhook.new(url: string | Secret) -> Webhook
Creates a new webhook instance.
-- Plain string
local webhook = rCord.Webhook.new("https://your-proxy.example.com/api/webhooks/...")
-- Roblox Secret (recommended — keeps URL out of output)
local webhook = rCord.Webhook.new(HttpService:GetSecret("DISCORD_WEBHOOK"))
webhook:send(body, wait?, thread_id?) -> (boolean, ResponseData)
Sends a message. body can be a plain string or a Message object.
Automatically retries once if Discord responds with a 429 rate limit, waiting the duration Discord specifies.
| Parameter | Type | Description |
|---|---|---|
body | string | Message | The message to send. A plain string is wrapped in a Message automatically. |
wait | boolean? | If true, Discord waits for the message to be created before responding. Required if you need the message ID back. Defaults to false. |
thread_id | string? | Sends the message into a specific thread inside the webhook's channel. |
local success, response = webhook:send("Hello!", true)
if success then
print(response.statusCode) -- 200
print(response.body.id) -- message ID (body is auto-decoded from JSON)
else
warn(response.error)
end
Message
Builds the payload sent to Discord. All setters return self for chaining.
Message.new() -> Message
local message = rCord.Message.new()
Setters
| Method | Parameter | Discord limit | Description |
|---|---|---|---|
:setContent(content) | string | 2000 chars | The text content of the message |
:setUsername(username) | string | — | Overrides the webhook's display name |
:setAvatarUrl(url) | string | — | Overrides the webhook's avatar |
:setTTS(tts) | boolean | — | Sends as a text-to-speech message |
:setThreadName(name) | string | — | Creates a new thread with this name (forum/media channels) |
:setAllowedMentions(body) | AllowedMentions | — | Controls which mentions actually ping |
:setFlags(flags) | number | — | Bitfield of message flags (use rCord.Flags) |
:addEmbed(embed) | Embed | EmbedType | 10 embeds | Adds an embed to the message |
message:validate() -> (boolean, string?)
Validates the message against Discord's limits. Called automatically by send — you only need this if you want to check before sending.
local ok, err = message:validate()
if not ok then
warn(err) -- e.g. "over 2000 characters"
end
message:toJSON() -> table
Returns a plain table representation suitable for JSON encoding. Called internally by send.
AllowedMentions type
webhook:send(
rCord.Message.new()
:setContent("Hey @everyone!")
:setAllowedMentions({ parse = {} }) -- parse = {} suppresses all pings
)
| Field | Type | Description |
|---|---|---|
parse | {"roles" | "users" | "everyone"}? | Which mention types to parse. Omit a type to suppress it. |
roles | {string}? | Allowlist of role IDs to ping (max 100) |
users | {string}? | Allowlist of user IDs to ping (max 100) |
replied_user | boolean? | Whether to ping the user being replied to |
Embed
Builds a Discord embed. All setters return self for chaining.
Embed.new() -> Embed
local embed = rCord.Embed.new()
Setters
| Method | Parameter | Discord limit | Description |
|---|---|---|---|
:setTitle(title) | string | 256 chars | Bold title at the top of the embed |
:setDescription(description) | string | 4096 chars | Main body text |
:setUrl(url) | string | — | Makes the title a hyperlink |
:setTimestamp(timestamp) | string | — | ISO 8601 timestamp shown in the footer |
:setColor(color) | number | Color3 | — | Left-side accent colour. Accepts a Color3 or a decimal integer |
:setFooter(body) | EmbedFooter | 2048 chars (text) | Footer text and optional icon |
:setImage(body) | EmbedImage | — | Large image at the bottom |
:setThumbnail(body) | EmbedThumbnail | — | Small image on the right |
:setAuthor(body) | EmbedAuthor | 256 chars (name) | Author line at the top |
:setProvider(body) | EmbedProvider | — | Provider info (usually set by Discord, not bots) |
:setType(type) | "rich" | "image" | ... | — | Embed type. Use "rich" for custom embeds |
:addField(body) | EmbedField | 25 fields, 256/1024 chars | Adds a name/value field |
:addField(body: EmbedField)
embed:addField({
name = "Score",
value = "1500",
inline = true,
})
| Field | Type | Limit | Description |
|---|---|---|---|
name | string | 256 chars | Field label |
value | string | 1024 chars | Field content |
inline | boolean? | — | Whether to display side-by-side with adjacent inline fields |
:setColor(color: number | Color3)
Both forms are accepted:
embed:setColor(0x5865F2) -- hex integer
embed:setColor(Color3.fromRGB(88, 101, 242)) -- Color3
:setFooter(body: EmbedFooter)
embed:setFooter({ text = "rCord", icon_url = "https://..." })
:setAuthor(body: EmbedAuthor)
embed:setAuthor({ name = "PlayerName", icon_url = "https://...", url = "https://..." })
:setImage(body: EmbedImage) / :setThumbnail(body: EmbedThumbnail)
embed:setImage({ url = "https://..." })
embed:setThumbnail({ url = "https://...", width = 64, height = 64 })
embed:validate() -> (boolean, string?)
Validates all fields against Discord's character limits. Called automatically via message:validate().
embed:getCharacters() -> number
Returns the total character count of the embed (title + description + footer text + author name + all field names and values). Discord's combined limit across all embeds in a message is 6000.
ResponseData
Returned as the second value from webhook:send().
| Field | Type | Description |
|---|---|---|
success | boolean | Whether Discord accepted the message (2xx status) |
statusCode | number | HTTP status code |
statusMessage | string | HTTP status message |
body | any? | Decoded response body. Contains the created message object as a table when wait = true. nil when Discord returns no content (e.g. wait = false). Falls back to the raw string if the body is not valid JSON. |
retry_after | number? | Seconds Discord asked to wait (present on 429 responses) |
error | string? | Error description when the request or validation failed |
Examples
Logging a player event
local rCord = require(path.to.rCord)
local webhook = rCord.Webhook.new(HttpService:GetSecret("LOG_WEBHOOK"))
local function logPlayerJoin(player)
local embed = rCord.Embed.new()
:setAuthor({ name = player.Name })
:setTitle("Player Joined")
:setColor(Color3.fromRGB(87, 242, 135))
:setTimestamp(DateTime.now():ToIsoDate())
webhook:send(rCord.Message.new():addEmbed(embed))
end
Silent notification with suppressed pings
local message = rCord.Message.new()
:setContent("Server restarting in 60 seconds.")
:setAllowedMentions({ parse = {} })
:setFlags(rCord.Flags.SUPPRESS_NOTIFICATIONS)
webhook:send(message)
Multiple inline fields
local embed = rCord.Embed.new()
:setTitle("Match Results")
:addField({ name = "Winner", value = "TeamA", inline = true })
:addField({ name = "Score", value = "5 - 2", inline = true })
:addField({ name = "Duration", value = "12m 34s", inline = true })
:setColor(0xFEE75C)
webhook:send(rCord.Message.new():addEmbed(embed))
Checking the response
local success, response = webhook:send("Test", true)
if not success then
warn("[rCord]", response.error, response.statusCode)
return
end
-- body is already decoded — no JSONDecode needed
print("Message ID:", response.body.id)
Package Details
Install command (Click to copy)
Version
0.1.4
License
MIT
Safe for commercial use
Automated license review — not legal advice.
