Blog
FiveM Discord Webhook Security: Leaked Tokens, Forged Logs and Locking Down Integrations
FiveM Discord Webhook Security: Leaked Tokens, Forged Logs and Locking Down Integrations
That long https://discord.com/api/webhooks/… string you pasted into a config file six months ago is a password. Not “like” a password — it is one. FiveM Discord webhook security comes down to a single uncomfortable fact: anyone holding that URL can post to your channel, delete your logs, or frame a player for something they never did, and Discord accepts every request because the token buried in the URL is the only proof of identity it asks for.
Recommended FiveM scripts for your server
A webhook URL is a bearer credential, not a config value
A Discord webhook URL is a bearer credential — the token after /webhooks/{id}/ is the entire authentication. There is no API key, no signature, no IP allowlist. Anyone who sends an HTTP POST to that URL can publish messages, embeds and @everyone pings as your logging bot, or delete the webhook outright. Treat it like a database password.
Here’s the distinction people miss. A bot token pairs with an application: you can reset it from the Developer Portal, scope its permissions, and gate it behind intents. A webhook is deliberately dumb by design — it exists so a printer or a CI job can drop a message without OAuth. That simplicity is exactly why it became the default for FiveM logging, and exactly why it hurts when it lands somewhere readable.
How FiveM webhooks leak — client-side is the worst offender
The FiveM-specific killer that generic guides skip: client resources. Every file that runs on the client is downloaded to every player who connects. It sits in their FiveM game cache, and unpacking that cache is a two-minute job with tools that get passed around leak forums daily. If a client script calls PerformHttpRequest to a Discord webhook — or even just stores the URL in a client-side Lua or NUI file — you have published that credential to your entire playerbase, including the one person who joined specifically to grief you.
The other common leak paths are less exotic but just as effective:
- Config sharing. Someone asks for help in a support server and pastes their
server.cfgor a whole resource into a channel or a paste site, webhook included. - Git history. You moved the URL into a convar last month, but it’s still sitting in commit three of a public repo. A single
git log -pfinds it. - Screenshare and stream. A webhook printed to the F8 console or the txAdmin live console while you debug on a stream is now on a VOD forever.
- Crash dumps and logs. A failed
PerformHttpRequestoften prints the full URL into your server console, which flows into log files that staff, your host, and third-party support techs can all read.
What an attacker actually does with it
Spam is the boring outcome. A leaked URL lets someone do real damage:
- Ping fatigue. Blast @everyone and @here until your community mutes the server or leaves.
- Forged log entries. This is the nasty one. Your admin-actions channel is fed by a webhook, so an attacker can POST a perfectly formatted embed reading “Admin John banned Steve for cheating” — with your bot’s name and avatar. Now you have a fabricated audit trail, and if you act on it, you’ve banned an innocent player on evidence the attacker wrote.
- Log deletion. A DELETE request to the same URL removes the webhook. Your logging goes dark, and the gap in your records is precisely the window someone wanted to hide.
- Impersonation. Webhook messages can override username and avatar per request, so a leaked “transactions” webhook becomes a convincing fake-payment or fake-staff channel used to social-engineer your players.
Log server-side, never from the client
The rule that prevents most of this is short: webhooks fire only from server-side scripts. A client should never know a webhook URL exists.
The pattern is standard server-authoritative design. The client detects an event — a menu opened, money spent — and calls TriggerServerEvent. The server validates the payload, then makes the PerformHttpRequest to Discord. Store the URL in a server-only file or a convar set with set, never sets: the sets variant replicates the value to every connected client, which is how people leak convars without ever realising it. The same instinct that keeps money and inventory safe applies here — our notes on defensive Lua patterns spell out the wider version: if the client can trigger it, validate it server-side; if the client can read it, it’s already leaked.
Rate limits: why your logs vanish without an error
Discord rate-limits webhooks at roughly 30 requests per minute per webhook, with a tighter burst ceiling of about five every two seconds. Cross it and Discord returns HTTP 429 with a retry_after value. If your logging resource fires and forgets with no 429 handling, those messages are simply gone — no Lua error, no console warning, just missing log lines. On a busy server that logs every gunshot or every /me, you can lose exactly the entries you’ll want after an incident.
- Read the HTTP status code in your
PerformHttpRequestcallback. On 429, respectretry_afterand requeue the message instead of dropping it. - Batch low-priority events. One embed with ten fields beats ten separate embeds and burns a fraction of the budget.
- Split channels across separate webhooks. Each URL gets its own rate-limit bucket, so a chatty combat log can’t starve your admin-actions webhook.
Graduate from raw webhooks to a bot or logging proxy
Raw webhooks are fine for a small server. Once logging genuinely matters, move to something with real authentication:
- A bot with a token. The token lives server-side, resets instantly from the Developer Portal, and can be scoped and gated behind intents. It also reads and moderates, not just writes.
- A logging proxy. Your FiveM server POSTs to your own endpoint with an API key; the service holds the real webhook, centralises rate-limit handling, and lets you rotate the downstream webhook without touching every server. If the FiveM box is ever popped, the attacker walks away with an API key you can revoke in seconds, not your Discord credential.
The honest trade-off: a bot or proxy is more moving parts and another service to keep online. For a two-channel hobby server, a well-guarded webhook set with set is plenty. For anything carrying staff actions, payments, or a reputation worth attacking, the proxy earns its keep the first time someone leaks a config.
Audit purchased scripts for hardcoded exfil webhooks
Not every leaked webhook is one you created — some scripts phone home to a channel you don’t own. Before you trust a resource, grep it:
grep -rniE "discord(app)?.com/api/webhooks" resources/your-script/
For open-source or non-escrow scripts you can read every line, so extend the search to PerformHttpRequest, io.popen, and any loadstring or base64 blobs that could hide a URL. A webhook you never configured, pointing at a channel you don’t control, is exfiltration — player identifiers, your admin list, or your own configured secrets quietly shipped to a stranger.
Escrow-encrypted scripts are the hard case: the FiveM asset escrow system encrypts the logic, so you can’t grep what you can’t read. You’re trusting the seller. That’s an argument for buying from vendors who publish real update notes and stake a reputation on it — the reasoning behind escrow and script compliance — and for stocking your server from vetted FiveM scripts rather than random leak drops. It’s also an argument for watching outbound traffic: if an escrowed resource opens a connection to a Discord webhook you never set up, your firewall or egress logs show it even when the source code won’t.
Watch the Discord side too: audit log and integrations
Discord’s own Server Settings → Audit Log records webhook create, update, and delete events, each with the user who did it and a timestamp. Read it after any incident, and skim it periodically:
- An unexpected Webhook Create means someone — or a compromised bot — added a channel feed you didn’t authorise.
- A Webhook Delete right before a suspiciously quiet stretch is a red flag worth chasing.
- Under Integrations, review which bots hold Manage Webhooks. That single permission lets a bot enumerate and rewrite every webhook in the server, so one compromised utility bot can dump the lot.
Rotating a compromised webhook
Rotation is deliberately painless, which is the one genuinely nice thing about webhooks. In the channel’s Integrations → Webhooks, either delete the webhook — which kills the old URL the instant you confirm — or copy the URL of a freshly created one. The old URL 404s immediately. Then update the convar on your server and reload the resource. Because you stored it in exactly one place with set (right?), that’s a one-line change instead of a hunt through six files.
Incident checklist: a webhook you think is burned
- Delete the webhook in Discord first. This revokes the credential before you do anything else.
- Create a replacement, update your server convar, and restart or reload the logging resource.
- Open the Audit Log and note any Webhook Create, Update, or Delete you didn’t perform, along with who did them.
- Treat recent log entries as untrustworthy. Cross-check any admin action from the exposure window against a second source — your database, the txAdmin action log — before you act on it.
- Find the leak source: grep client resources and git history for the old URL, and review who had
server.cfgaccess. - If it leaked through a script, pull the script and rotate anything else that resource could read.
- Rotate adjacent secrets. If one webhook leaked from your config, treat every value near it — API keys, database strings — as suspect.
- Tell your staff. A forged log entry only works when people trust the channel blindly, so a heads-up is a real control.
Webhooks earned their place in every FiveM logging stack because they take thirty seconds to set up. That’s also the trap: a credential this easy to paste is easy to leak, and the fallout — silently dropped logs, fabricated bans, a deleted audit trail — lands right where you can least afford it. Keep webhooks server-side, rotate on any doubt, audit what your scripts ship out, and graduate to a proxy before your community gets big enough to be worth attacking. For the server-ops half of the same hygiene — convar handling, secrets in server.cfg, resource review — our writeups on locking down server configuration pair cleanly with everything above.