May 20, 2026
5 min read
ZeroTrust Team

Secure your events: FiveM Lua Event Security Guide

Cheats allow malicious clients to trigger events in any context. Learn how to secure client-server communication, implement server-side validation, and configure FXServer security convars.

The anti-cheat team is always trying to improve the anti-cheat, but sometimes things slip through.

In this guide we'll try to help cover some common-practice things you can do to make your server more secure by properly locking down your events.

Understanding Network Events in FiveM

Cheats can allow the client to trigger events in any context.

When we say context we mean they can execute client->server (via TriggerServerEvent) or client resource->client resource (via TriggerEvent).

Proper Use of Event Handlers in Lua

When working with events in Lua, it's crucial to register them correctly based on whether they are called by the client or the server.

A common mistake is registering server events that are not supposed to be called by the client, or vice-versa, which can lead to security vulnerabilities.

AddEventHandler

Use AddEventHandler when the event is intended to be triggered within the same context, either client-client or server-server. This ensures that the events are not networked and cannot be called by the opposite side.

AddEventHandler("eventName", function(eventParam1, eventParam2)
    -- Code here will be executed once the event is triggered in the same context.
end)

RegisterNetEvent

Use RegisterNetEvent when the event needs to be triggered across different contexts, such as client-to-server or server-to-client.

Under the hood

NOTE: This does not block execution from the same context. Under the hood, RegisterNetEvent is a wrapper that simply matches this: RegisterNetEvent("eventName") AddEventHandler("eventName", function() ... end)
RegisterNetEvent("eventName", function(eventParam1, eventParam2)
    -- Code here will be executed once the event is triggered across different contexts.
end)

This example is for the client, and like anything on the client, it is not foolproof and can be manipulated by cheating clients.

If you want to block execution from the same context (such as preventing a server-to-server network event from being triggered by the client), you must register your event and check the sender's source:

RegisterNetEvent("eventName", function(eventParam1, eventParam2)
    -- Server will send network id `65535` for events originating from the server
    if source ~= 65535 then return end
end)

Adding Checks and Validations

Even if you build a robust anti-cheat, adding checks on server events makes them significantly more secure. This is a highly recommended practice, although it won't prevent everything. Below we share some good tips.

  • Player money: Validate balance and transactions server-side.
  • Player state bags: Verify state data in active sessions.
  • Player inventory items: Verify item names and quantities.
  • Player position: Verify ranges and distances.
  • Player experience and level: Ensure stats are calculated server-side.
  • Player permissions and roles: Validate server-side ACL.

Rule of Thumb

Make sure to retrieve all values using server-side methods, without allowing players to supply or modify these values. Please note that client-side checks can also be good practice for User Experience (UX), but they can be easily bypassed.

This ensures the integrity and security of your gaming environment.

Common Security Pattern Examples

All examples below assume the use of a framework (such as ESX, QB-Core, etc.).

Bad Security (Never do this)

This is intended to show you bad ways to handle events, you should never do this. Directly adding items to the user from their own input is always bad practice: you must always validate user inputs.

RegisterNetEvent("job:givePlayerItem", function(item, count)
    local ply = FX.GetPlayerFromSource(source)
    -- Directly adding items to the user from their own input is dangerous!
    ply.addItem(item, count)
end)

Good Security (Recommended)

Here is a robust security pattern. The server tracks the player's state and coordinates, and verifies actions using server-side ticks instead of relying solely on client triggers.

-- dummy coordinate
local VALID_JOB_COORD = vector3(125.0, 111.1, 35.83)
local MAX_ITEM_COUNT = 10

-- dummy coordinate
local VALID_TURNIN_COORD = vector3(1888.0, 1254.1, 48.0)

local ITEM_NAME = 'log'

-- list of players with an active job
local activeJobs = {}

AddEventHandler("playerDropped", function ()
    if not activeJobs[source] then return end
    activeJobs[source] = nil
end)

function isPedWithinRange(ped, tgtCoords)
    return #(GetEntityCoords(ped) - tgtCoords) < 15.0
end

-- process job tick and increment items server-side
CreateThread(function()
    while true do
        for src, data in pairs(activeJobs) do
            local ped = GetPlayerPed(src)
            -- if they are not in range, we don't want to give them the item
            if isPedWithinRange(ped, VALID_JOB_COORD) then
                -- give them the item, but limit it to MAX_ITEM_COUNT
                data.itemCount = math.min(data.itemCount + 1, MAX_ITEM_COUNT)
            end
        end
        -- process job tick once per second
        Wait(1000)
    end
end)

RegisterNetEvent("job:startJob", function()
    local ped = GetPlayerPed(source)
    -- if they are within 15 units, they do the job
    if isPedWithinRange(ped, VALID_JOB_COORD) then
        activeJobs[source] = {
            itemCount = 0,
        }
    end
end)

RegisterNetEvent("job:givePlayerItem", function()
    local ply = FX.GetPlayerFromSource(source)
    -- if they have no active job, they shouldn't reach this event!
    local jobData = activeJobs[source]
    if not jobData then return end

    local ped = GetPlayerPed(source)
    -- they are not within range of turn in coordinate, reject their changes
    if not isPedWithinRange(ped, VALID_TURNIN_COORD) then return end

    -- reset job data so they can't trigger it multiple times
    activeJobs[source] = nil

    -- add items to user validated server side
    ply.addItem(ITEM_NAME, jobData.itemCount)
end)

Server Owner Options (Convars)

Please note that the following settings should not be changed unless you know exactly what you are doing. The Cfx.re / Adhesive team works very hard to prevent cheaters. Most of these features will be enabled by default with FXServer build version 8450 and above.

  • sv_kick_players_cnl_timeout_sec: This is the delay after which the server will kick the player (e.g. if it is 600, it will kick them after 10 minutes without CnL connection).
  • sv_kick_players_cnl_update_rate_sec: This is how often the list of players is queried with CnL.
  • sv_pure_verify_client_settings: Replaces the periodic request to info.json at the client. Establishes a secure connection between adhesive and svadhesive and verifies certain sv_settings like pureLevel, scripthook, and other configurations.
  • sv_kick_players_cnl_consecutive_failures: Number of consecutive failures needed beyond timeout_sec to kick a player. By default, the value is set to 2, which means that if a player fails to connect for 10 minutes and then misses the next update, they will be kicked. This serves as a safety mechanism.
  • sv_authMaxVariance: Variance indicates how likely the user's ID is to change for a given provider (i.e. 'steam', 'ip', or 'license').
  • sv_authMinTrust: Trust indicates how unlikely the user's identity is to be spoofed by a malicious client.
  • sv_filterRequestControl: A console variable used to block the routing of REQUEST_CONTROL_EVENT based on a configurable policy.
  • sv_disableClientReplays: Turning this on aims to reduce opportunities for cheating. Please note this will disable the Rockstar Editor.

Results on Player

Turning these convars on will likely result in the player being kicked with the following reason: Connection to CNL timed out.

Being kicked does not mean the player is automatically globally banned (Global Banned). However, it provides a strong indication of player reliability, which is extremely useful in assessing their trust.

Important to Know

The provided codes are not meant to work by simple copy-paste. They are just some tips to prevent certain actions that could happen on the server. This requires some programming knowledge. You are always free to join our Discord to get additional help.