Discord Webhook Module

Why create this module?
There are lot of modules available for making discord webhook calls from Roblox but often they disregard the API limits for message lengths and request limits which lead to failed or dropped messages.

It was only recently that changes to the HTTPService allowed developers to have more control over how HTTP calls are made. The addition of RequestAsync now allows us to make more informed decisions based upon the API state.

Main features
Request throttling - Using the header data rate limits are handled accordingly.
Message queue - Lightweight queue that only runs when required.
Failed/Success callbacks - Allows for more control over messages (return false in the failed callback to resend).
Format Helper - A small utility module that provides markdown support.

Demo

API Overview

DiscordWebhook
https://discordapp.com/api/webhooks/[id]/[token]
webhook DiscordWebhook.new(string webhookId, string webhookToken)
PostHandler webhook:GetPostHandler()
Message webhook:NewMessage()
FormatHelper webhook:GetFromatHelper()

PostHandler
void PostHandler:QueueMessage() – mainly for internal use
void PostHandler:Resume()
void PostHandler:Pause()
void PostHandler:SuccessCallback(function successCallback)
void PostHandler:FailedCallback(function failedCallback)

Message
void Messahge:Send()
void Message:Append(string txt)
void Message:AppendLine(string txt)
void Message:SetUsername(string txt)
void Message:SetAvatarUrl(string url)
void Message:SetTTS(boolean isTTS)
string Message:GetUid()
int Message:CountEmbeds()
AllowedMention Message:GetAllowedMention()
Embed Message:NewEmbed()

AllowedMention
void AllowedMention:AddGlobalMention(string [roles, users, everyone])
void AllowedMention:AddUserId(int id)
void AllowedMention:AddRoleId(int id)

Embed
void Embed:SetTitle(string title)
void Embed:Append(string txt)
void Embed:AppendLine(string txt)
void Embed:SetURL(string url)
void Embed:SetTimestamp([number epoch])
void Embed:SetColor3(Color3 color3)
void Embed:AppendFooter(string txt)
void Embed:AppendFooterLine(string txt)
void Embed:SetFooterIconURL(string url)
void Embed:SetImageURL(string url)
void Embed:SetThumbnailIconURL(string url)
void Embed:SetAuthorName(string name)
void Embed:SetAuthorURL(string url)
void Embed:SetAuthorIconURL(string url)
int Embed:CountFields()
Field Embed:NewField()

Field
void Field:SetName(string name)
void Field:Append(string txt)
void Field:AppendLine(string txt)
void Field:SetIsInline(boolean isInline)

FormatHelper
string FormatHelper:Italic(string txt)
string FormatHelper:Bold(string txt)
string FormatHelper:BoldItalic(string txt)
string FormatHelper:Underline(string txt)
string FormatHelper:UnderlineItalic(string txt)
string FormatHelper:UnderlineBold(string txt)
string FormatHelper:UnderlineBoldItalic(string txt)
string FormatHelper:Strikethrough(string txt)
string FormatHelper:CodeblockLine(string txt)
string FormatHelper:Codeblock(string txt, [string syntax])
string FormatHelper:BlockQuote(string txt)
string FormatHelper:MultiLineBlockQuote(string txt)
string FormatHelper:Spoiler(string txt)
string FormatHelper:URL(string url, [string txt])

Test Script
local discordWebHook = require(script.DiscordWebhook)

--https://discordapp.com/api/webhooks/[id]/[token]
local hook = discordWebHook.new('[id]', '[token]')
local formatHelper = hook:GetFromatHelper()

local avatarUrl = game:GetService('Players'):GetUserThumbnailAsync(1540993, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size420x420)

-- basic message
local msg = hook:NewMessage()
msg:Append('test')
msg:AppendLine('1')
msg:Append('line2')
msg:SetUsername('Test script')
msg:SetAvatarUrl(avatarUrl)
msg:SetTTS(false)
msg:Send()

-- message with markdown
local msg = hook:NewMessage()
msg:AppendLine(formatHelper:UnderlineBold('Test script'))
msg:AppendLine(formatHelper:Spoiler('Sent from Roblox'))
msg:AppendLine(formatHelper:Codeblock([[
print('Hello world!')

local a = true
if a then
	print(a)
end
]], 'Lua'))
msg:SetUsername('Test script')
msg:SetAvatarUrl(avatarUrl)
msg:Send()


-- basic message with mention
local msg = hook:NewMessage()
local allowedMention = msg:GetAllowedMention()
allowedMention:AddGlobalMention('everyone')
msg:SetAvatarUrl(avatarUrl)
msg:Append('test')
msg:AppendLine('1')
msg:Append('line2 @everyone')
msg:SetUsername('Test script')
msg:SetAvatarUrl(avatarUrl)
msg:SetTTS(false)
msg:Send()

-- basic embed
local msg = hook:NewMessage()
msg:SetAvatarUrl(avatarUrl)
local embed = msg:NewEmbed()
embed:SetTitle('basic embed')
embed:Append('test')
embed:AppendLine('1')
embed:Append('line2')
msg:Send()

-- multiple embeds
local msg = hook:NewMessage()
msg:SetAvatarUrl(avatarUrl)
local embed1 = msg:NewEmbed()
embed1:SetTitle('Embed 1')
embed1:Append('embed 1 body')
embed1:SetColor3(Color3.fromRGB(0, 255, 0))
local embed2 = msg:NewEmbed()
embed2:SetTitle('Embed 2')
embed2:Append('embed 2 body')
embed2:SetColor3(Color3.fromRGB(0, 0, 255))
msg:Send()

-- complex embed with fields
local msg = hook:NewMessage()
msg:SetAvatarUrl(avatarUrl)
local embed = msg:NewEmbed()
embed:SetTitle('complex embed')
embed:Append('embed body')
embed:SetURL('https://www.roblox.com/users/1540993/profile')
embed:SetTimestamp(tick() - 86400) -- -1 day
embed:SetColor3(Color3.fromRGB(255, 0, 0))
embed:AppendFooter('embed footer')
embed:SetFooterIconURL(avatarUrl)
embed:SetImageURL(avatarUrl)
embed:SetThumbnailIconURL(avatarUrl)
embed:SetAuthorName('kingdom5')
embed:SetAuthorURL('https://www.roblox.com/users/1540993/profile')
embed:SetAuthorIconURL(avatarUrl)
local field1 = embed:NewField()
field1:SetName('field 1')
field1:Append('body1')
local field2 = embed:NewField()
field2:SetName('field 2')
field2:Append('body2')
local field3 = embed:NewField()
field3:SetName('field 3')
field3:Append('body3')
field3:SetIsInline(true)
local field4 = embed:NewField()
field4:SetName('field 4')
field4:Append('body4')
field4:SetIsInline(true)
msg:Send()

-- message spam test
for i=1, 10 do
	local msg = hook:NewMessage()
	msg:SetAvatarUrl(avatarUrl)
	msg:Append('message spam test ' .. i)
	msg:Send()
end

Results:-
Discord_XafVtevGRo Discord_wuAlLLY0nl Discord_XafVtevGRo

47 Likes

I see good use of this in feedback UI’s where the users feedback on a game can be sent straight into a channel.

1 Like

This is a cool idea but didn’t Discord blacklist Roblox webhooks?

2 Likes

Discord blocked out requests originating from Roblox at first, due to the abuse of the API, and at that time Roblox did not offer a way for us to see the headers returned, which was where Discord provided a timeout header-ish.

After Roblox added better http request support, Discord has since unblocked requests coming from Roblox again.

9 Likes

I just created a post about Roblox - Discord webhooks and it’s in approval :rofl:

But yours is way better than mine :smiley:

I would definitely use yours instead of mine :smirk:

Anyways, thanks for sharing this with the community! :partying_face:

2 Likes

Provided that the message being sent includes its own __tostring metamethod you should be able to add messages directly the PostHandler.

This is so that it can be integrated into existing system without the need to rewrite large amounts of code.

1 Like

Using Discord-Roblox webhooks can be quite dangerous, depending on what you send using those webhooks. I strongly recomend using a discord bot as discord do not really like you sending too much data or information to them, if you do use this in a abusive way you can get your server deleted and possibly your account.

1 Like

All imputs are checked so that messages, embeds and field conform to API limits. If there are any checks missing please let me know and I will update the module accordingly.

Now that webhooks can essentially escape @ (see AllowedMention object) I feel that there are safer than before. All content posted under the http service should be scrutinize regardless of the endpoint being used.

The goal of this module is to conform to API specs.

1 Like

The webhooks aren’t sending for me, (nothing happens and I’m pretty sure the format is correct), any idea why?

Oh hey I know this guy. Well done KD :slight_smile:

Here is this webhook in a live game sending sales logs to our server.

3 Likes

The def error handler will print out faild messages. Double check that api credentials and that you have enabled http calls in your game.

1 Like

Thank you, I have bookmarked this page as I will be sure to use this

The test script is for debugging/testing and used as a general example. It is disabled by default and requires your webhook credentials to run.

I am unsure why you are running this test script in “pending-applications”. I would use a test channel then change the webhook credentials afte you have the message in the format you desire.

I’ve really been enjoying this webhook and generally it’s very nice to use. But I have an issue with mentioning specific roles. Here is a snippet of what I am using… do you think you can help?

local Message = Webhook:NewMessage()
local Mentions = Message:GetAllowedMention()

Mentions:AddGlobalMention("roles")
--Mentions:AddRoleId(774417755259928577) --No matter whether I use this or the add global mention, they both don't work
Message:Append("@On Duty")

Message:Send()

Go into your discord and type in @On Duty and then before the @ put a , press enter then make sure your Append is what you just sent.

That’s not correct. The solution is “<@roleidhere>”. Mentioning roles is the same as mentioning a user, it requires the ID.

1 Like

There is two ways you can mention the role then put \ before you click enter and it sends the same result.

1 Like

That’s…not how discord works? You do realize the mechanics behind mentioning isn’t the same as sending text right?

1 Like

No, you just use <@&[ROLE-ID]>

![https://gyazo.com/31ea4e724c35316213db2d7a01a6bb21.gif]