Socketino for developers

Realtime channels your own code can drive: a REST API with scoped keys and per-key rate limits, a live view of who is connected and what they are subscribed to, signed webhooks when a channel fills or empties, and client libraries that are drop-in replacements for pusher-js and pusher.

API keys

Get a key and authenticate

The Socketino REST API lets your own backend read the realtime state the dashboard shows: your projects, the websocket connections open right now, and the channels those connections are subscribed to.

Open your project in the Socketino dashboard and create an API key under API keys. Copy the secret while the key is being created and store it somewhere safe — the key list never shows it again. A key belongs to a single project, so the project is implied by the key.

Authenticate every request with HTTP Basic auth carrying only the key secret, base64-encoded, in the Authorization header.

# The Authorization header is HTTP Basic auth carrying only the key secret,
# with no username and no colon.
Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)

Every endpoint lives under https://api.socketino.com. Requests made with a key are rate limited per key; going over the limit returns 429.

Quick start

Your first three calls

List your projects, then look at who is connected and what they are listening to.

# List the projects the key can reach
curl https://api.socketino.com/api/projects \
  -H "Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)"

# See who is connected right now — one row per open websocket
curl "https://api.socketino.com/api/connections?projectId=PROJECT_ID&limit=50" \
  -H "Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)"

# See which channels those connections are subscribed to
curl "https://api.socketino.com/api/channel-subscriptions?projectId=PROJECT_ID&channelName=presence-room-1" \
  -H "Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)"

Browse the full API reference — every endpoint with its parameters, request body, responses and required scope.

Publishing

Signed with the project secret

Sending an event to a channel does not use an API key at all. The publish endpoints are signed with your project secret, the same scheme Pusher uses, so the server libraries you already have work unchanged. The project secret is a signing key: it lives on your server, and no endpoint ever returns it.

// Publishing is NOT part of the key-authenticated REST API: it is signed
// with your project secret, exactly the way Pusher's own SDKs sign requests.
// socketino is a drop-in replacement for the pusher 5.2.0 node package.
import Socketino from 'socketino'

const socketino = new Socketino({
  projectId: 'PROJECT_ID',
  secret: 'PROJECT_SECRET', // never leaves your server
})

await socketino.trigger('presence-room-1', 'message', { text: 'hello' })

In the browser, subscribe with the project key. socketino-js is a drop-in replacement for pusher-js 8.3.0, and socketino is a drop-in replacement for the pusher 5.2.0 node package — same constructors, same channel and presence API.

// socketino-js is a drop-in replacement for pusher-js 8.3.0
import Socketino from 'socketino-js'

const socketino = new Socketino('PROJECT_KEY')
const channel = socketino.subscribe('presence-room-1')

channel.bind('message', data => {
  console.log(data.text)
})

Scopes

Least privilege by default

Each key carries a list of scopes, so an integration that only needs to watch your channels never gets the ability to change your project. New keys start read-only; widen them explicitly in the dashboard. A request whose key is missing the scope an endpoint requires is refused with 403.

  • projects:readList your projects and read a single project.
  • projects:writeUpdate a project — its name, its allowed origins and its settings.
  • connections:readList the websocket connections open right now, with their socket ids.
  • channelsubscriptions:readList which connections are subscribed to which channels, filtered by channel name.

There is deliberately no scope for publishing or for the channel occupancy reads: those endpoints authenticate with the project secret signature instead, so an API key can never be used to send an event.

Webhooks

Signed webhooks

Add a webhook subscription to your project and Socketino POSTs the events you picked to your server as they happen — including the ones that originate inside the websocket layer, when a channel fills up or a presence member drops off.

  • channel.occupiedThe first subscriber joined a channel.
  • channel.vacatedThe last subscriber left a channel.
  • member.addedA member joined a presence channel.
  • member.removedA member left a presence channel, or its connection dropped.
  • project.createdA project was created.
  • project.updatedA project was edited.
POST https://your-server.com/socketino-webhook
X-Socketino-Event: channel.occupied
X-Socketino-Signature: t=1719000000,v1=<hmac-sha256 hex>
Content-Type: application/json

{
  "event": "channel.occupied",
  "timestamp": 1719000000,
  "data": { "channel": "presence-room-1", "type": "presence" }
}

Verify the signature

Every delivery carries an X-Socketino-Signature header of the form t=timestamp,v1=signature, where the signature is an HMAC-SHA256 of timestamp.body keyed by the subscription secret shown to you once when the subscription was created. Recompute it over the raw body and compare before trusting the payload.

import crypto from 'node:crypto'

// body must be the RAW request body, byte for byte
function verify(header, body, secret) {
  const [t, v1] = (header || '').split(',').map(part => part.split('=')[1])
  if (!t || !v1) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${body}`)
    .digest('hex')

  // timingSafeEqual throws on a length mismatch, so a malformed signature
  // has to be rejected before the comparison rather than by it.
  if (v1.length !== expected.length) return false

  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}

Delivery is one best-effort attempt with a five second timeout and no retries, so respond 2xx quickly and do the work asynchronously. An endpoint that fails twenty times in a row is disabled automatically and has to be re-enabled in the dashboard.

Start building