Quickstart
Connect, subscribe to a channel, and publish a message. Under 30 lines of client code, using raw WebSockets and a JWT.
Skip the raw WebSocket path and use the @metered-ca/realtime SDK instead. It does framing, ack correlation, reconnect, perfect-negotiation WebRTC, and TURN credential injection for you. Five lines to connect + subscribe + publish.
This quickstart is for any other stack (Go, Python, Java, Swift, Kotlin, Rust, Unity, etc.) — or if you want to see the underlying wire protocol.
This guide gets you talking to the server end-to-end. For richer integration patterns (per-message peer metadata, server-side publish, WebRTC with auto-injected TURN), jump to the Use Case Guides after this.
Prerequisites
- A Metered account — sign up if you don't have one.
- An App with Signalling enabled. From the dashboard, click Signalling in the sidebar and complete the one-question onboarding survey.
- A secret key created from Dashboard → Realtime Messaging → Keys → Create key, type
secret. You'll get a key id (sk_id_…) and a signing secret (sk_secret_…). Copy the signing secret when it's shown — it is never shown again.
You'll also need Node.js (or any JWT-capable backend) for token minting, and the ws package on the client side for this quickstart.
Step 1 — Mint a JWT (server-side)
Your backend signs an HS256 JWT with the sk_'s signing secret. The signalling server verifies it on connect.
const jwt = require("jsonwebtoken");
const KEY_ID = "sk_id_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // from dashboard
const SECRET = "sk_secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // shown once at create
function mintToken(peerId, channels = []) {
return jwt.sign(
{
iss: "your-backend",
sub: peerId,
channels, // channels this peer may interact with
permissions: ["publish", "subscribe", "presence", "send"],
exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour
},
SECRET,
{ algorithm: "HS256", header: { alg: "HS256", kid: KEY_ID } },
);
}
console.log(mintToken("alice", ["app_abc/room-1"]));
If you'd rather not embed JWT-signing into your backend, call POST /v1/tokens with your key pair (sk_id_…:sk_secret_…) as Bearer auth — Metered mints the JWT for you with the same claims. See REST API → Tokens.
Step 2 — Connect from the client
The wire protocol is the same regardless of where your client runs. The only differences between the browser and Node versions are how you construct the WebSocket and which events you listen on. Pick the variant for your environment.
Browser
Save as an HTML file and open in two tabs.
<!DOCTYPE html>
<html>
<head><title>Realtime Messaging Quickstart</title></head>
<body>
<h1>Open this in two tabs</h1>
<script>
const token = "<jwt from step 1>";
const ws = new WebSocket(`wss://rms.metered.ca/v1?token=${encodeURIComponent(token)}`);
ws.addEventListener("open", () => {
console.log("connected, waiting for welcome");
});
ws.addEventListener("message", (event) => {
const msg = JSON.parse(event.data);
console.log("←", msg);
if (msg.type === "welcome") {
ws.send(JSON.stringify({
type: "subscribe",
channel: "app_abc/room-1",
requestId: "sub-1",
}));
}
if (msg.type === "ack" && msg.requestId === "sub-1") {
ws.send(JSON.stringify({
type: "publish",
channel: "app_abc/room-1",
data: { hello: "world" },
requestId: "pub-1",
}));
}
});
ws.addEventListener("close", (event) => {
console.log("closed", event.code, event.reason);
});
</script>
</body>
</html>
Node.js
Install the ws package: npm install ws. Then save and run with node client.js. Run two copies with different JWTs (sub values) to see them broadcast to each other.
const WebSocket = require("ws");
const token = "<jwt from step 1>";
const ws = new WebSocket(`wss://rms.metered.ca/v1?token=${encodeURIComponent(token)}`);
ws.on("open", () => {
console.log("connected, waiting for welcome");
});
ws.on("message", (raw) => {
const msg = JSON.parse(raw.toString());
console.log("←", msg);
if (msg.type === "welcome") {
// server confirmed our auth. Subscribe to the room.
ws.send(JSON.stringify({
type: "subscribe",
channel: "app_abc/room-1",
requestId: "sub-1",
}));
}
if (msg.type === "ack" && msg.requestId === "sub-1") {
// subscribe succeeded. Publish a hello.
ws.send(JSON.stringify({
type: "publish",
channel: "app_abc/room-1",
data: { hello: "world" },
requestId: "pub-1",
}));
}
});
ws.on("close", (code, reason) => {
console.log("closed", code, reason.toString());
});
Run two copies with different peerIds (the sub claim in your JWTs) and you'll see them broadcast to each other.
What you saw
- Connect URL carries the JWT as
?token=. The server verifies the JWT before completing the WebSocket handshake. welcomeis the first message the server sends. It confirms auth and carries server metadata (yourpeerId,serverTime,maxMessageSize, plus an optional opaquemetadatapass-through from the JWT — WebRTC integrations conventionally puticeServersinsidemetadata).subscribejoins a channel. The server replies withack(success) orerror(e.g. channel not in your key'schannelPatterns).publishbroadcasts to every other subscriber on that channel. The server delivers it as amessageevent to each.- Closing the WebSocket triggers
peer-leftpresence events for every channel this peer was subscribed to.
Next
- Connect from a browser using a pklive key (no JWT needed) — see Authentication
- Wire up peerMetadata so other peers see your
userId/username/profilePic— see Presence & Metadata - Build a real app — pick a Use Case Guide