Repository navigation
api reference
Complete API documentation for Soketi WebSocket and HTTP APIs.
Soketi implements the Pusher WebSocket protocol. Clients connect via WebSocket and communicate using JSON messages.
Connect to Soketi using the Pusher client library:
import Pusher from 'pusher-js';
const pusher = new Pusher('app-key', {
wsHost: 'localhost',
wsPort: 6001,
forceTLS: false,
encrypted: false,
enabledTransports: ['ws', 'wss'],
});| Event | Description |
|---|---|
pusher:connection_established |
Fired when connection is established |
pusher:error |
Fired when an error occurs |
Example:
pusher.connection.bind('connected', () => {
console.log('Connected to Soketi');
});
pusher.connection.bind('error', (err: any) => {
console.error('Connection error:', err);
});const channel = pusher.subscribe('my-channel');
channel.bind('my-event', (data: any) => {
console.log('Received:', data);
});Private channels require authentication:
const privateChannel = pusher.subscribe('private-my-channel');
privateChannel.bind('my-event', (data: any) => {
console.log('Received:', data);
});Presence channels track online users:
const presenceChannel = pusher.subscribe('presence-my-channel');
presenceChannel.bind('pusher:subscription_succeeded', (members: any) => {
console.log('Online users:', members.count);
members.each((member: any) => {
console.log('User:', member.id, member.info);
});
});
presenceChannel.bind('pusher:member_added', (member: any) => {
console.log('User joined:', member.id);
});
presenceChannel.bind('pusher:member_removed', (member: any) => {
console.log('User left:', member.id);
});Send events from client to other clients (must be enabled in app configuration):
channel.trigger('client-my-event', {
message: 'Hello from client!'
});Requirements:
- Channel must be private or presence
-
enable_client_messagesmust be true in app configuration - Event name must start with
client-
pusher.unsubscribe('my-channel');pusher.disconnect();The HTTP API allows you to trigger events, query channel information, and manage connections from your backend.
http://localhost:6001/apps/{app_id}
All HTTP API requests must be authenticated using HMAC SHA256 signature.
- Create auth string:
POST\n/apps/{app_id}/events\nauth_key={key}&auth_timestamp={timestamp}&auth_version=1.0&body_md5={md5} - Sign with HMAC SHA256 using app secret
- Add signature to query parameters
Example using Node.js:
import crypto from 'crypto';
function signRequest(
method: string,
path: string,
params: Record<string, string>,
body: string,
secret: string
): string {
const bodyMd5 = crypto.createHash('md5').update(body).digest('hex');
const sortedParams = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join('&');
const authString = `${method}\n${path}\n${sortedParams}&body_md5=${bodyMd5}`;
return crypto
.createHmac('sha256', secret)
.update(authString)
.digest('hex');
}Send events to channels from your backend.
Endpoint: POST /apps/{app_id}/events
Request Parameters:
| Parameter | Type | Description | Required |
|---|---|---|---|
name |
string | Event name | Yes |
data |
string or object | Event data (JSON string or object) | Yes |
channel |
string | Single channel name | No* |
channels |
array | Array of channel names | No* |
socket_id |
string | Socket ID to exclude from receiving the event | No |
*Either channel or channels must be provided
Request Body Examples:
Single Channel:
{
"name": "my-event",
"channel": "my-channel",
"data": "{\"message\": \"Hello World!\"}"
}Multiple Channels:
{
"name": "my-event",
"channels": ["channel-1", "channel-2", "channel-3"],
"data": "{\"message\": \"Hello World!\"}"
}With Socket ID Exclusion:
{
"name": "message-sent",
"channel": "chat-room",
"data": "{\"user\": \"John\", \"message\": \"Hi everyone!\"}",
"socket_id": "123.456"
}Response:
{}Status Codes:
| Code | Description |
|---|---|
200 |
Event triggered successfully |
400 |
Invalid request (validation error) |
401 |
Authentication failed |
403 |
Forbidden (rate limit exceeded) |
413 |
Payload too large |
Example using Pusher Server SDK (Recommended):
import Pusher from 'pusher';
const pusher = new Pusher({
appId: 'app-id',
key: 'app-key',
secret: 'app-secret',
host: 'localhost',
port: 6001,
useTLS: false,
});
// Trigger to single channel
await pusher.trigger('my-channel', 'my-event', {
message: 'Hello World!'
});
// Trigger to multiple channels
await pusher.trigger(['channel-1', 'channel-2'], 'my-event', {
message: 'Hello World!'
});
// Trigger with socket ID exclusion
await pusher.trigger('chat-room', 'message-sent', {
user: 'John',
message: 'Hi everyone!'
}, {
socket_id: '123.456'
});Example using cURL:
# Generate authentication parameters
APP_ID="app-id"
APP_KEY="app-key"
APP_SECRET="app-secret"
TIMESTAMP=$(date +%s)
BODY='{"name":"my-event","channel":"my-channel","data":"{\"message\":\"Hello!\"}"}'
BODY_MD5=$(echo -n "$BODY" | md5sum | cut -d' ' -f1)
# Create auth string and signature
AUTH_STRING="POST
/apps/$APP_ID/events
auth_key=$APP_KEY&auth_timestamp=$TIMESTAMP&auth_version=1.0&body_md5=$BODY_MD5"
SIGNATURE=$(echo -n "$AUTH_STRING" | openssl dgst -sha256 -hmac "$APP_SECRET" | cut -d' ' -f2)
# Make request
curl -X POST "http://localhost:6001/apps/$APP_ID/events?auth_key=$APP_KEY&auth_timestamp=$TIMESTAMP&auth_version=1.0&auth_signature=$SIGNATURE&body_md5=$BODY_MD5" \
-H "Content-Type: application/json" \
-d "$BODY"Example using Axios (Node.js):
import axios from 'axios';
import crypto from 'crypto';
async function triggerEvent(
appId: string,
appKey: string,
appSecret: string,
channel: string,
event: string,
data: any
) {
const timestamp = Math.floor(Date.now() / 1000);
const body = JSON.stringify({
name: event,
channel: channel,
data: JSON.stringify(data),
});
const bodyMd5 = crypto.createHash('md5').update(body).digest('hex');
const params = {
auth_key: appKey,
auth_timestamp: timestamp.toString(),
auth_version: '1.0',
body_md5: bodyMd5,
};
const sortedParams = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join('&');
const authString = `POST\n/apps/${appId}/events\n${sortedParams}`;
const signature = crypto
.createHmac('sha256', appSecret)
.update(authString)
.digest('hex');
const url = `http://localhost:6001/apps/${appId}/events?${sortedParams}&auth_signature=${signature}`;
const response = await axios.post(url, body, {
headers: { 'Content-Type': 'application/json' },
});
return response.data;
}
// Usage
await triggerEvent(
'app-id',
'app-key',
'app-secret',
'my-channel',
'my-event',
{ message: 'Hello World!' }
);Validation Limits:
| Limit | Default | Description |
|---|---|---|
| Event name length | 200 characters | Maximum length of event name |
| Channel name length | 200 characters | Maximum length of channel name |
| Payload size | 10 KB | Maximum size of event data |
| Channels per request | 100 | Maximum number of channels in one request |
These limits can be configured per application in the app configuration.
Trigger multiple events in a single request for better performance.
Endpoint: POST /apps/{app_id}/batch_events
Request Body:
{
"batch": [
{
"name": "event-1",
"channel": "channel-1",
"data": "{\"message\": \"Event 1\"}"
},
{
"name": "event-2",
"channels": ["channel-2", "channel-3"],
"data": "{\"message\": \"Event 2\"}"
},
{
"name": "event-3",
"channel": "channel-4",
"data": "{\"user\": \"John\", \"action\": \"joined\"}",
"socket_id": "123.456"
}
]
}Response:
{}Status Codes:
| Code | Description |
|---|---|
200 |
All events triggered successfully |
400 |
Invalid request (validation error) |
401 |
Authentication failed |
403 |
Forbidden (rate limit exceeded) |
Example using Pusher Server SDK:
import Pusher from 'pusher';
const pusher = new Pusher({
appId: 'app-id',
key: 'app-key',
secret: 'app-secret',
host: 'localhost',
port: 6001,
useTLS: false,
});
// Trigger batch events
await pusher.triggerBatch([
{
channel: 'channel-1',
name: 'event-1',
data: { message: 'Event 1' },
},
{
channel: 'channel-2',
name: 'event-2',
data: { message: 'Event 2' },
},
{
channel: 'channel-3',
name: 'event-3',
data: { message: 'Event 3' },
},
]);Example using Axios:
import axios from 'axios';
import crypto from 'crypto';
async function triggerBatchEvents(
appId: string,
appKey: string,
appSecret: string,
events: Array<{
channel: string;
name: string;
data: any;
socket_id?: string;
}>
) {
const timestamp = Math.floor(Date.now() / 1000);
const body = JSON.stringify({
batch: events.map(event => ({
name: event.name,
channel: event.channel,
data: JSON.stringify(event.data),
socket_id: event.socket_id,
})),
});
const bodyMd5 = crypto.createHash('md5').update(body).digest('hex');
const params = {
auth_key: appKey,
auth_timestamp: timestamp.toString(),
auth_version: '1.0',
body_md5: bodyMd5,
};
const sortedParams = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join('&');
const authString = `POST\n/apps/${appId}/batch_events\n${sortedParams}`;
const signature = crypto
.createHmac('sha256', appSecret)
.update(authString)
.digest('hex');
const url = `http://localhost:6001/apps/${appId}/batch_events?${sortedParams}&auth_signature=${signature}`;
const response = await axios.post(url, body, {
headers: { 'Content-Type': 'application/json' },
});
return response.data;
}
// Usage
await triggerBatchEvents(
'app-id',
'app-key',
'app-secret',
[
{ channel: 'channel-1', name: 'event-1', data: { message: 'Event 1' } },
{ channel: 'channel-2', name: 'event-2', data: { message: 'Event 2' } },
{ channel: 'channel-3', name: 'event-3', data: { message: 'Event 3' } },
]
);Batch Size Limits:
| Limit | Default | Description |
|---|---|---|
| Batch size | 10 events | Maximum number of events per batch request |
Benefits of Batch Events:
- Reduced Network Overhead: Single HTTP request instead of multiple
- Better Performance: Lower latency for multiple events
- Atomic Operations: All events are processed together
- Rate Limit Efficiency: Counts as one request for rate limiting
List all active channels with their subscription information.
Endpoint: GET /apps/{app_id}/channels
Query Parameters:
| Parameter | Type | Description | Required |
|---|---|---|---|
filter_by_prefix |
string | Filter channels by prefix | No |
info |
string | Comma-separated list of attributes to include | No |
Available Info Attributes:
-
user_count- Number of unique users (presence channels only) -
subscription_count- Number of active subscriptions
Response:
{
"channels": {
"channel-1": {
"subscription_count": 3,
"occupied": true
},
"channel-2": {
"subscription_count": 1,
"occupied": true
},
"presence-room": {
"subscription_count": 5,
"occupied": true,
"user_count": 5
}
}
}Example - List All Channels:
import Pusher from 'pusher';
const pusher = new Pusher({
appId: 'app-id',
key: 'app-key',
secret: 'app-secret',
host: 'localhost',
port: 6001,
useTLS: false,
});
// Get all channels
const result = await pusher.get({ path: '/channels' });
console.log(result.channels);Example - Filter by Prefix:
// Get only presence channels
const result = await pusher.get({
path: '/channels',
params: { filter_by_prefix: 'presence-' }
});
console.log(result.channels);Example - Include User Count:
// Get channels with user count info
const result = await pusher.get({
path: '/channels',
params: { info: 'user_count' }
});
console.log(result.channels);Example using Axios:
import axios from 'axios';
import crypto from 'crypto';
async function getChannels(
appId: string,
appKey: string,
appSecret: string,
filterPrefix?: string
) {
const timestamp = Math.floor(Date.now() / 1000);
const path = `/apps/${appId}/channels`;
const params: Record<string, string> = {
auth_key: appKey,
auth_timestamp: timestamp.toString(),
auth_version: '1.0',
};
if (filterPrefix) {
params.filter_by_prefix = filterPrefix;
}
const sortedParams = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join('&');
const authString = `GET\n${path}\n${sortedParams}`;
const signature = crypto
.createHmac('sha256', appSecret)
.update(authString)
.digest('hex');
const url = `http://localhost:6001${path}?${sortedParams}&auth_signature=${signature}`;
const response = await axios.get(url);
return response.data;
}
// Usage
const channels = await getChannels('app-id', 'app-key', 'app-secret');
console.log(channels);
// With filter
const presenceChannels = await getChannels(
'app-id',
'app-key',
'app-secret',
'presence-'
);
console.log(presenceChannels);Example using cURL:
APP_ID="app-id"
APP_KEY="app-key"
APP_SECRET="app-secret"
TIMESTAMP=$(date +%s)
# Create auth string
AUTH_STRING="GET
/apps/$APP_ID/channels
auth_key=$APP_KEY&auth_timestamp=$TIMESTAMP&auth_version=1.0"
SIGNATURE=$(echo -n "$AUTH_STRING" | openssl dgst -sha256 -hmac "$APP_SECRET" | cut -d' ' -f2)
# Make request
curl "http://localhost:6001/apps/$APP_ID/channels?auth_key=$APP_KEY&auth_timestamp=$TIMESTAMP&auth_version=1.0&auth_signature=$SIGNATURE"
# With filter
curl "http://localhost:6001/apps/$APP_ID/channels?auth_key=$APP_KEY&auth_timestamp=$TIMESTAMP&auth_version=1.0&auth_signature=$SIGNATURE&filter_by_prefix=presence-"Notes:
- Only channels with at least one active subscription are returned
- Empty channels are automatically excluded from the response
- The
occupiedfield is alwaystruefor returned channels
Get detailed information about a specific channel.
Endpoint: GET /apps/{app_id}/channels/{channel_name}
Query Parameters:
| Parameter | Type | Description | Required |
|---|---|---|---|
info |
string | Comma-separated list of attributes to include | No |
Available Info Attributes:
-
user_count- Number of unique users (presence channels only) -
subscription_count- Number of active subscriptions
Response for Regular Channel:
{
"occupied": true,
"subscription_count": 5
}Response for Presence Channel:
{
"occupied": true,
"subscription_count": 5,
"user_count": 5
}Example using Pusher SDK:
import Pusher from 'pusher';
const pusher = new Pusher({
appId: 'app-id',
key: 'app-key',
secret: 'app-secret',
host: 'localhost',
port: 6001,
useTLS: false,
});
// Get channel info
const result = await pusher.get({
path: '/channels/my-channel',
params: { info: 'subscription_count' }
});
console.log(result);
// Get presence channel info with user count
const presenceResult = await pusher.get({
path: '/channels/presence-room',
params: { info: 'user_count,subscription_count' }
});
console.log(presenceResult);Example using Axios:
import axios from 'axios';
import crypto from 'crypto';
async function getChannelInfo(
appId: string,
appKey: string,
appSecret: string,
channelName: string,
info?: string[]
) {
const timestamp = Math.floor(Date.now() / 1000);
const path = `/apps/${appId}/channels/${encodeURIComponent(channelName)}`;
const params: Record<string, string> = {
auth_key: appKey,
auth_timestamp: timestamp.toString(),
auth_version: '1.0',
};
if (info && info.length > 0) {
params.info = info.join(',');
}
const sortedParams = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join('&');
const authString = `GET\n${path}\n${sortedParams}`;
const signature = crypto
.createHmac('sha256', appSecret)
.update(authString)
.digest('hex');
const url = `http://localhost:6001${path}?${sortedParams}&auth_signature=${signature}`;
const response = await axios.get(url);
return response.data;
}
// Usage
const channelInfo = await getChannelInfo(
'app-id',
'app-key',
'app-secret',
'my-channel',
['subscription_count']
);
console.log(channelInfo);
// Presence channel
const presenceInfo = await getChannelInfo(
'app-id',
'app-key',
'app-secret',
'presence-room',
['user_count', 'subscription_count']
);
console.log(presenceInfo);Example using cURL:
APP_ID="app-id"
APP_KEY="app-key"
APP_SECRET="app-secret"
CHANNEL_NAME="my-channel"
TIMESTAMP=$(date +%s)
# URL encode channel name
ENCODED_CHANNEL=$(echo -n "$CHANNEL_NAME" | jq -sRr @uri)
# Create auth string
AUTH_STRING="GET
/apps/$APP_ID/channels/$ENCODED_CHANNEL
auth_key=$APP_KEY&auth_timestamp=$TIMESTAMP&auth_version=1.0"
SIGNATURE=$(echo -n "$AUTH_STRING" | openssl dgst -sha256 -hmac "$APP_SECRET" | cut -d' ' -f2)
# Make request
curl "http://localhost:6001/apps/$APP_ID/channels/$ENCODED_CHANNEL?auth_key=$APP_KEY&auth_timestamp=$TIMESTAMP&auth_version=1.0&auth_signature=$SIGNATURE"Status Codes:
| Code | Description |
|---|---|
200 |
Channel info retrieved successfully |
401 |
Authentication failed |
404 |
Channel not found or has no subscribers |
Get list of users in a presence channel with their user information.
Endpoint: GET /apps/{app_id}/channels/{channel_name}/users
Requirements:
- Channel must be a presence channel (name starts with
presence-) - Channel must have at least one active subscriber
Response:
{
"users": [
{
"id": "user-1"
},
{
"id": "user-2",
"user_info": {
"name": "John Doe",
"email": "john@example.com"
}
},
{
"id": "user-3",
"user_info": {
"name": "Jane Smith",
"email": "jane@example.com",
"avatar": "https://example.com/avatar.jpg"
}
}
]
}Example using Pusher SDK:
import Pusher from 'pusher';
const pusher = new Pusher({
appId: 'app-id',
key: 'app-key',
secret: 'app-secret',
host: 'localhost',
port: 6001,
useTLS: false,
});
// Get users in presence channel
const result = await pusher.get({
path: '/channels/presence-room/users'
});
console.log('Users:', result.users);
result.users.forEach((user: any) => {
console.log(`User ${user.id}:`, user.user_info);
});Example using Axios:
import axios from 'axios';
import crypto from 'crypto';
async function getChannelUsers(
appId: string,
appKey: string,
appSecret: string,
channelName: string
) {
const timestamp = Math.floor(Date.now() / 1000);
const path = `/apps/${appId}/channels/${encodeURIComponent(channelName)}/users`;
const params = {
auth_key: appKey,
auth_timestamp: timestamp.toString(),
auth_version: '1.0',
};
const sortedParams = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join('&');
const authString = `GET\n${path}\n${sortedParams}`;
const signature = crypto
.createHmac('sha256', appSecret)
.update(authString)
.digest('hex');
const url = `http://localhost:6001${path}?${sortedParams}&auth_signature=${signature}`;
const response = await axios.get(url);
return response.data;
}
// Usage
const users = await getChannelUsers(
'app-id',
'app-key',
'app-secret',
'presence-room'
);
console.log('Online users:', users.users.length);
users.users.forEach((user: any) => {
console.log(`- ${user.id}: ${user.user_info?.name || 'Anonymous'}`);
});Example using cURL:
APP_ID="app-id"
APP_KEY="app-key"
APP_SECRET="app-secret"
CHANNEL_NAME="presence-room"
TIMESTAMP=$(date +%s)
# URL encode channel name
ENCODED_CHANNEL=$(echo -n "$CHANNEL_NAME" | jq -sRr @uri)
# Create auth string
AUTH_STRING="GET
/apps/$APP_ID/channels/$ENCODED_CHANNEL/users
auth_key=$APP_KEY&auth_timestamp=$TIMESTAMP&auth_version=1.0"
SIGNATURE=$(echo -n "$AUTH_STRING" | openssl dgst -sha256 -hmac "$APP_SECRET" | cut -d' ' -f2)
# Make request
curl "http://localhost:6001/apps/$APP_ID/channels/$ENCODED_CHANNEL/users?auth_key=$APP_KEY&auth_timestamp=$TIMESTAMP&auth_version=1.0&auth_signature=$SIGNATURE"Status Codes:
| Code | Description |
|---|---|
200 |
Users retrieved successfully |
400 |
Channel is not a presence channel |
401 |
Authentication failed |
404 |
Channel not found or has no subscribers |
Notes:
- Only works for presence channels (channels starting with
presence-) - Returns all unique users currently subscribed to the channel
- The
user_infofield contains the data provided during authentication - If no
user_infowas provided during auth, only theidfield is returned
Terminate all WebSocket connections for a specific user across all channels.
Endpoint: POST /apps/{app_id}/users/{user_id}/terminate_connections
Use Cases:
- Force logout a user from all devices
- Revoke access when user permissions change
- Security: Terminate compromised user sessions
- Administrative actions
Response:
{}Status Codes:
| Code | Description |
|---|---|
200 |
Connections terminated successfully |
401 |
Authentication failed |
500 |
Internal server error |
Behavior:
- Server identifies all connections associated with the user ID
- Sends
pusher:errorevent with code4009to each connection - Closes all WebSocket connections for that user
- User is removed from all presence channels
Example using Pusher SDK:
import Pusher from 'pusher';
const pusher = new Pusher({
appId: 'app-id',
key: 'app-key',
secret: 'app-secret',
host: 'localhost',
port: 6001,
useTLS: false,
});
// Terminate all connections for a user
await pusher.post({
path: '/users/user-123/terminate_connections'
});
console.log('User connections terminated');Example using Axios:
import axios from 'axios';
import crypto from 'crypto';
async function terminateUserConnections(
appId: string,
appKey: string,
appSecret: string,
userId: string
) {
const timestamp = Math.floor(Date.now() / 1000);
const path = `/apps/${appId}/users/${encodeURIComponent(userId)}/terminate_connections`;
const body = '';
const bodyMd5 = crypto.createHash('md5').update(body).digest('hex');
const params = {
auth_key: appKey,
auth_timestamp: timestamp.toString(),
auth_version: '1.0',
body_md5: bodyMd5,
};
const sortedParams = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join('&');
const authString = `POST\n${path}\n${sortedParams}`;
const signature = crypto
.createHmac('sha256', appSecret)
.update(authString)
.digest('hex');
const url = `http://localhost:6001${path}?${sortedParams}&auth_signature=${signature}`;
const response = await axios.post(url, body, {
headers: { 'Content-Type': 'application/json' },
});
return response.data;
}
// Usage
await terminateUserConnections('app-id', 'app-key', 'app-secret', 'user-123');
console.log('User connections terminated');Example using cURL:
APP_ID="app-id"
APP_KEY="app-key"
APP_SECRET="app-secret"
USER_ID="user-123"
TIMESTAMP=$(date +%s)
BODY=""
BODY_MD5=$(echo -n "$BODY" | md5sum | cut -d' ' -f1)
# URL encode user ID
ENCODED_USER=$(echo -n "$USER_ID" | jq -sRr @uri)
# Create auth string
AUTH_STRING="POST
/apps/$APP_ID/users/$ENCODED_USER/terminate_connections
auth_key=$APP_KEY&auth_timestamp=$TIMESTAMP&auth_version=1.0&body_md5=$BODY_MD5"
SIGNATURE=$(echo -n "$AUTH_STRING" | openssl dgst -sha256 -hmac "$APP_SECRET" | cut -d' ' -f2)
# Make request
curl -X POST "http://localhost:6001/apps/$APP_ID/users/$ENCODED_USER/terminate_connections?auth_key=$APP_KEY&auth_timestamp=$TIMESTAMP&auth_version=1.0&auth_signature=$SIGNATURE&body_md5=$BODY_MD5" \
-H "Content-Type: application/json" \
-d "$BODY"Client-Side Handling:
When a user's connections are terminated, they will receive an error event:
pusher.connection.bind('error', (err: any) => {
if (err.error && err.error.code === 4009) {
console.log('Connection terminated by server');
// Handle forced logout
// Redirect to login page or show message
window.location.href = '/login?reason=session_terminated';
}
});Complete Example - Force Logout System:
// Backend: Terminate user connections
import Pusher from 'pusher';
import express from 'express';
const app = express();
const pusher = new Pusher({
appId: 'app-id',
key: 'app-key',
secret: 'app-secret',
host: 'localhost',
port: 6001,
useTLS: false,
});
// Admin endpoint to force logout a user
app.post('/admin/force-logout/:userId', async (req, res) => {
const userId = req.params.userId;
try {
// Terminate all Pusher connections for the user
await pusher.post({
path: `/users/${userId}/terminate_connections`
});
// Also invalidate user sessions in your database
await invalidateUserSessions(userId);
res.json({ success: true, message: 'User logged out from all devices' });
} catch (error) {
console.error('Failed to terminate connections:', error);
res.status(500).json({ error: 'Failed to logout user' });
}
});
// Frontend: Handle forced logout
const pusher = new Pusher('app-key', {
wsHost: 'localhost',
wsPort: 6001,
forceTLS: false,
});
pusher.connection.bind('error', (err: any) => {
if (err.error && err.error.code === 4009) {
// User was forcefully logged out
localStorage.clear();
sessionStorage.clear();
window.location.href = '/login?reason=forced_logout';
}
});Soketi uses HMAC SHA256 signatures for authentication. There are three types of authentication:
- HTTP API Authentication - For backend API requests
- Private Channel Authentication - For private channel subscriptions
- Presence Channel Authentication - For presence channel subscriptions with user info
All HTTP API requests must be authenticated using HMAC SHA256 signature.
Add these query parameters to your API requests:
| Parameter | Description | Required |
|---|---|---|
auth_key |
Your application key | Yes |
auth_timestamp |
Unix timestamp in seconds | Yes |
auth_version |
Authentication version (use "1.0") | Yes |
auth_signature |
HMAC SHA256 signature | Yes |
body_md5 |
MD5 hash of request body | Yes (for POST) |
The signature is generated using HMAC SHA256:
- Create the auth string:
METHOD\nPATH\nQUERY_STRING - Sign with HMAC SHA256 using your app secret
- Add the signature to query parameters
Example - Manual Signature Generation (Node.js):
import crypto from 'crypto';
function generateAuthSignature(
method: string,
path: string,
appKey: string,
appSecret: string,
body: string = ''
): string {
// Generate timestamp
const timestamp = Math.floor(Date.now() / 1000);
// Generate body MD5 (for POST requests)
const bodyMd5 = body ? crypto.createHash('md5').update(body).digest('hex') : '';
// Build query parameters (sorted alphabetically)
const params: Record<string, string> = {
auth_key: appKey,
auth_timestamp: timestamp.toString(),
auth_version: '1.0',
};
if (bodyMd5) {
params.body_md5 = bodyMd5;
}
// Sort parameters and create query string
const sortedParams = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join('&');
// Create auth string
const authString = `${method}\n${path}\n${sortedParams}`;
// Generate signature
const signature = crypto
.createHmac('sha256', appSecret)
.update(authString)
.digest('hex');
return signature;
}
// Usage example
const method = 'POST';
const path = '/apps/app-id/events';
const appKey = 'your-app-key';
const appSecret = 'your-app-secret';
const body = JSON.stringify({
name: 'my-event',
channel: 'my-channel',
data: JSON.stringify({ message: 'Hello!' })
});
const signature = generateAuthSignature(method, path, appKey, appSecret, body);
console.log('Signature:', signature);Example - Using Pusher SDK (Recommended):
import Pusher from 'pusher';
const pusher = new Pusher({
appId: 'app-id',
key: 'app-key',
secret: 'app-secret',
host: 'localhost',
port: 6001,
useTLS: false,
});
// The SDK handles authentication automatically
await pusher.trigger('my-channel', 'my-event', {
message: 'Hello World!'
});Important: Authentication timestamps must be within 600 seconds (10 minutes) of the server time. This prevents replay attacks.
If your timestamp is outside this window, you'll receive a 401 Unauthorized error.
Example - Timestamp Validation:
// Current timestamp (valid)
const validTimestamp = Math.floor(Date.now() / 1000);
// Timestamp from 5 minutes ago (valid)
const stillValid = validTimestamp - 300;
// Timestamp from 15 minutes ago (invalid)
const expired = validTimestamp - 900; // Will be rejectedWhen a client subscribes to a private channel, they must authenticate via your backend.
- Client attempts to subscribe to a private channel
- Pusher client makes a POST request to your auth endpoint
- Your backend validates the user and generates an auth signature
- Client receives the signature and completes subscription
Client Request:
The Pusher client will make a POST request to your auth endpoint (default: /pusher/auth):
POST /pusher/auth
Content-Type: application/x-www-form-urlencoded
socket_id=123.456&channel_name=private-my-channel
Server Response:
Your backend must return a signed authentication token:
import Pusher from 'pusher';
import express from 'express';
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const pusher = new Pusher({
appId: 'app-id',
key: 'app-key',
secret: 'app-secret',
host: 'localhost',
port: 6001,
useTLS: false,
});
app.post('/pusher/auth', (req, res) => {
const socketId = req.body.socket_id;
const channel = req.body.channel_name;
// Validate user authentication (your logic here)
if (!req.session || !req.session.user) {
return res.status(403).json({ error: 'Unauthorized' });
}
// Validate channel access (your logic here)
if (!canAccessChannel(req.session.user, channel)) {
return res.status(403).json({ error: 'Forbidden' });
}
// Generate auth signature
const authResponse = pusher.authorizeChannel(socketId, channel);
res.json(authResponse);
});Response Format:
{
"auth": "app-key:signature"
}Manual Signature Generation:
If you're not using the Pusher SDK, you can generate the signature manually:
import crypto from 'crypto';
function generateChannelAuth(
appKey: string,
appSecret: string,
socketId: string,
channelName: string
): string {
// Create string to sign: socket_id:channel_name
const toSign = `${socketId}:${channelName}`;
// Generate HMAC SHA256 signature
const signature = crypto
.createHmac('sha256', appSecret)
.update(toSign)
.digest('hex');
// Return in format: app_key:signature
return `${appKey}:${signature}`;
}
// Usage
const auth = generateChannelAuth(
'app-key',
'app-secret',
'123.456',
'private-my-channel'
);
// Response
res.json({ auth });Presence channels require additional user information to track online users.
Server Response:
import Pusher from 'pusher';
app.post('/pusher/auth', (req, res) => {
const socketId = req.body.socket_id;
const channel = req.body.channel_name;
// Validate user authentication
if (!req.session || !req.session.user) {
return res.status(403).json({ error: 'Unauthorized' });
}
// For presence channels, provide user data
if (channel.startsWith('presence-')) {
const presenceData = {
user_id: req.session.user.id,
user_info: {
name: req.session.user.name,
email: req.session.user.email,
avatar: req.session.user.avatar,
},
};
const authResponse = pusher.authorizeChannel(socketId, channel, presenceData);
return res.json(authResponse);
}
// For private channels, no user data needed
const authResponse = pusher.authorizeChannel(socketId, channel);
res.json(authResponse);
});Response Format:
{
"auth": "app-key:signature",
"channel_data": "{\"user_id\":\"123\",\"user_info\":{\"name\":\"John\",\"email\":\"john@example.com\"}}"
}Manual Signature Generation for Presence Channels:
import crypto from 'crypto';
function generatePresenceAuth(
appKey: string,
appSecret: string,
socketId: string,
channelName: string,
userId: string,
userInfo: Record<string, any>
): { auth: string; channel_data: string } {
// Create channel data
const channelData = JSON.stringify({
user_id: userId,
user_info: userInfo,
});
// Create string to sign: socket_id:channel_name:channel_data
const toSign = `${socketId}:${channelName}:${channelData}`;
// Generate HMAC SHA256 signature
const signature = crypto
.createHmac('sha256', appSecret)
.update(toSign)
.digest('hex');
return {
auth: `${appKey}:${signature}`,
channel_data: channelData,
};
}
// Usage
const authResponse = generatePresenceAuth(
'app-key',
'app-secret',
'123.456',
'presence-room',
'user-123',
{ name: 'John Doe', email: 'john@example.com' }
);
res.json(authResponse);For user-specific features, you can authenticate users with the pusher:signin event.
Client Side:
// After connection is established
pusher.signin();Server Side:
app.post('/pusher/user-auth', (req, res) => {
const socketId = req.body.socket_id;
// Validate user session
if (!req.session || !req.session.user) {
return res.status(403).json({ error: 'Unauthorized' });
}
const userData = JSON.stringify({
id: req.session.user.id,
name: req.session.user.name,
email: req.session.user.email,
});
// Generate user auth signature
const toSign = `${socketId}::user::${userData}`;
const signature = crypto
.createHmac('sha256', 'app-secret')
.update(toSign)
.digest('hex');
res.json({
auth: `app-key:${signature}`,
user_data: userData,
});
});Common Authentication Errors:
| Error | Description | Solution |
|---|---|---|
401 Unauthorized |
Invalid signature or expired timestamp | Verify your app secret and timestamp |
403 Forbidden |
User not authorized for channel | Check your authorization logic |
4008 (WebSocket) |
Connection is unauthorized | Verify app key and credentials |
Example - Error Handling:
// Client side
const channel = pusher.subscribe('private-my-channel');
channel.bind('pusher:subscription_error', (status: any) => {
if (status === 403) {
console.error('Access denied to channel');
} else if (status === 401) {
console.error('Authentication failed');
}
});
// Server side
app.post('/pusher/auth', (req, res) => {
try {
const socketId = req.body.socket_id;
const channel = req.body.channel_name;
// Your validation logic
if (!isValidUser(req)) {
return res.status(403).json({
error: 'Forbidden',
message: 'User not authorized for this channel'
});
}
const authResponse = pusher.authorizeChannel(socketId, channel);
res.json(authResponse);
} catch (error) {
console.error('Auth error:', error);
res.status(500).json({
error: 'Internal Server Error',
message: 'Failed to generate auth signature'
});
}
});- No authentication required
- Anyone can subscribe
- Channel name doesn't start with
private-orpresence-
- Require authentication
- Channel name starts with
private- - Used for user-specific or sensitive data
- Require authentication with user info
- Channel name starts with
presence- - Track online users
- Provide member added/removed events
| Event | Description |
|---|---|
pusher:connection_established |
Connection successful |
pusher:error |
Error occurred |
pusher:subscription_succeeded |
Channel subscription successful |
pusher:subscription_error |
Channel subscription failed |
pusher:member_added |
User joined presence channel |
pusher:member_removed |
User left presence channel |
You can trigger custom events with any name (except those starting with pusher: or pusher_internal:).
| Code | Description |
|---|---|
| 4000 | Application does not exist |
| 4001 | Application disabled |
| 4003 | Application over connection quota |
| 4004 | Path not found |
| 4005 | Invalid version string format |
| 4006 | Unsupported protocol version |
| 4007 | No protocol version supplied |
| 4008 | Connection is unauthorized |
| 4009 | Connection limit exceeded |
| 4100 | Over capacity |
| 4200 | Generic error |
| 4201 | Pong reply not received in time |
| 4202 | Closed after inactivity |
Rate limits are configured per application:
-
max_connections: Maximum concurrent connections -
max_backend_events_per_second: Backend event rate limit -
max_client_events_per_second: Client event rate limit -
max_read_requests_per_second: Read request rate limit
When rate limits are exceeded, requests will receive a 429 status code.
Soketi provides several endpoints for health checks, monitoring, and observability.
Basic health check endpoint to verify the server is running.
Endpoint: GET /
Authentication: None required
Response:
{
"ok": true
}Example:
curl http://localhost:6001/Use Case: Basic uptime monitoring
Check if the server is ready to accept connections.
Endpoint: GET /ready
Authentication: None required
Response (Ready):
{
"ready": true
}Response (Not Ready):
{
"ready": false,
"reason": "server is shutting down"
}Status Codes:
| Code | Description |
|---|---|
200 |
Server is ready |
503 |
Server is not ready (shutting down) |
Example:
curl http://localhost:6001/readyUse Case: Kubernetes readiness probes, load balancer health checks
Check if the server can accept traffic based on memory usage.
Endpoint: GET /accept-traffic
Authentication: None required
Response (Can Accept):
{
"accept_traffic": true,
"used_memory_mb": 512,
"threshold_mb": 1024
}Response (Cannot Accept):
{
"accept_traffic": false,
"reason": "memory threshold exceeded",
"used_memory_mb": 1100,
"threshold_mb": 1024
}Status Codes:
| Code | Description |
|---|---|
200 |
Server can accept traffic |
503 |
Server cannot accept traffic (memory threshold exceeded or shutting down) |
Example:
curl http://localhost:6001/accept-trafficConfiguration:
The memory threshold can be configured in your server configuration:
{
"http_api": {
"accept_traffic_memory_threshold_mb": 1024
}
}Use Case: Load balancer health checks with memory awareness
Get detailed memory usage information.
Endpoint: GET /usage
Authentication: None required
Response:
{
"memory": {
"total_mb": 2048,
"used_mb": 512,
"free_mb": 1536,
"available_mb": 1600,
"usage_percent": 25.0
}
}Example:
curl http://localhost:6001/usageUse Case: Monitoring dashboards, capacity planning
Get Prometheus-compatible metrics for monitoring.
Endpoint: GET /metrics
Authentication: None required
Query Parameters:
| Parameter | Description | Default |
|---|---|---|
format |
Response format: text or json
|
text |
Response (Plaintext - Prometheus format):
# HELP soketi_connections_total Total number of WebSocket connections
# TYPE soketi_connections_total gauge
soketi_connections_total 42
# HELP soketi_messages_sent_total Total number of messages sent
# TYPE soketi_messages_sent_total counter
soketi_messages_sent_total 1234
# HELP soketi_messages_received_total Total number of messages received
# TYPE soketi_messages_received_total counter
soketi_messages_received_total 5678
Response (JSON format):
{
"connections_total": 42,
"messages_sent_total": 1234,
"messages_received_total": 5678,
"channels_total": 15,
"http_requests_total": 890
}Example - Plaintext (Prometheus):
curl http://localhost:6001/metricsExample - JSON:
curl "http://localhost:6001/metrics?format=json"Prometheus Configuration:
Add this to your prometheus.yml:
scrape_configs:
- job_name: 'soketi'
static_configs:
- targets: ['localhost:6001']
metrics_path: '/metrics'
scrape_interval: 15sUse Case: Prometheus monitoring, Grafana dashboards
Note: Metrics endpoint is only available if metrics are enabled in the server configuration.
Docker Health Check:
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:6001/ready || exit 1Kubernetes Probes:
apiVersion: v1
kind: Pod
metadata:
name: soketi
spec:
containers:
- name: soketi
image: soketi:latest
ports:
- containerPort: 6001
livenessProbe:
httpGet:
path: /
port: 6001
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 6001
initialDelaySeconds: 5
periodSeconds: 5Load Balancer Health Check (Nginx):
upstream soketi_backend {
server soketi:6001 max_fails=3 fail_timeout=30s;
# Health check
check interval=5000 rise=2 fall=3 timeout=3000 type=http;
check_http_send "GET /accept-traffic HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx;
}- Getting Started - Quick start guide
- Examples - Code examples
- Configuration - Configuration reference