Skip to Content
SecuritySecure Network Events

Treat a network event as an untrusted request

RegisterNetEvent enables network transport; it does not authenticate a payload or make a client honest. The server must decide what the caller may do, validate the requested action, and derive sensitive values from server-owned state.

Use AddEventHandler for events that should remain within one context. Do not register an internal reward or administrative handler as a network event merely because it is convenient. For syntax and routing, see events.

Complete example: request a documentation topic

This resource accepts only two topic names, checks a permission, and acknowledges the request. It does not create a persistent support ticket or expose arbitrary text to an administrator. Create resources/[local]/doc_help/ with the following files.

fxmanifest.lua

fx_version 'cerulean' game 'gta5' client_script 'client.lua' server_script 'server.lua'

server.lua

local topics = { controls = 'Open settings to inspect your FiveM key bindings.', connection = 'Record the exact error and contact the server staff.' } local lastRequest = {} RegisterNetEvent('doc_help:request', function(topic) local playerId = source if type(playerId) ~= 'number' or playerId <= 0 then return end -- Rate-limit attempts, including invalid or unauthorized requests. local now = os.time() if lastRequest[playerId] and now - lastRequest[playerId] < 3 then return end lastRequest[playerId] = now if type(topic) ~= 'string' or #topic > 24 or not topics[topic] then return end if not IsPlayerAceAllowed(playerId, 'doc.help') then TriggerClientEvent('doc_help:reply', playerId, 'This action is not permitted.') return end print(('[doc_help] Session %s requested topic %s'):format(playerId, topic)) TriggerClientEvent('doc_help:reply', playerId, topics[topic]) end) AddEventHandler('playerDropped', function() lastRequest[source] = nil end)

client.lua

RegisterCommand('dochelp', function(_, args) TriggerServerEvent('doc_help:request', args[1] or 'controls') end, false) RegisterNetEvent('doc_help:reply', function(message) if source ~= 65535 then return end -- Expect a server-origin event. if type(message) ~= 'string' or #message > 200 then return end print(('[Docs] %s'):format(message)) end)

The client-side origin check filters unexpected local dispatch; a modified client can still change its own display. The security boundary is the server’s validation, not that client check, the event name, or an obscured script.

Grant only the permission being demonstrated

Replace the identifier below with your test account’s real server-verified license identifier. In server.cfg:

add_ace group.docreader doc.help allow add_principal identifier.license:REPLACE_WITH_YOUR_LICENSE group.docreader ensure doc_help

Use refresh first if the running server has not discovered the new resource. In the client’s F8 console run dochelp controls. The permitted player sees the fixed help message. An unpermitted player sees a refusal. The custom doc.help object is separate from a command.* ACE; see permissions.

Abuse tests for your development server

Input or conditionExpected result
dochelp controls with permissionFixed controls message
Same request without permissionNo privileged action; refusal
Unknown topic, number, table, or oversized stringIgnored before business logic
Repeated requests within three secondsExtra attempts ignored
Another player requests a topicSeparate per-player rate limit
Player disconnectsRate-limit entry removed

To exercise non-string payloads, temporarily call TriggerServerEvent('doc_help:request', {}) from your own development client script. Do not perform abuse tests against someone else’s server. This small in-process limiter demonstrates the boundary; it is not a general anti-cheat, durable quota service, or protection against all network flooding.

Applying the pattern to money and inventory

For a purchase, accept an item identifier and intended action—not a client-chosen price, balance, owner, or reward. Resolve the player from server source; check current server-side inventory, permissions, and relevant world state. Use a transaction or idempotent operation so two concurrent requests cannot duplicate a grant. Revalidate after asynchronous work and confirm the original session is still connected.

State bags, NUI messages, hidden buttons, and client exports are not substitutes for those checks. Avoid automatic bans from one malformed payload; programming errors and version mismatches can produce bad requests too.

The official event-security guide  documents these trust boundaries. Continue with connection-time checks, async source handling, and database access.