For live feeds a request-per-poll is the wrong shape. ctx.ws keeps a persistent two-way connection open to a host you declared, held by the platform's broker (your sandbox still has no network access) and reconnected automatically. Capability: proxy:websocket.
@plugin.on_slash_command("feed-start")
def start_feed(ctx, event):
# Idempotent: call it whenever you need the connection alive.
ctx.ws.ensure(
"prices",
"wss://stream.example.com/v1",
secret_auth="STREAM_KEY", # optional, injected on connect
subscribe=['{"op": "subscribe", "channel": "ticker"}'],
)
ctx.interaction.respond(content="Feed running.")
@plugin.on_ws_message("prices")
def on_frame(ctx, msg):
# msg = {"name", "conn_id", "data", "binary"}; binary data is base64
ctx.kv.set("last_tick", msg["data"])
@plugin.on_ws_close("prices")
def on_close(ctx, msg):
ctx.log("price feed closed", level="warning")
ensure(name, url, *, secret_auth=None, auth=None, subscribe=None, binary=False)opens or confirms the named connection.subscribeframes are re-sent after every reconnect, so subscriptions survive drops. Setbinary=Truefor binary protocols; inbound binary frames arrive base64-encoded.ctx.ws.send(name, data)sends one frame (stris a text frame,bytesa binary frame).ctx.ws.close(name)hangs up.- Handlers:
@plugin.on_ws_message(name),on_ws_open,on_ws_close. Since v0.8.2 a wildcard like"rustplus:*"matches every connection sharing the prefix; the concretenameis in the frame. Frames for one connection arrive in order. A broker error surfaces as a close, there is no separate error handler. - Hosts: the exact host must be in your
proxy_domains_requested. When the server admin supplies the address at setup time (their own game server, say), callctx.ws.allow_host(host)from a slash handler run by a member with Admin or Manage Server — or from a dashboard handler where the platform verifies the viewer is at least a manager. Either way the platform checks the host is public, then remembers it for that server.ctx.ws.revoke_host(host)undoes it. - Tier and quotas:
proxy:websocketis a staff-reviewed capability. Unlike HTTP, the WebSocket allow-list matches the exact host (no automatic subdomains). Budgets per (server, plugin): 6 connects/min and a single 4 MB/min traffic budget shared across both directions. - Local testing:
MockContext.wsrecordsallowed_hostsandrevoked_hosts, andyourbot validatedetectsctx.wsusage so the capability isn't missing at upload.