Skip to main content

Quickstart

Connect, subscribe to a channel, and publish a message. Under 30 lines of client code, using raw WebSockets and a JWT.

Using JavaScript / TypeScript?

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

  1. A Metered account — sign up if you don't have one.
  2. An App with Signalling enabled. From the dashboard, click Signalling in the sidebar and complete the one-question onboarding survey.
  3. 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.

mint-token.js
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"]));
Use the REST API instead

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.

client.html
<!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.

client.js
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

  1. Connect URL carries the JWT as ?token=. The server verifies the JWT before completing the WebSocket handshake.
  2. welcome is the first message the server sends. It confirms auth and carries server metadata (your peerId, serverTime, maxMessageSize, plus an optional opaque metadata pass-through from the JWT — WebRTC integrations conventionally put iceServers inside metadata).
  3. subscribe joins a channel. The server replies with ack (success) or error (e.g. channel not in your key's channelPatterns).
  4. publish broadcasts to every other subscriber on that channel. The server delivers it as a message event to each.
  5. Closing the WebSocket triggers peer-left presence events for every channel this peer was subscribed to.

Next