Skip to content

Send your first realtime message

Publish a message from your backend and receive it in a browser.

Connect a browser tab to chat:room-1, then send it a message from your backend. You will register the channel’s namespace, create a backend key and issue a token that lets the browser subscribe. The examples use one chat application; replace the product ID, key and user lookup with your own.

Prerequisites

  • A Lepsto product with Owner or Admin access for namespace and key creation.
  • A backend where you can add a token endpoint, with a way to authenticate your users.
  • A frontend where you can open a browser connection.
  • Node 20 or later and npm for @lepsto/sdk.

1. Register the namespace

Open Realtime → Namespaces and select Create namespace. Name it chat and keep the defaults: visibility: authorized, with presence, client events and history disabled.

With MCP, call realtime_namespace_create with name: chat.

For the REST equivalent, see the Realtime API reference.

Register the namespace before proceeding. Without it, token issuance in step 3 returns 422 because no requested capability is allowed.

2. Create a public key

Open Product → Settings → Public Access and select Create public key. Enter a name, choose Selected toolkits, and select Realtime. This grants the key access to all keyed Realtime routes. Copy the lpk_… secret into your backend’s secret store before closing the dialog; it is shown only once. Set your product ID alongside it:

LEPSTO_REALTIME_API_KEY=lpk_0123456789abcdef…
LEPSTO_PRODUCT_ID=your-product-id

Wait about 30 seconds for the new key to reach the edge. A 401 during that interval does not establish that the key is invalid.

If you already use an rtk_… key, keep that value. Imported legacy keys continue to work and appear with an Imported badge.

Keep the public key on your backend. The browser will receive a separate delegated token. See authentication.

3. Install the SDK and mint a token

Install the package in your application:

npm install @lepsto/sdk

This tutorial uses @lepsto/sdk/realtime: its HTTP client accepts your product public key and product ID, while its socket client accepts a delegated token. The separate @lepsto/sdk/realtime/server import supports management operations with a bearer token and explicit base URL.

Create the HTTP client once when your backend starts:

// server/realtime.ts
import { createRealtime } from '@lepsto/sdk/realtime';

export const realtime = createRealtime({
  productId: process.env.LEPSTO_PRODUCT_ID!,
  publicKey: process.env.LEPSTO_REALTIME_API_KEY!,
});

Add a token endpoint at /api/realtime/token. Replace requireSignedInUser with your application’s session lookup and ensure the user may access chat:room-1. The SDK sends this request to POST /tokens/issue:

// server/routes/realtime-token.ts
import { realtime } from '../realtime.js';

export async function handleTokenRequest(req, res) {
  const user = await requireSignedInUser(req); // your own session

  const { token, gatewayUrl, expiresAt } = await realtime.tokens.issue({
    subject: user.id,
    channels: [{ name: 'chat:room-1', ops: ['subscribe'] }],
    ttlSeconds: 900,
  });

  res.json({ token, gatewayUrl, expiresAt });
}

The token requests only subscribe. With client events disabled, namespace policy removes any requested publish capability. Your backend will publish the message in step 5.

4. Subscribe from the browser

In the browser, call connectRealtime with a provider that fetches the token from your endpoint, then subscribe to the channel. Both clients are in the same package; no additional install is needed. The socket starts connecting in the background:

// app/chat.ts
import { connectRealtime } from '@lepsto/sdk/realtime';

const socket = connectRealtime({
  tokenProvider: async () => {
    const res = await fetch('/api/realtime/token', { credentials: 'include' });
    return res.json(); // { token, gatewayUrl }
  },
});

socket.onStateChange((state) => {
  console.log('realtime:', state); // connecting → connected
});

socket.subscribe('chat:room-1', (message) => {
  console.log(message.channel, message.data);
});

The socket factory is connectRealtime, and its class is RealtimeSocket. RealtimeClient names the HTTP client’s type; connect() is a method on the socket instance.

You can subscribe before the socket opens. It remembers the channel, subscribes when connected and resubscribes after a reconnect.

5. Publish from your backend

Wait for the tab to connect and subscribe, then run this on your backend. The SDK calls POST /messages:

import { realtime } from './realtime.js';

const result = await realtime.messages.publish({
  channel: 'chat:room-1',
  data: { from: 'alice', text: 'hello' },
});
// { channel: 'chat:room-1', published: true }

Check the browser console. The subscription handler should print the payload:

chat:room-1 { from: 'alice', text: 'hello' }

You have now sent a backend message to a subscribed tab. With history enabled, the publish response also contains offset and epoch, which clients use to recover missed messages. This tutorial leaves history off.

When something does not arrive

Symptom
What it means
The mint returns 422
Every capability you declared was stripped. The namespace is not registered, or its policy does not allow the operations you asked for.
401 public_key_required
No key reached the edge. Check that your backend is sending Authorization: Bearer <key>.
401 public_key_invalid
The key is not an active key of the product in the URL: unknown, revoked, mistyped, belonging to another product, or created on the other environment. Keys are per environment and do not carry across, and the response will not tell you which environment answered — check the edge origin before you suspect the key. A key created less than 30 seconds ago also lands here.
403 public_key_scope
The key is real but its scope does not reach Realtime. Widen it with Edit scope on Product → Settings → Public Access.
503 public_config_unavailable
The edge is starting up and fails closed. Retry with backoff; your key is fine.
Calls return 404
The namespace is not registered for your product.
The connection opens but nothing arrives
Check that the channel name in subscribe is exactly the one you publish to, and that the token was minted with subscribe on it.
A call rejects with status: 0 and code: 'public_key_required'
The request never left your process: the operation needs a key and the client was constructed without one. Pass publicKey to createRealtime.

Next steps

Was this page helpful?
Esc

Start typing to search the docs.

↑↓navigate↵select