> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rebind.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# rb.net

> Send HTTP requests, serve HTTP and WebSocket, connect to WebSocket servers, and use UDP.

`rb.net` sends HTTP requests, serves HTTP and WebSocket on a port of this
computer, connects to WebSocket servers, and sends and receives UDP datagrams.
A script reaches for it to call a web API, or to take commands from another
program or machine.

**Needs:** the `net` [permission](https://docs.rebind.gg/reference/sdk/modeline.md#permissions), which
the client calls and `ws_connect` also accept as `net:<host>`
[entries](https://docs.rebind.gg/reference/sdk/modeline.md#scoped-permissions); a coroutine for the
client calls (`get`, `post`, `put`, `patch`, `delete`, `head` and `request`),
so call them inside `rb.run` or `rb.async` (see [Where code runs](https://docs.rebind.gg/reference/sdk/globals.md#where-code-runs)).

This script posts JSON to an API and logs the reply, or the status when the
request did not succeed:

```luau theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
-- rebind: min_sdk=3.9.0 name=example version=1.0.0 permissions=net
rb.run(function()
  local resp = rb.net.post("https://api.example.com/data", '{"key": "value"}', {
    headers = { ["Content-Type"] = "application/json" },
    timeout = 5000,
  })
  if resp.status ~= 200 then
    rb.log.info("request failed", resp.status)
    return
  end
  rb.log.info(resp.body)
end)
```

## HTTP client

| Signature |
| - |
| `rb.net.get(url: string, options: RbHttpOptions?) -> HttpResponse` |
| `rb.net.post(url: string, body: string, options: RbHttpOptions?) -> HttpResponse` |
| `rb.net.put(url: string, body: string, options: RbHttpOptions?) -> HttpResponse` |
| `rb.net.patch(url: string, body: string, options: RbHttpOptions?) -> HttpResponse` |
| `rb.net.delete(url: string, options: RbHttpOptions?) -> HttpResponse` |
| `rb.net.head(url: string, options: RbHttpOptions?) -> HttpResponse` |
| `rb.net.request(options: HttpRequestOptions) -> HttpResponse` |

`options` is `{ headers = { [name] = value }, timeout = ms }`.
`rb.net.request` takes `{ method, url, body?, headers?, timeout? }` with method
`GET`, `POST`, `PUT`, `PATCH`, `DELETE` or `HEAD`. Without `timeout` a request
gives up after 30 seconds.

The response is `{ status, body, headers }` for any status; check `status`,
since a 404 or 500 does not raise. `body` holds the response bytes exactly, so
a PNG or a zip arrives intact; write it with `rb.file.write`. A body that is not
UTF-8 makes `rb.json.parse` raise, so check `status` and the `Content-Type`
header first. Request bodies are sent byte for byte as well. Binary bodies need
`min_sdk=3.18.0`.

Outside a coroutine a client call raises `Net.Get() must be called inside Run()`
(with the call's own name). A failed request raises
`HTTP request failed: <reason>`. To build the body from a table, pass it through
`rb.json.stringify`.

## rb.net.listen

`rb.net.listen(port: number, handler: (request: HttpRequest) -> HttpResponseSpec | string, options: { host: string? }?) -> ServerHandle`

Serves HTTP on `127.0.0.1:<port>`, reachable only from this computer. A third
argument `{ host = "0.0.0.0" }` binds every interface so other machines can
connect, and any other IP address binds that one; the `host` option needs
`min_sdk=3.18.0`. `host` must be an IP address; `"localhost"`
raises `Net.Listen(): host 'localhost' is not an IP address`. The
server has no authentication, so check a token in the request before acting on
it when it listens beyond this computer. The
handler receives `{ method, path, body, headers }` and returns
`{ status?, body?, headers? }` (status defaults to 200) or a string sent as a 200
body. Handlers run one request at a time per server; see the server dispatch
row in [Limits](https://docs.rebind.gg/reference/sdk/limits.md).
A request unanswered after 30 seconds gets `504` with body `Handler timeout`. A
second `listen` on a port this script already serves raises
`Port <n> already in use`. A port another program holds, or an address this
computer does not have, does not raise: the handle comes back, but nothing is
served, and the failure is written to Rebind's own log. The [ServerHandle](https://docs.rebind.gg/reference/sdk/handles.md) has `port` and `stop`.

The handler is a plain call, not a coroutine: build the response at once, and
start anything that waits with `rb.run` before returning. An uncaught error in
the handler stops the script, so wrap parsing in `pcall`. `path` includes the
query string, such as `/press?key=F5`, and header names keep the case the client
sent. Returning nothing sends `200` with an empty body. A targeted script answers
only while it is active; requests that arrive while it is inactive wait and get
`504` after 30 seconds.

## rb.net.ws\_listen

`rb.net.ws_listen(port: number, handlers: WsListenHandlers, options: { host: string? }?) -> WsServerHandle`

Serves WebSocket on `0.0.0.0:<port>`, reachable from the network. A third
argument `{ host = "127.0.0.1" }` binds one address instead. There is no
built-in rate limiting or authentication, so check a token before acting on a
message. Handlers, all optional:

| Handler | Arguments | Runs when |
| - | - | - |
| `on_connect` | `client` | a client completed the handshake |
| `on_message` | `client, payload, is_binary` | a frame arrived; binary frames arrive as byte strings |
| `on_close` | `client` | the connection closed |

`client` is a [WsClient](https://docs.rebind.gg/reference/sdk/handles.md) with an `id`. A message or frame larger than
64 MiB drops the connection. An error in a handler is logged to the script's
**Logs**, and the server keeps running. `host` must be an IP
address; `"localhost"` raises
`Net.WSListen(): host 'localhost' is not an IP address`. A second `ws_listen` on
a port this script already serves raises `WS port <n> already in use`, and a
port held elsewhere fails as with `listen`. For timing on the
remote-control path, see [Remote control](https://docs.rebind.gg/remote-control/protocol.md#timing).

## rb.net.ws\_connect

`rb.net.ws_connect(url: string, handlers: WsConnectHandlers) -> WsConnectHandle`

Connects to a `ws://` or `wss://` URL; TLS trusts the standard web root
certificates. Handlers `on_open()`, `on_message(payload, is_binary)` and
`on_close()` are optional. A failed connection runs `on_close` without
`on_open`. Up to 3 redirects are followed, none under `net:` entries.

## rb.net.udp\_listen

`rb.net.udp_listen(bind_ip: string, port: number, handler: (data: string, ip: string, port: number) -> ()) -> UdpHandle`

Binds a UDP socket to the address given, so `"127.0.0.1"` stays on this computer.
Port `0` picks a free port, readable from `socket.port`. The handler gets each
datagram's bytes and the sender. Datagrams of 2,048 bytes or more are dropped. An
address that is not an IP raises `Net.UDPListen: invalid bind address '<ip>'`;
one that cannot be bound, or a taken port, raises
`Net.UDPListen: could not bind <ip>:<port>: <reason>`. A handler error is logged
to the script's **Logs**, and the socket keeps running.
`socket:send_to(ip, port, bytes)` sends from the
bound address and returns `false` when the send fails or the socket is stopped.

## rb.net.local\_ip

`rb.net.local_ip() -> string?`

This computer's IPv4 address on its default route, or `nil`. It sends nothing.
It needs bare `net`; `net:<host>` entries do not grant it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.