PiTyUs.Hire me

Engineering

Writing server events that cannot be abused

Secure your FiveM net events: why any client can trigger any RegisterNetEvent, capturing source, validating types, ranges, distance, jobs and ownership, ACE checks, never trusting amounts or prices from the client, internal events with AddEventHandler, rate limits and logging.

Updated 12 min readBy PiTyUs · FiveM developer

Overview

Cheat menus ship with lists of event names from popular scripts, and one unprotected TriggerServerEvent('job:pay', 999999) is enough to wreck a server’s economy. The fix is not hiding event names; it is writing every server event as if the person calling it is hostile — because sometimes they are.

The classic exploit

server.lua — DO NOT do thislua
RegisterNetEvent('fishing:sell', function(amount)
    local xPlayer = ESX.GetPlayerFromId(source)
    xPlayer.addMoney(amount * 50)
end)

The client decides amount. A cheater calls TriggerServerEvent('fishing:sell', 100000) and gets five million.

The same event, secured

server.lualua
local SELL_POINT = vec3(-1845.0, -1195.0, 14.3)
local PRICE = 50
local last = {}

RegisterNetEvent('fishing:sell', function()
    local src = source
    local now = os.time()
    if last[src] and now - last[src] < 5 then return end
    last[src] = now

    if #(GetEntityCoords(GetPlayerPed(src)) - SELL_POINT) > 5.0 then
        print(('[fishing] %s tried to sell away from the market'):format(GetPlayerName(src)))
        return
    end

    local count = exports.ox_inventory:GetItemCount(src, 'fish')
    if count < 1 then return end
    if exports.ox_inventory:RemoveItem(src, 'fish', count) then
        exports.ox_inventory:AddItem(src, 'money', count * PRICE)
    end
end)

AddEventHandler('playerDropped', function()
    last[source] = nil
end)

The client now sends nothing. The server counts the fish it knows about, removes them, and pays a price only it knows.

The validation checklist

CheckHow
Typestype(x) == 'number', math.type(x) == 'integer', string length limits
RangesClamp or reject: if amount < 1 or amount > 10 then return end
Location#(GetEntityCoords(GetPlayerPed(src)) - point) <= radius
Job / groupRead the job from your framework on the server
PermissionIsPlayerAceAllowed(src, 'myres.admin')
OwnershipDoes this player own this vehicle / stash / house?
ItemsRemove the item first; only reward if removal succeeded
CooldownPer-player timestamps, cleared on playerDropped
Entity IDsDoesEntityExist(NetworkGetEntityFromNetworkId(netId)) and a relation to the player

Why `local src = source` matters

source is a global that FiveM sets for the event currently running. If your handler waits (a database query with MySQL.query.await, a Wait), another event can run and change it. Copy it into a local on the first line and use only the local.

Internal events and callbacks

  • Events that only server scripts trigger: AddEventHandler('myres:internal', ...) without RegisterNetEvent — clients cannot call them.
  • Better still, use exports for server-to-server calls — see exports.
  • For client requests that need an answer, use a callback (lib.callback) and validate inside it exactly like an event.
  • Never send secrets, other players’ data or admin lists to clients with TriggerClientEvent(-1, ...).

Log refusals

A validation failure is either a bug or an attack. Log it with the player’s name and identifiers; a burst of refusals from one player is your best cheat detector. For weapon-specific checks see weapon anticheat validation.

Frequently asked questions

Can players trigger my server events?

Yes. Any event registered with RegisterNetEvent can be triggered by any client with any arguments.

Does renaming events stop exploits?

No. Event names are visible in client code and network traffic. Validate instead.

Why should I copy source into a local variable?

source is global and can change after a yield such as a database query. A local keeps the right player.

How do I make an event only the server can call?

Register it with AddEventHandler only, without RegisterNetEvent, or use an export.

Should the client send the price of an item?

Never. Prices, amounts and rewards must come from server-side configuration.

Need this built, not just explained?

Ten years of FiveM work, from Lua to NUI

Custom resources, React NUI, ESX / QBCore / Qbox integration, OneSync performance audits and security reviews — plus the websites and SEO around your server brand.

Related guides