Quick answer: The Async — HTTP event is either not defined on the object that made the request, the request ID doesn’t match what you’re comparing in async_load[? “id”], or on HTML5 the browser’s CORS policy is silently blocking the response. Add an Async — HTTP event, log every field of async_load to confirm the event fires at all, then narrow down from there.
You call http_get("https://api.example.com/scores") in GameMaker, build a leaderboard system around it, and then nothing happens. The Async — HTTP event never fires, the leaderboard stays empty, and there are no error messages. GameMaker’s async HTTP system has a handful of specific gotchas that all produce the same “nothing happened” symptom. Here’s how to diagnose and fix each one.
The Symptom
The hallmark of this issue: you call http_get() or http_post(), wait, and the Async — HTTP event never executes. Alternatively, the event does fire but your code inside it never runs because the request ID check filters it out. In either case, your response processing logic is never reached.
Less obvious variant: the event fires on a different object than expected, and you’re looking at the wrong object’s event in the debugger.
What Causes This
1. The Object Has No Async — HTTP Event
This is the most common cause and the easiest to miss. In GameMaker, async events are just like any other event — they must be explicitly defined on the object. If you call http_get() in the Create event of obj_NetworkManager, but you never added an Async — HTTP event to obj_NetworkManager, the response arrives and is silently discarded.
Open obj_NetworkManager in the object editor. Look at the event list on the left. Under Async, you should see HTTP. If it’s not there, click Add Event → Async → HTTP. Add at minimum a single line to confirm it’s being reached:
/// obj_NetworkManager — Async HTTP event (diagnostic version)
show_debug_message("Async HTTP fired!");
show_debug_message("id = " + string(async_load[? "id"]));
show_debug_message("status = " + string(async_load[? "status"]));
show_debug_message("result = " + string(async_load[? "result"]));
Run the game. If you see “Async HTTP fired!” in the output log, the event is working and the problem is in your filtering logic. If you don’t see it, work through the causes below.
2. The Request ID Doesn’t Match
http_get() returns an integer request ID. Every async HTTP response includes async_load[? "id"], which is the ID of the originating request. If you have multiple requests in flight or if your comparison variable is initialized incorrectly, your filter check discards valid responses.
The correct pattern is to store the return value of http_get() and compare it in the Async event:
/// Create event
request_id = -1; // initialize to an invalid value
/// Step event or button press
request_id = http_get("https://api.example.com/scores");
/// Async — HTTP event
if (async_load[? "id"] == request_id)
{
var status = async_load[? "status"];
if (status == 1) // 1 = success
{
var result = async_load[? "result"];
show_debug_message("Response: " + result);
}
else if (status == 0) // 0 = failure
{
show_debug_message("Request failed. HTTP status: " +
string(async_load[? "http_status"]));
}
// status == -1 means still in progress (intermediate event)
}
A subtle bug: if you call http_get() multiple times before the first response arrives, each call returns a different ID. Make sure request_id holds the ID of the most recent request you care about, or use a ds_map keyed by request ID to track multiple concurrent requests.
3. Understanding async_load[? “status”] Values
GameMaker fires the Async — HTTP event multiple times for a single request on some platforms, with different status values representing the request lifecycle:
- -1: The request is in progress. This intermediate event is fired on some platforms to indicate ongoing progress. The result may be partial. Do not process the response yet.
- 0: The request failed. This could be a network error, an unreachable server, a timeout, or (on HTML5) a CORS block. Check
async_load[? "http_status"]for the HTTP status code (e.g., 404, 500). A value of 0 here usually means the request never reached the server or was rejected at the transport layer. - 1: The request succeeded. The full response is in
async_load[? "result"]as a string.
If your code only processes status == 1 and never logs the failure case, you’ll silently miss failed requests. Always add a branch for status == 0 during development.
4. HTML5 and CORS
On the HTML5 export, your game runs inside a browser, which enforces the Same-Origin Policy. A request from https://yourgame.itch.io to https://api.example.com is a cross-origin request. The browser sends a preflight OPTIONS request to the server; if the server doesn’t respond with the correct Access-Control-Allow-Origin header, the browser blocks the response and GameMaker’s Async event either never fires or fires with status == 0.
The server must respond with:
# Required response header (server-side configuration)
Access-Control-Allow-Origin: *
# Or for credentialed requests:
Access-Control-Allow-Origin: https://yourgame.itch.io
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
If you control the server, add these headers. If you do not (e.g., a third-party API), you need a same-origin proxy. For itch.io hosted games, the proxy approach is the only option for external APIs that don’t support CORS.
You can verify a CORS problem by running the same request in the browser’s developer console. Open the console, type fetch("https://api.example.com/scores").then(r => r.text()).then(console.log) and watch for CORS errors in the console output. If you see “Access to fetch at ... from origin ... has been blocked by CORS policy”, that’s your culprit.
5. The Event Fires on a Different Object
In GameMaker, the Async — HTTP event fires on every instance that has an Async — HTTP event defined, not just the one that made the request. If you have two objects with Async — HTTP events — say obj_NetworkManager and a UI element obj_Leaderboard that also has one — both will receive the event. The request ID filtering (async_load[? "id"] == request_id) is what scopes the response to the right handler.
This also means: if you call http_get() from an object that does not have an Async — HTTP event, but another object in the room does, that other object will still receive the response. This can create confusing cross-object interactions in a large project.
Best practice is to route all HTTP requests through a single persistent controller object, keep the Async — HTTP event only on that object, and use callbacks or global variables to deliver results to other objects.
6. http_get() vs http_get_file()
If you’re downloading a binary file (an image, a save file, audio data) and using http_get(), the response may arrive garbled or truncated because binary data is decoded as a UTF-8 string. Use http_get_file() instead:
/// Download a file to disk instead of into memory
var save_path = working_directory + "downloaded_save.json";
request_id = http_get_file(
"https://api.example.com/save/12345",
save_path
);
/// Async — HTTP event
if (async_load[? "id"] == request_id && async_load[? "status"] == 1)
{
// File has been written to save_path; load it
var buf = buffer_load(save_path);
// ... process buffer ...
buffer_delete(buf);
}
The key difference: http_get() delivers the response as a string in async_load[? "result"], while http_get_file() writes the response to disk and the result key contains the local file path.
The Fix: Step-by-Step Diagnostic
- Add an Async — HTTP event with full logging to confirm the event fires at all. Log every key in
async_load. - Check the request ID. Log
http_get()’s return value in the Create or Step event, and logasync_load[? "id"]in the Async event. Confirm they match. - Check the status. Log
async_load[? "status"]. If it’s 0, the request failed at the network level. Logasync_load[? "http_status"]for the HTTP code. - On HTML5, test CORS using browser DevTools. Check the Network tab for the request and look for CORS errors in the console.
- Confirm the object has the event. Open the object in the GameMaker IDE and verify Async — HTTP appears in the event list.
- Check for duplicate handlers. Search your project for all objects that have an Async — HTTP event to rule out interference.
Related Issues
If your Async — HTTP event fires but the response body is empty or truncated, check whether the server is returning a Content-Encoding: gzip response. GameMaker’s HTTP functions do not decompress gzip-encoded responses on all platforms. If your server compresses responses, either disable compression for GameMaker clients (detect via User-Agent) or configure the server to return uncompressed responses when the request lacks an Accept-Encoding: gzip header.
Also note that http_post_string() sends the body with Content-Type: application/x-www-form-urlencoded by default. If your server expects JSON, use http_request() instead, which lets you set headers explicitly:
var headers = ds_map_create();
ds_map_add(headers, "Content-Type", "application/json");
var body = '{"score": 9001, "player": "you"}';
request_id = http_request(
"https://api.example.com/scores",
"POST",
headers,
body
);
ds_map_destroy(headers); // free immediately after the call
Log everything in the Async event first — you can’t debug an event you can’t confirm is firing, and the logs will tell you exactly which of these causes you’re dealing with.