Quickstart

A presence-enabled room with live messages, in five steps. This assumes Next.js with the App Router; the same pieces apply to any React setup.

This is the finished thing, running live — the same five steps below produced it.

Connecting…
public-docs-demo
Ada

Nothing yet. Send from either side.

Grace

Nothing yet. Send from either side.

Each pane is its own connection. Sending publishes through this site's server using the secret key, and both panes receive it back over their own socket.

1. Create an app

In the dashboard, create an app and add your development origin (http://localhost:3000) to its allowed origins. You will be shown a public key and a secret key.

The secret key is shown once and is not recoverable afterwards. Copy it now, and keep it server-side only — it can publish to every channel in your app and sign any subscription.

2. Install

npm install @socketly/client @socketly/react

Add the keys to your environment:

NEXT_PUBLIC_SOCKETLY_KEY=pk_app_xxxxxxxx_yyyyyyyyyyyyyyyyyyyyyy
SOCKETLY_SECRET=sk_app_xxxxxxxx_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

3. Create the client

// lib/socketly.ts
import { Socketly } from '@socketly/client';

export const socketly = new Socketly({
  key: process.env.NEXT_PUBLIC_SOCKETLY_KEY!,
  authEndpoint: '/api/socketly/auth',
  url: process.env.NEXT_PUBLIC_SOCKETLY_URL, // omit in production
});

Then wrap your app:

// app/providers.tsx
'use client';

import { SocketlyProvider } from '@socketly/react';
import { socketly } from '@/lib/socketly';

export function Providers({ children }: { children: React.ReactNode }) {
  return <SocketlyProvider client={socketly}>{children}</SocketlyProvider>;
}

4. Add your auth endpoint

Anything beyond a public- channel needs a signature from your backend. The SDK calls this route automatically whenever it subscribes, and again after every reconnect — the signature is bound to the socket id, so it cannot be replayed on a different connection.

// app/api/socketly/auth/route.ts
import { authorizeChannel } from '@socketly/server';
import { getCurrentUser } from '@/lib/session';

export async function POST(request: Request) {
  const { socket_id, channel_name } = await request.json();

  // You decide who may subscribe. You already know who your user is.
  const user = await getCurrentUser();
  if (!user) return new Response('Unauthorized', { status: 401 });

  if (channel_name.startsWith('private-room-')) {
    const roomId = channel_name.replace('private-room-', '');
    if (!(await user.canAccessRoom(roomId))) {
      return new Response('Forbidden', { status: 403 });
    }
  }

  return Response.json(
    authorizeChannel({
      secret: process.env.SOCKETLY_SECRET!,
      socketId: socket_id,
      channel: channel_name,
      // Required for presence- channels only.
      userData: { user_id: user.id, user_info: { name: user.name } },
    }),
  );
}

5. Use it

'use client';

import { useState } from 'react';
import { useChannel, usePresence } from '@socketly/react';

export function Room({ roomId }: { roomId: string }) {
  const [messages, setMessages] = useState<string[]>([]);
  const channel = `presence-room-${roomId}`;

  const { subscribed, error } = useChannel(channel, {
    'new-message': (data) => setMessages((m) => [...m, data.data.text]),
  });

  const { members, count } = usePresence(channel);

  if (error) return <p>{error}</p>;
  if (!subscribed) return <p>Connecting…</p>;

  return (
    <div>
      <p>{count} online</p>
      <ul>
        {messages.map((text, i) => (
          <li key={i}>{text}</li>
        ))}
      </ul>
    </div>
  );
}

Publishing from your server

Clients can only publish when you explicitly enable client events, and then only to channels they are subscribed to. Most events should come from your backend:

// Anywhere on your server
await fetch(`https://api.socketly.co/v1/apps/${APP_ID}/events`, {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    authorization: `Bearer ${process.env.SOCKETLY_SECRET}`,
  },
  body: JSON.stringify({
    channels: ['presence-room-42'],
    event: 'new-message',
    data: { text: 'Hello from the server' },
  }),
});

If something is not working

  • Connection refused — check the origin you added to the app matches the one your browser is on, exactly, including the port.
  • auth_required — the channel is private- or presence- and authEndpoint is not set on the client.
  • auth_invalid — your endpoint signed a different socket id or channel than the one being subscribed. Pass through socket_id and channel_name unchanged.
  • Presence roster is empty — presence channels need userData with a user_id in the signature.