Any language
There are exactly two things a backend has to do, and neither needs an SDK: sign a subscription, and publish an event. One is an HMAC, the other is an HTTP POST. Everything below is a few lines, and every signing snippet on this page is executed by our test suite against a real gateway before it ships.
On the frontend there is nothing to port at all — @socketly/client touches no browser-only global, so the same package runs in a browser, in React Native and Expo, and anywhere else JavaScript runs.
Authorize a subscription
Your backend already knows who its user is. This is where it says so — for private- and presence- channels, the client asks your endpoint and your endpoint signs. public- channels skip this entirely.
// app/api/socketly/auth/route.ts — Next.js
import { createHmac } from 'node:crypto';
export function socketly_authorize(secret, socketId, channel, userData) {
const appId = 'app_' + secret.split('_')[2];
let channelData = null;
let payload = socketId + ':' + channel;
if (channel.startsWith('presence-')) {
channelData = JSON.stringify(userData);
payload += ':' + channelData;
}
const signature = createHmac('sha256', secret).update(payload).digest('hex');
const result = { auth: appId + ':' + signature };
if (channelData) result.channel_data = channelData;
return result;
}
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 });
return Response.json(
socketly_authorize(process.env.SOCKETLY_SECRET, socket_id, channel_name, {
user_id: user.id,
user_info: { name: user.name },
}),
);
}Watch out — You probably want `@socketly/server` instead — it is this function plus a publish client, and it runs on Vercel Edge and Cloudflare Workers where `node:crypto` does not.
channel_data bytes the client sends back, so key order, slash escaping and separator spacing are all free — Go sorts map keys, PHP escapes slashes, and both verify fine. What does not verify is signing one serialization and then handing your framework the object to serialize again: that second pass is free to differ, and then nothing matches. Build the string once and use it twice, as every snippet above does.Publish an event
A POST with your secret key as a bearer token. The app id is the middle segment of that key, so there is no second value to keep in step with it.
const appId = 'app_' + process.env.SOCKETLY_SECRET.split('_')[2];
await fetch(`https://api.socketly.co/v1/apps/${appId}/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: 'Shipped.' },
}),
});On the frontend
@socketly/react exists because hooks are pleasant, not because React is required. Everything else uses @socketly/client directly, and it is the same object underneath.
'use client';
import { useChannel, usePresence } from '@socketly/react';
export function Room({ roomId }: { roomId: string }) {
const channel = `presence-room-${roomId}`;
const { subscribed } = useChannel(channel, {
'new-message': ({ data }) => append(data.text),
});
const { members, count } = usePresence(channel);
if (!subscribed) return <p>Connecting…</p>;
return <p>{count} online</p>;
}What is not on this page
A signing snippet we have not run. Every language above is executed by check-signatures.mjs on each build — it subscribes to a real presence channel with that language's own output and asserts the gateway accepts it — and a language whose runtime is missing skips out loud rather than passing quietly. If yours is not here, the construction is short enough to port from any of them: HMAC-SHA256 over socketId:channel, or socketId:channel:channelData for presence, hex-encoded, prefixed with your app id and a colon.