atherhubather.hub
Back to Guides
SUNC
10 min read
May 11, 2026

SUNC HTTP & WebSocket

Roblox provides its own HTTP API (game:HttpGet, HttpService:RequestAsync), but it's rate- limited and lacks several headers that custom integrations need. SUNC's request function and WebSocket.connect bridge the gap with a richer, script-controlled API. This article walks through both.

Ather
Ather
Lead developer at Atherhub. Writes about Roblox internals, Luau, script engineering, and platform security.Last updated May 11, 2026

request: the workhorse HTTP function

One function call, full control over method, URL, headers, and body. Returns a response object with status, body, and reply headers.

luau
request(options: {
    Url: string,
    Method: string?,        -- defaults to "GET"
    Headers: { [string]: string }?,
    Body: string?,          -- for POST/PUT/PATCH
    Cookies: { [string]: string }?,
}): {
    Success: boolean,
    StatusCode: number,
    StatusMessage: string,
    Headers: { [string]: string },
    Cookies: { [string]: string },
    Body: string,
}
Returnstable
A response object. Success is true for 2xx, false otherwise. Body is always a string (binary-safe).
local response = request({
    Url = "https://httpbin.org/post",
    Method = "POST",
    Headers = {
        ["Content-Type"] = "application/json",
        ["X-Client"] = "atherhub",
    },
    Body = game:GetService("HttpService"):JSONEncode({
        version = "1.0",
        nonce = math.random(),
    }),
})

print(response.StatusCode)  --> 200
print(response.Body)        --> {"args":{},"data":"{...}","headers":...}

Notice the response is synchronous from the script's point of view but actually executes off-thread internally — the call yields the script's coroutine until the response arrives. That means you can write linear request/response code without manual callbacks, but it also means you can't callrequest from a parallel context. Synchronise first.

Request idioms

A few patterns are worth knowing.

JSON over HTTP. The 90% case: encode a table to JSON, send as POST body, decode the response.

luau
local HttpService = game:GetService("HttpService")

local function postJson(url: string, payload: any)
    local response = request({
        Url = url,
        Method = "POST",
        Headers = { ["Content-Type"] = "application/json" },
        Body = HttpService:JSONEncode(payload),
    })
    if not response.Success then
        warn("request failed:", response.StatusCode, response.StatusMessage)
        return nil
    end
    local ok, data = pcall(HttpService.JSONDecode, HttpService, response.Body)
    return ok and data or nil
end

Custom user agents. By default SUNC sets a generic UA string. Some servers want you to identify yourself; request lets you set your own.

luau
request({
    Url = "https://api.example.com/me",
    Headers = {
        ["User-Agent"] = "MyScript/1.0",
        ["Authorization"] = "Bearer " .. token,
    },
})

Binary downloads. The body is a raw byte string. Pair with writefile to persist binaries — images, audio, archives.

luau
local response = request({ Url = "https://example.com/logo.png" })
if response.Success then writefile("logo.png", response.Body) end

WebSocket.connect

For long-lived, bidirectional connections, request is the wrong shape. WebSocket.connect opens a standard WS or WSS connection and returns an object with events and methods.

luau
WebSocket.connect(url: string): WebSocket

The returned WebSocket exposes:

  • :Send(data: string) — send a frame.
  • :Close() — close the connection.
  • .OnMessage: RBXScriptSignal — fires once per inbound frame.
  • .OnClose: RBXScriptSignal — fires when the connection is closed by either side.
luau
local ws = WebSocket.connect("wss://ws.example.com/")

ws.OnMessage:Connect(function(payload)
    print("server →", payload)
end)

ws.OnClose:Connect(function()
    print("connection closed")
end)

ws:Send('{"type":"hello"}')

task.wait(60)
ws:Close()
ReturnsWebSocket
A live connection object. OnMessage / OnClose are RBXScriptSignal connections like any other Roblox signal.

WebSocket idioms

A reconnecting client is the most common shape. WS connections drop for all kinds of reasons; long-running scripts should handle that automatically.

luau
local function connect(url: string, onMessage: (string) -> ())
    local ws
    local function loop()
        while true do
            ws = WebSocket.connect(url)
            ws.OnMessage:Connect(onMessage)
            ws.OnClose:Wait()  -- yield until the connection drops
            task.wait(2)        -- backoff before reconnect
        end
    end
    task.spawn(loop)
    return function() if ws then ws:Close() end end
end

local stop = connect("wss://ws.example.com/feed", function(msg)
    print("update:", msg)
end)

The pattern: open, listen, wait for close, sleep, reopen. The returned stop function gives the caller a clean way to tear down on script exit.

queue_on_teleport

When a Roblox game teleports the player to another place, all scripts (including yours) are killed. queue_on_teleport lets you stash a chunk of Luau that will auto-execute on the next loaded place. That's how scripts survive teleports.

luau
queue_on_teleport(code: string): ()
Returnsvoid
No return value. The code is queued. It executes on the next place the player teleports into; nothing happens if no teleport occurs in this session.
queue_on_teleport([[
    -- The same loader we ran on the first place
    loadstring(game:HttpGet("https://example.com/loader.lua"))()
]])

The string you queue is plain Luau source, not a function. It gets loadstring'd and run in the next place's script context. Keep it minimal — usually just a re-fetch of the main loader.

Error handling

Both request and WebSocket.connect will throw on entirely unreachable hosts (DNS failure, TLS handshake failure). Always wrap in pcall for anything you care about, and inspect StatusCode for the HTTP-level case.

luau
local ok, response = pcall(request, { Url = "https://offline.invalid/" })
if not ok then
    warn("network call threw:", response)
    return
end
if not response.Success then
    warn("HTTP error:", response.StatusCode)
    return
end
Don't retry blindly
A failed request is sometimes a soft failure (rate limit you should back off from) and sometimes a hard failure (404 — the endpoint isn't there). Don't loop on retry without inspecting StatusCode first.

Wrap-up

The network API is what unlocks most non-trivial script features: telemetry, remote config, live updates, content downloads. request is the workhorse, WebSocket is the long-lived sibling, and queue_on_teleport is the bridge between place sessions. None of these is complicated in isolation, but their combinations cover almost every reason a Roblox script needs to talk to the outside world.

Ather
Written by Ather

Ather is the lead developer behind Atherhub. He's been writing Luau and Roblox tooling for the better part of a decade, with a focus on the messy interface between game-script internals and the platforms that host them. Have feedback on this article? Drop it in the Discord.