Skip to main content
TRACK B·TEST AND RELEASE·Verified June 2026 · Lua 5.4 · ox_lib 3.x
Learning with an AI assistant?
Copies this lesson plus 2026 ground rules (no lua54 'yes', Cfx.re Portal, correct callback signatures) as a ready-to-paste mentor prompt.

Webhooks and privacy

Webhooks are how your server tells Discord what happened: a sale, a ban, an error. Done carelessly they leak the webhook URL, dump player data into a channel, or spam Discord until it rate-limits you. This lesson keeps logging useful and safe.

You'll learn
How to log to Discord webhooks without leaking secrets, spamming channels, or exposing player data.
Time
~20 minutes.
You need
The HTTP/webhooks lesson and a server you can configure.
The arc
Webhooks are secrets, send server-side only, decide what NOT to log, then rate-limit and redact.
BEFORE YOU START

Log safely

Send only from the server

The webhook URL never reaches a client.

All webhook calls happen in server scripts, with the URL read from a convar. A client never sees the URL and never triggers the send directly without a server check. PerformHttpRequest is a server native, so this code can only live in a server_script.

Read the URL from a convar, then post a Discord embed. Discord expects JSON, so set the Content-Type header and encode the body with json.encode.

code
local hook = GetConvar('logs_webhook', '')

local function sendLog(title, description)
    if hook == '' then return end -- no webhook configured, do nothing
    local body = json.encode({
        embeds = {
            {
                title = title,
                description = description,
                color = 3447003, -- a blue stripe down the embed
            }
        }
    })
    PerformHttpRequest(hook, function(status)
        if status ~= 200 and status ~= 204 then
            print(('[logs] webhook returned %s'):format(status))
        end
    end, 'POST', body, { ['Content-Type'] = 'application/json' })
end

Decide what NOT to log

No private or sensitive data goes to a channel.

Log events, not secrets. Never send license keys, passwords, full IPs, tokens, or anything that identifies a real person beyond what you actually need.

Do not use FiveM's GetHashKey as privacy protection. It is a fast 32-bit game hash, not a cryptographic hash, so it can collide and does not provide a meaningful irreversible pseudonym. Keep raw identifiers in your access-controlled database when they are genuinely required. Post an internal audit-row id or a short-lived server source to Discord, then let authorized staff look up the private record.

code
-- The private database row contains the durable identifier and evidence.
-- Discord receives only the internal case id and current server slot.
local caseId = 1842
local src = source
sendLog('Ban issued', ('Case #%d, current source %d'):format(caseId, src))

Rate-limit your sends

Discord does not throttle or block your webhook.

Discord rate-limits webhooks. Batch or throttle high-frequency events instead of firing one request per tick. A log that spams itself stops logging when Discord cuts it off.

Push lines onto a queue and flush them on a timer. One request every few seconds carries many events instead of one request per event.

code
local queue = {}

local function logEvent(line)
    queue[#queue + 1] = line
end

CreateThread(function()
    while true do
        Wait(3000) -- flush at most once every 3 seconds
        if #queue > 0 then
            local batch = table.concat(queue, '\n')
            queue = {}
            sendLog('Activity', batch)
        end
    end
end)

Separate channels by severity

Important logs are not buried in noise.

Send errors and security events to a private staff channel, routine activity to another. Mixing a critical ban alert into a firehose of chat logs means nobody sees it.

Keep reading the full lesson

Sign in to start, then unlock every step of this lesson and the full FiveM School with a membership.

Still ahead in this lesson
  • Common mistakes
  • What you can do now

The remainder of Webhooks and privacy is available to FiveM School members.