Event cancellation and latent delivery
A local event can let another resource veto an action. A latent network event can transfer a larger payload without monopolizing the normal event channel. Neither feature replaces server validation, and neither is an automatic application-level acknowledgement.
Start with event direction and security. This guide covers what happens after you have selected the correct runtime and recipient.
Cancellation is not a return value from your handler
CancelEvent() marks the current event as canceled. It does not prevent other handlers for that event from running. WasEventCanceled() inspects the last triggered local event; it is not a way to ask a remote machine whether it accepted your request. Lua’s TriggerEvent additionally returns whether that local event was canceled.
A handler returning false is not the same operation as CancelEvent(). A native engine event can only prevent its underlying action where that event’s documented contract permits cancellation. Check the client or server contract rather than assuming every notification is cancelable. These distinctions follow the official cancellation guide .
Build a local veto before the side effect
Create resources/[local]/doc_event_gate/fxmanifest.lua:
fx_version 'cerulean'
game 'gta5'
client_script 'client.lua'Create client.lua:
local gate = AddEventHandler('doc_event_gate:beforeNote', function(text)
if type(text) ~= 'string' or #text == 0 or #text > 80 then
CancelEvent()
end
end)
RegisterCommand('docnote', function(_, args)
local text = table.concat(args, ' ')
local canceled = TriggerEvent('doc_event_gate:beforeNote', text)
if canceled then
print('Note rejected: use 1 to 80 bytes of text.')
return
end
print(('Local note: %s'):format(text))
end, false)
AddEventHandler('onClientResourceStop', function(name)
if name == GetCurrentResourceName() then
RemoveEventHandler(gate)
end
end)Run refresh and ensure doc_event_gate in the server console. In F8, docnote hello should print a note, while docnote without text should be rejected. Lua’s string length here counts bytes, not user-perceived characters; this deliberate small diagnostic is not a multilingual text-length validator.
The command performs the side effect only after checking the veto. A second handler that prints immediately would still print even if the gate cancels the event. Do not depend on handler order to implement a transaction. Keep this immediate validation non-yielding; a later asynchronous result needs an explicit request/reply protocol instead of trying to undo an already completed action.
This client-local example has no authority over money, items or permissions. For those operations the server must own the validation and the mutation.
Choose normal or latent transport
| Direction | Normal Lua call | Latent Lua call |
|---|---|---|
| Client to server | TriggerServerEvent(name, ...) | TriggerLatentServerEvent(name, bps, ...) |
| Server to client | TriggerClientEvent(name, target, ...) | TriggerLatentClientEvent(name, target, bps, ...) |
Use ordinary events for small requests. Consider latent delivery for genuinely larger payloads after removing fields the recipient does not need. bps means bytes per second, not bits or a timeout. The documented default for zero or minus one is 25000. On server broadcasts, the budget is per recipient: targeting -1 increases aggregate traffic with the number of players.
The official transport guide explains the channel and targeting behavior. Raising bps to an extreme value defeats the reason for throttling. Estimate transfer duration from serialized size and the chosen budget, but do not treat that estimate as a delivery deadline or a bandwidth guarantee.
Send a bounded test payload to one client
For a separate network test, create doc_latent with this manifest:
fx_version 'cerulean'
game 'gta5'
server_script 'server.lua'
client_script 'client.lua'Create server.lua. Only the server console can invoke this example:
RegisterCommand('docnotice', function(commandSource, args)
if commandSource ~= 0 then return end
local target = tonumber(args[1])
if not target or target < 1 or target % 1 ~= 0 then
print('Usage: docnotice <connected server ID>')
return
end
if not GetPlayerName(target) then
print('That player is not connected.')
return
end
local text = string.rep('A bounded documentation transfer.\n', 400)
if #text > 65536 then return end
TriggerLatentClientEvent('doc_latent:notice', target, 25000, text)
end, true)Create client.lua:
RegisterNetEvent('doc_latent:notice', function(text)
if source ~= 65535 then return end
if type(text) ~= 'string' or #text > 65536 then return end
print(('Received documentation payload: %d bytes'):format(#text))
end)Start doc_latent, then run docnotice with the connected test client’s server ID in the server console. Only that client should print a received-byte count. The code prints the size rather than dumping a large payload. Repeat with two connected clients and verify that the other client receives nothing.
The source check on the client identifies the expected server-originated event context; it does not make client state trustworthy. This example intentionally exposes no client-to-server upload endpoint. A real upload needs bounded schema validation, per-player rate limits, server-owned authorization and disconnect cleanup. A slower transport is not protection against an excessive or malicious payload.
Verify failure behavior, not just a successful send
Disconnect the target during the test and observe that the command has no end-to-end acceptance response. For a real operation that needs confirmation, design a reply with a request ID, timeout and duplicate handling. Never interpret a remote CancelEvent or a successful trigger call as proof that a database write completed.
Stop doc_event_gate and doc_latent and remove their startup entries afterward. The code is source-reviewed documentation; cancellation dispatch, network behavior and disconnect handling still require actual FiveM client/server acceptance. No simulated network test or website build proves those engine behaviors.