# Callbacks (/docs/guides/callbacks)

An ESX callback is a request/response call between client and server: one side sends a request with arguments, the other side runs a handler and sends values back. Use `ESX.RegisterServerCallback` on the server and `ESX.TriggerServerCallback` or `ESX.AwaitServerCallback` on the client whenever the client needs data that only the server has (money, database rows, ownership checks).

## Overview [#overview]

| Function | Side | Purpose |
| --- | --- | --- |
| `ESX.RegisterServerCallback(name, handler)` | Server | Answer requests from clients |
| `ESX.TriggerServerCallback(name, cb, ...)` | Client | Ask the server, get the answer in `cb` |
| `ESX.AwaitServerCallback(name, ...)` | Client | Ask the server and wait for the return values |
| `ESX.DoesServerCallbackExist(name)` | Server | Check whether a name is registered |
| `ESX.RegisterClientCallback(name, handler)` | Client | Answer requests from the server |
| `ESX.TriggerClientCallback(playerId, name, cb, ...)` | Server | Ask one client, get the answer in `cb` |
| `ESX.AwaitClientCallback(playerId, name, ...)` | Server | Ask one client and wait for the return values |
| `ESX.DoesClientCallbackExist(name)` | Client | Check whether a name is registered |

In ESX Legacy 1.16 all of these are thin wrappers around the callback system in `esx_lib` (`xLib.callback`). ESX keeps the classic handler signatures so old scripts still work:

- Server handler: `function(source, cb, ...)`
- Client handler: `function(cb, ...)`

## Create a server callback [#create-a-server-callback]

<Steps>
<Step>
### Load the ESX object [#load-the-esx-object]

Add the ESX import to your `fxmanifest.lua` so `ESX` is available on both sides.

```lua title="fxmanifest.lua"
fx_version 'cerulean'
game 'gta5'
lua54 'yes'

shared_script '@es_extended/imports.lua'

server_script 'server/main.lua'
client_script 'client/main.lua'
```
</Step>
<Step>
### Register the handler on the server [#register-the-handler-on-the-server]

The first argument is the player's server id. Always call `cb` exactly once, also when you reject the request.

```lua title="server/main.lua"
ESX.RegisterServerCallback('myshop:getBalance', function(source, cb, accountName)
    local xPlayer = ESX.GetPlayerFromId(source)

    if not xPlayer or (accountName ~= 'money' and accountName ~= 'bank') then
        return cb(nil)
    end

    local account = xPlayer.getAccount(accountName)
    cb(account and account.money or 0)
end)
```
</Step>
<Step>
### Call it from the client [#call-it-from-the-client]

Callback style (non-blocking):

```lua title="client/main.lua"
ESX.TriggerServerCallback('myshop:getBalance', function(balance)
    if not balance then return end
    ESX.ShowNotification(('Bank balance: $%s'):format(ESX.Math.GroupDigits(balance)))
end, 'bank')
```

Await style (blocks the current thread until the answer arrives):

```lua title="client/main.lua"
CreateThread(function()
    local balance = ESX.AwaitServerCallback('myshop:getBalance', 'bank')

    if balance then
        print('Bank balance', balance)
    end
end)
```
</Step>
</Steps>

`AwaitServerCallback` must run inside a thread (for example `CreateThread`, an event handler, or a command handler), because it suspends the coroutine while it waits.

## Return several values [#return-several-values]

`cb` accepts any number of arguments, and they arrive in the same order on the other side.

```lua title="server/main.lua"
ESX.RegisterServerCallback('myjob:getStatus', function(source, cb)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return cb(false) end

    local job = xPlayer.getJob()
    cb(true, job.name, job.grade, job.onDuty)
end)
```

```lua title="client/main.lua"
CreateThread(function()
    local ok, jobName, grade, onDuty = ESX.AwaitServerCallback('myjob:getStatus')
    if ok then
        print(jobName, grade, onDuty)
    end
end)
```

## Database work inside a callback [#database-work-inside-a-callback]

oxmysql's `.await` functions fit naturally into callback handlers, because the handler already runs in its own thread.

```lua title="server/main.lua"
ESX.RegisterServerCallback('mygarage:getVehicles', function(source, cb)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return cb({}) end

    local rows = MySQL.query.await(
        'SELECT `plate`, `vehicle`, `stored` FROM `owned_vehicles` WHERE `owner` = ?',
        { xPlayer.getIdentifier() }
    )

    cb(rows or {})
end)
```

See [Using oxmysql with ESX](/docs/guides/oxmysql) for the full query API.

## Client callbacks (server asks the client) [#client-callbacks-server-asks-the-client]

Sometimes the server needs something only the client knows, such as the vehicle the player is looking at or a UI choice. Register the handler on the client and call it from the server.

```lua title="client/main.lua"
ESX.RegisterClientCallback('myscript:getStreetName', function(cb)
    local coords = GetEntityCoords(PlayerPedId())
    local streetHash = GetStreetNameAtCoord(coords.x, coords.y, coords.z)
    cb(GetStreetNameFromHashKey(streetHash))
end)
```

```lua title="server/main.lua"
RegisterCommand('where', function(source)
    if source == 0 then return end

    local street = ESX.AwaitClientCallback(source, 'myscript:getStreetName')
    print(('Player %s is on %s'):format(source, street))
end, false)
```

Treat the answer from a client callback as untrusted input, exactly like a net event. A modified client can return anything.

## Security: validate on the server [#security-validate-on-the-server]

A server callback is a public entry point. Any connected client can call it with any arguments, as often as the rate limit allows. The rules are the same as for `RegisterNetEvent` handlers:

1. Use `source` to identify the player. Never accept a player id, identifier or job from the client arguments.
2. Check types and ranges (`type(amount) == 'number'`, `amount > 0`, `amount <= limit`).
3. Re-check permissions and state on the server (job, grade, distance to a shop, item count).
4. Do the money or item change on the server, not in the client after the callback returns.

```lua title="server/main.lua"
local SHOP_COORDS <const> = vector3(25.7, -1347.3, 29.5)
local PRICE <const> = 5

ESX.RegisterServerCallback('myshop:buyWater', function(source, cb, amount)
    local xPlayer = ESX.GetPlayerFromId(source)

    if not xPlayer or type(amount) ~= 'number' or amount ~= math.floor(amount) or amount < 1 or amount > 10 then
        return cb(false, 'invalid_request')
    end

    if #(GetEntityCoords(GetPlayerPed(source)) - SHOP_COORDS) > 5.0 then
        return cb(false, 'too_far')
    end

    if not xPlayer.canCarryItem('water', amount) then
        return cb(false, 'no_space')
    end

    if not xPlayer.removeMoney(PRICE * amount, 'Bought water') then
        return cb(false, 'no_money')
    end

    xPlayer.addInventoryItem('water', amount)
    cb(true)
end)
```

`xPlayer.removeMoney` returns `false` when the player does not have enough money, so the check and the deduction happen in one step.

## Timeouts, payload size and rate limits [#timeouts-payload-size-and-rate-limits]

The callback system in `esx_lib` adds protection that older ESX versions did not have. You can tune it with convars in `server.cfg`.

| Convar | Default | Effect |
| --- | --- | --- |
| `xLib:callbackTimeout` (or `esx:callbackTimeout`) | `300000` ms await timeout | How long a pending request waits before it is rejected. The server also cancels handlers that run too long (30 seconds unless the convar is set). |
| `xLib:callbackMaxPayloadBytes` | `65536` | Maximum size of the arguments a client may send to a server callback |
| `xLib:callbackBurst` / `xLib:callbackRate` | `60` / `30` per second | Per-player request budget |
| `xLib:callbackMaxInFlight` | `8` | Parallel callbacks one player may have open |

```bash title="server.cfg"
set xLib:callbackTimeout 15000
set xLib:callbackMaxPayloadBytes 32768
```

When a request is over budget or too large, the server answers with `false` instead of running your handler. Your client code should handle a `false` or `nil` result.

## Common issues [#common-issues]

- **Callback never returns**: an early `return` without `cb(...)`. Every code path in the handler must call `cb`.
- **"callback is already owned by resource"**: callback names are owned by the resource that registered them first. Two resources cannot register the same name. Prefix names with your resource name (`myshop:getBalance`).
- **Callback stops working after a restart**: callbacks are removed when the owning resource stops. Restart the resource that registers the callback, not only the one that calls it.
- **`AwaitServerCallback` errors outside a thread**: wrap the call in `CreateThread`.
- **Large tables fail silently**: arguments above `xLib:callbackMaxPayloadBytes` are rejected. Send ids and load the data on the server instead.
- **Trusting the client**: a callback that takes a price or an amount of money from the client is an exploit. Calculate those values on the server.

## Related [#related]

- [Client callback module reference](/docs/es_extended/client/modules/callback)
- [Server functions](/docs/es_extended/server/functions)
- [xPlayer reference](/docs/es_extended/server/xplayer)
- [Using oxmysql with ESX](/docs/guides/oxmysql)
- [Coding practices](/docs/getting-started/coding-practices)
