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/peer 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 (TURN credentials in the token, per-message peer metadata, server-side publish), 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.
- An sklive key created from Dashboard → Realtime Messaging → Keys → Create key, type
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 the sk_ as Bearer auth — Metered mints the JWT for you with the same claims. See REST API → Tokens.
Step 2 — Connect from the client
const WebSocket = require("ws"); // browser code uses the native WebSocket constructor
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 of client.js with different peerIds 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