Engineering
Calling web APIs from FiveM scripts
Use PerformHttpRequest in FiveM: the callback (statusCode, body, headers, errorData), GET and POST with JSON, custom headers and auth, awaiting results with promises, timeouts and error handling, keeping API keys on the server, rate limits, and JavaScript alternatives.
Overview
Discord bots, web panels, licence checks, analytics — sooner or later a resource needs to talk to the web. FiveM’s PerformHttpRequest does it from Lua with a callback; wrapped in a promise it reads like a normal function call.
A basic request
PerformHttpRequest('https://api.example.com/status', function(status, body, headers, err)
if status ~= 200 then
print(('request failed: %s %s'):format(status, err or ''))
return
end
local data = json.decode(body)
print('players online on the website:', data.online)
end, 'GET')POST with JSON and headers
local API_KEY = GetConvar('myres_api_key', '')
local function post(url, payload, cb)
PerformHttpRequest(url, cb, 'POST', json.encode(payload), {
['Content-Type'] = 'application/json',
['Authorization'] = 'Bearer ' .. API_KEY,
})
endStore the key with set myres_api_key "..." (server-only, not setr) — see convars.
Awaiting the result
local function request(url, method, body, headers)
local p = promise.new()
PerformHttpRequest(url, function(status, resBody, resHeaders, err)
p:resolve({ status = status, body = resBody, headers = resHeaders, error = err })
end, method or 'GET', body or '', headers or {})
return Citizen.Await(p)
end
CreateThread(function()
local res = request('https://api.example.com/status')
if res.status == 200 then print(res.body) end
end)Citizen.Await must run inside a thread, event handler or callback. Thread basics: threads and Wait.
Safety and limits
- Never put API keys in client files — every player can read them.
- Respect the API’s rate limits; queue or batch requests.
- Do not make requests per frame or per player action without caching.
- Validate responses before using them in game logic.
- For logging to Discord, see webhook logging.
JavaScript alternative
Server-side JavaScript resources run on Node.js and can use fetch or npm HTTP clients directly — see TypeScript scripts.
Frequently asked questions
How do I make an HTTP request in FiveM Lua?
Call PerformHttpRequest(url, callback, method, data, headers); the callback receives status code, body, headers and error data.
How do I send JSON?
Pass json.encode(table) as the data and set the Content-Type: application/json header.
Can I wait for the response?
Yes — resolve a promise in the callback and Citizen.Await it inside a thread.
Should I call APIs from the client?
Not with secrets. Make authenticated requests from the server.
Need this built, not just explained?
Ten years of FiveM work, from Lua to NUI
Custom resources, React NUI, ESX / QBCore / Qbox integration, OneSync performance audits and security reviews — plus the websites and SEO around your server brand.
Related guides
- EngineeringLogging FiveM server events to DiscordFrom the server, POST a JSON body with an embeds array (title, description, integer color, fields, ISO timestamp) to the webhook URL using PerformHttpRequest. Keep the URL in a server-only convar (set), respect Discord’s limits (for example 256 characters for titles, 4096 for descriptions, 1024 per field value, up to 10 embeds per message) and back off on HTTP 429, batch noisy events, and log identifiers only where staff need them.
- EngineeringConvars in FiveM: configuration that lives in server.cfgset name value creates a server-only convar. sets name value also publishes it to the server list (for tags, locale, banners). setr name value replicates it to clients so client scripts can read it. Read convars with GetConvar(name, default) (strings) or GetConvarInt(name, default) (integers). Never use setr or sets for secrets.
- EngineeringThreads and Wait in FiveM: loops that do not eat framesCreateThread(fn) starts a coroutine that runs alongside the game; Wait(ms) pauses it and lets everything else run. Wait(0) resumes on the next frame, so the loop runs every frame (60+ times a second). Use it only while you must draw or read input every frame; otherwise sleep for hundreds of milliseconds, and make loops adaptive — fast when the player is near something, slow when they are not.
- EngineeringWriting FiveM resources in TypeScriptInstall @citizenfx/client, @citizenfx/server, TypeScript and esbuild. Write src/client and src/server, bundle each into a single file (dist/client.js, dist/server.js), and list those in the fxmanifest. Client JS has the ES2017 standard library but no browser or Node APIs; server JS runs on Node.js 16 by default, or Node 22 with node_version '22' in the manifest. Type other resources’ exports by extending CitizenExports.