START HERE·MAKING THINGS HAPPEN·Verified June 2026 · Lua 5.4 · ox_lib 3.x
Learning with an AI assistant?
Copies this whole lesson - every step, code block, and the exact console errors - plus 2026 ground rules (no lua54 'yes', Cfx.re Portal, correct callback signatures) as a ready-to-paste mentor prompt.
Start Here · Making things happen

Callbacks: ask the server and wait for an answer

An event is fire-and-forget. You trigger it, the message lands, and you carry on. Your code does not stop and wait, and the other side has no way to hand a value straight back to you. But sometimes the client genuinely needs an answer first. Before it opens a shop menu it has to ask "how much money do I have?" and then wait for the real number. Asking the server a question and waiting for the answer before continuing is exactly what a callback is for. By the end of this lesson you will have asked one and read the reply.

You'll learn
What a callback is, why a plain event cannot hand a value back, how to register and call one with ox_lib, the safety rule that still applies, and how to build a tiny working callback yourself.
Time
~10 minutes
Difficulty
Beginner
You need
The qu_hello practice resource from earlier lessons, a test server you can restart, and ox_lib installed and started. If you skipped "Commands: trigger code on demand", do that first.

Watch

RED: the server and client round-trip, explained.
BEFORE YOU START

What a callback is

A callback is a question your code sends to the server, paired with the answer the server sends back. One half is the request: the client asks something. The other half is the response: the server computes a value and returns it. Unlike a plain event, the client does not just fire and move on. It pauses on that line until the answer comes back, then keeps going with the value in hand.

If you have ever loaded a web page, you have already met this shape. A plain event is like sending a postcard: it goes out, and that is the end of it. A callback is like asking a question out loud and standing there for the reply before you do anything else. The request goes one way, the response comes back the other way, and the two are tied together as one round trip.

In modern FiveM you build callbacks with a tool called lib.callback, which comes from a resource called ox_lib. It gives you one clean function to register a callback on the server and one to call it from the client. The waiting part has a name too. When your code stops on a line until the answer arrives, that is a synchronous wait: the next line does not run until this line is done.

Vocabulary

callback
A request and response pair. The client asks the server a question, the server returns a value, and the client receives that value before continuing.
request
The asking half of a callback. The client sends a callback name plus any details, like which item or how many, to the server.
response
The answering half. Whatever the server function returns is the value the client receives back.
lib.callback
The callback tool from ox_lib. lib.callback.register defines a callback on the server; lib.callback.await calls it from the client and waits for the answer.
synchronous wait
When a line of code stops and waits for a result before the next line runs. lib.callback.await waits this way, so the answer is ready on the very next line.

Why events are not enough

Picture the shop again. The player clicks "open shop", and before you draw a single button you need to know their balance so the screen can show what they can afford. With only the events from the last lesson, you are stuck. TriggerServerEvent sends a message to the server and returns nothing. There is no slot for the server to put a number into. The event is a one-way street.

You could try to fake a round trip with two separate events: the client triggers qu_shop:askBalance, and later the server triggers qu_shop:hereIsYourBalance back. But now the answer arrives in a totally different place in your code, with no clean way to tie it to the question you asked. Which menu was this balance for? Did the reply even arrive? The flow scatters and gets fragile fast.

A callback fixes this by keeping the question and the answer together. The client asks, waits right there, and the number lands on the very next line. The whole "how much money do I have?" round trip becomes one readable request, and the answer is exactly where you need it.

How it works with ox_lib

A callback has two sides, just like an event. The server side defines what the answer is. The client side asks for it and waits. Both use lib, which only exists because ox_lib is running, so ox_lib must be listed as a dependency of your resource (you add dependency 'ox_lib' to your fxmanifest.lua). If ox_lib is not started, lib is nil and nothing works.

The server registers a callback that returns a value

lua
lib.callback.register('qu_hello:getNumber', function(source)
    return 42
end)

lib.callback.register defines a callback by name, here qu_hello:getNumber, and hands it a function. That function is the one the server runs when a client asks. Its first value, source, is the id of the player who asked, exactly like a server event handler. Whatever the function returns is what the client receives. This one always returns the number 42. In a real feature the server would look up the player's actual balance and return that instead.

The client asks and waits for the answer

lua
local result = lib.callback.await('qu_hello:getNumber', false)
print('[qu_hello] the server said ' .. result)

lib.callback.await sends the request using the same name, qu_hello:getNumber, and then waits for the response. The line stops there until the server answers, and the returned value lands in result. That is the synchronous wait: the print on the next line does not run until the number is back, so result is guaranteed to be ready by then. The second argument, false, means "use the default timeout" (about 5 seconds). The names on both sides must match character for character, just like event names.

Build a tiny callback

You will register a callback on the server that returns a number, then call it from a command on the client and print what came back. This is the smallest possible proof of a value making the full round trip from client to server and back.

Register the callback on the server

The server knows how to answer one question.

Before you add this, predict: does registering a callback print anything on its own, or does it just sit there waiting until a client asks? Open server.lua inside your qu_hello resource and add this:

lua
lib.callback.register('qu_hello:getNumber', function(source)
    return 42
end)

lib.callback.register defines the callback by name. The function it holds returns 42, and that returned value is what any client who asks will receive.

Add a client command that asks

A typed command sends the question and waits for the reply.

Before you add this, predict: when lib.callback.await runs, does the print on the next line fire right away, or does it have to wait for the server's answer first? Open client.lua in the same resource and add this:

lua
RegisterCommand('getnumber', function()
    local result = lib.callback.await('qu_hello:getNumber', false)
    print('[qu_hello] the server said ' .. result)
end, false)

When you type getnumber, lib.callback.await asks the server using the matching name and waits. The answer lands in result, then the next line prints it. The false on RegisterCommand lets you run it from your own game.

Confirm ox_lib is a dependency

lib exists when your resource loads.

Before you check this, predict: if ox_lib is not started before qu_hello, what will lib be when your code first runs? Open fxmanifest.lua in qu_hello and make sure this line is present:

lua
dependency 'ox_lib'

This tells the server to start ox_lib before your resource, so lib is ready by the time your code runs. Make sure ox_lib itself is started in your server.cfg with ensure ox_lib before qu_hello.

Reload and run it

The number makes the round trip and prints.

Before you run it, predict: what exact line shows up in the F8 console after the round trip finishes? In the txAdmin Live Console, reload the resource:

text
restart qu_hello

Join the server, press F8 to open the client console, type getnumber, and press Enter. The print lives in client code, so read the result in the F8 client console, not the server console.

You just asked the server a question and got a real value back on the next line. Swap that return 42 for a lookup of the player's balance and you have the exact shape every "how much do I have?" feature is built on.

The safety rule still applies

A callback feels friendlier than a raw event, but the danger is identical, because under the hood a callback travels across the same network as an event. The rule from the last lesson does not relax: the server computes and returns the trusted value, and the client never gets to decide the answer it wanted.

Common beginner mistakes

SymptomFix
The client asks but nothing ever comes back, then it times out.The callback name on the client does not match the name registered on the server. lib.callback.await('qu_hello:getNumber', ...) must match lib.callback.register('qu_hello:getNumber', ...) character for character. Compare the two strings, including the colon and capital letters.
attempt to index a nil value (global 'lib')ox_lib is not started, so lib does not exist. Add ensure ox_lib to server.cfg before your resource, add dependency 'ox_lib' to your fxmanifest.lua, and restart.
You triggered a plain event and waited for it to return a value, but got nothing.A plain event is fire-and-forget and cannot return anything. If you need a value back, you need a callback: register it with lib.callback.register and call it with lib.callback.await, not TriggerServerEvent.
The line after await never seems to run while you expected the script to keep going.That is await doing its job: it blocks until the server replies. The next line only runs once the answer is back. If the server function is slow or errors, the wait stretches to the timeout. Keep the server function fast and make sure it returns a value.
When do you reach for a callback instead of a normal event?

Reach for a callback whenever the client needs a value back from the server before it can continue, like a bank balance, a shop's stock, or a yes/no answer to "am I allowed to open this menu?". The client asks, waits, and uses the answer on the next line. If the client does not need anything back, a plain event is the right tool, because it is simpler and does not make your code wait. The shorthand: need an answer, use a callback; just announcing something happened, use an event.

Try it yourself

What you can do now

  • Explain that a callback is a request and response pair: the client asks, the server returns a value, and the client receives it before continuing.
  • Say why a plain event is not enough: it is fire-and-forget and cannot hand a value back.
  • Register a callback on the server with lib.callback.register and return the value the client should get.
  • Call a callback from the client with lib.callback.await and read the answer on the next line.
  • Remember ox_lib must be started and listed as a dependency, or lib is nil.
  • State the safety rule: the server computes and returns the trusted value; the client never decides the answer.

You can now ask the server a question and act on the real answer, which unlocks every feature that has to check something before it does anything. Right now your callback lives in one resource. Next up: "Exports: share code between resources", where you take a function you wrote in one resource and let another resource call it by name, so your scripts work together instead of in isolation.