Getting Started
Apinator provides WebSocket infrastructure for adding real-time features to any application. Publish events from your server, receive them instantly on connected clients.
The architecture is simple: your backend publishes events through the REST API using HMAC-signed requests, and clients subscribe to channels over WebSocket connections. Apinator handles fan-out, reconnection, and presence tracking.
Installation
Install the client SDK in your frontend application and a server SDK in your backend. The client SDK connects via WebSocket; server SDKs sign API requests with HMAC.
npm install @apinator/jsnpm install @apinator/serverpip install apinator-servergo get github.com/apinator-io/apinator-goQuick Example
Here is a minimal example: the client subscribes to a channel and binds to an event, then the server publishes a message to that channel.
import { RealtimeClient } from '@apinator/js'
const client = new RealtimeClient({
appKey: 'your-app-key',
cluster: 'us'
})
client.connect()
const channel = client.subscribe('notifications')
channel.bind('new-alert', (data) => {
console.log('Received:', data)
})import { Apinator } from '@apinator/server'
const apinator = new Apinator({
appId: 'your-app-id',
key: 'your-key',
secret: 'your-secret',
cluster: 'us'
})
await apinator.trigger('notifications', 'new-alert', {
title: 'New message',
body: 'You have a new notification'
})Channels
Channels are the primary mechanism for organizing real-time messages. Clients subscribe to channels, and servers publish events to them. There are three channel types, each with different authorization and features.
Channel names can contain alphanumeric characters, hyphens, underscores, and dots. The maximum length is 200 characters. The channel type is determined by the name prefix.
| Type | Prefix | Auth Required | Use Case |
|---|---|---|---|
| Public | (none) | No | Broadcast data visible to all users |
| Private | private- | Yes | Restricted data for authorized users |
| Presence | presence- | Yes | Track who is online in a channel |
Public Channels
Public channels require no authorization. Any connected client can subscribe. Use them for broadcasting publicly visible data such as live scores, stock tickers, or site-wide notifications.
const channel = client.subscribe('live-scores')
channel.bind('score-update', (data) => {
console.log(data.team, data.score)
})Private Channels
Private channels require server-side authorization before a client can subscribe. The client SDK sends an authorization request to your auth endpoint, which returns an HMAC signature if the user is permitted.
Prefix the channel name with private- to create a private channel.
const client = new RealtimeClient({
appKey: 'your-app-key',
cluster: 'us',
authEndpoint: '/api/realtime/auth'
})
const channel = client.subscribe('private-user-123')// POST /api/realtime/auth
const auth = apinator.authorizeChannel(
req.body.socket_id,
req.body.channel_name
)
res.json(auth)Presence Channels
Presence channels extend private channels with member tracking. When a user subscribes, their identity is shared with other members. Presence channels track who is currently online and fire events when members join or leave.
Prefix the channel name with presence-. The auth endpoint must include channel_data containing the user's identity.
const auth = apinator.authorizeChannel(
req.body.socket_id,
req.body.channel_name,
{
user_id: 'user-123',
user_info: { name: 'Alice' }
}
)Events
Events are the messages delivered through channels. Each event has a name and a data payload (JSON string). There are three categories of events: server events (published by your backend), client events (sent between clients), and system events (emitted by Apinator).
Server Events
Server events are published from your backend via the REST API. They can have any name that does not start with realtime: or client-. The data payload is a JSON string with a maximum size of 10 KB.
await apinator.trigger('chat-room', 'new-message', {
user: 'Alice',
text: 'Hello, world!'
})channel.bind('new-message', (data) => {
console.log(data.user, data.text)
})Client Events
Client events are sent directly from one client to others on the same channel, without going through your server. They must be on private or presence channels, and the event name must start with client-.
Client events are useful for features like typing indicators where server round-trips add unnecessary latency.
Client events are not delivered to the sender, only to other subscribers on the channel.
channel.trigger('client-typing', {
user: 'Alice'
})System Events
System events are emitted by Apinator and prefixed with realtime:. They cannot be published manually. Use them to react to connection state and subscription lifecycle changes.
| Event | Description |
|---|---|
| realtime:subscription_succeeded | Subscription to a channel was authorized and completed |
| realtime:subscription_error | Subscription to a channel failed (auth rejected) |
| realtime:member_added | A new member joined a presence channel |
| realtime:member_removed | A member left a presence channel |
| realtime:connection_established | WebSocket connection was established and authenticated |
Presence
Presence lets you track which users are currently subscribed to a channel. It is built on top of presence channels and provides member lists, join/leave events, and multi-tab deduplication.
Subscribing
Subscribe to a presence channel the same way as a private channel. The auth endpoint must return channel_data with a unique user_id and optional user_info.
const channel = client.subscribe('presence-room')
channel.bind('realtime:subscription_succeeded', (members) => {
console.log('Online:', members.count)
members.each((member) => {
console.log(member.id, member.info)
})
})Member Tracking
The member list is available after the realtime:subscription_succeeded event fires. Each member has an id (from user_id) and info (from user_info).
Apinator handles multi-tab deduplication automatically. If a user opens multiple tabs, they appear as a single member. The member_removed event only fires when all of a user's connections leave.
Presence Events
Presence channels emit join and leave events so you can update your UI in real time.
channel.bind('realtime:member_added', (member) => {
console.log('Joined:', member.info.name)
})
channel.bind('realtime:member_removed', (member) => {
console.log('Left:', member.info.name)
})Server API
The Apinator server API is a REST API authenticated with HMAC-SHA256 signed requests. All requests must include the X-Realtime-Key, X-Realtime-Timestamp, and X-Realtime-Signature headers. The server SDKs handle signing automatically.
The base URL is https://api-{cluster}.apinator.io, where {cluster} is your region (e.g., us, eu).
Publish Events
Publish an event to one or more channels. The data payload is a JSON string with a maximum size of 10 KB.
| Field | Type | Description |
|---|---|---|
| channel | string | Target channel name |
| event | string | Event name (no realtime: or client- prefix) |
| data | string | JSON-encoded payload (max 10 KB) |
| channels | string[] | Multiple channels (alternative to channel, max 10) |
| socket_id | string | Optional: exclude this connection from receiving the event |
POST /apps/{app_id}/events
Content-Type: application/json
{
"channel": "notifications",
"event": "new-alert",
"data": "{\"title\":\"Hello\"}"
}Channel Queries
Query active channels and their subscriber counts. Useful for building admin dashboards or monitoring.
GET /apps/{app_id}/channels
# Response
{
"channels": {
"chat-room": { "subscription_count": 42 },
"notifications": { "subscription_count": 156 }
}
}GET /apps/{app_id}/channels/{channel_name}
# Response
{
"occupied": true,
"subscription_count": 42
}Batch Events
Send up to 10 events in a single request. Each item in the batch array follows the same schema as a single publish request.
POST /apps/{app_id}/batch
Content-Type: application/json
{
"batch": [
{ "channel": "room-1", "event": "msg", "data": "..." },
{ "channel": "room-2", "event": "msg", "data": "..." }
]
}JavaScript SDK
The JavaScript client SDK connects to Apinator via WebSocket from browsers. It handles automatic reconnection with exponential backoff, channel subscription management, and auth token negotiation.
Installation
Install via npm, yarn, or pnpm. Works with any bundler (webpack, Vite, esbuild, Rollup).
npm install @apinator/js<script src="https://cdn.apinator.io/js/latest/apinator.min.js">script>Usage
Create a client instance, connect, then subscribe to channels and bind event handlers.
| Option | Type | Description |
|---|---|---|
| appKey | string | Your application key (required) |
| cluster | string | Region cluster: us, eu (required) |
| authEndpoint | string | URL for private/presence channel auth |
| authHeaders | object | Extra headers sent with auth requests |
| forceTLS | boolean | Force TLS connection (default: true) |
import { RealtimeClient } from '@apinator/js'
const client = new RealtimeClient({
appKey: 'your-app-key',
cluster: 'us',
authEndpoint: '/api/realtime/auth'
})
client.connect()
// Public channel
const alerts = client.subscribe('alerts')
alerts.bind('new-alert', (data) => showAlert(data))
// Private channel
const inbox = client.subscribe('private-inbox-123')
// Presence channel
const room = client.subscribe('presence-room')
room.bind('realtime:member_added', (m) => addUser(m))
// Disconnect
client.disconnect()Node.js SDK
The Node.js server SDK provides event publishing, channel authorization, channel queries, and webhook verification. Zero dependencies — uses Node.js built-in crypto and fetch (Node 18+).
Installation
Requires Node.js 18 or later.
npm install @apinator/serverUsage
Initialize with your credentials, then use the client to publish events and sign channel auth.
import { Apinator } from '@apinator/server'
const apinator = new Apinator({
appId: 'your-app-id',
key: 'your-key',
secret: 'your-secret',
cluster: 'us'
})
// Publish an event
await apinator.trigger('chat', 'message', { text: 'Hello' })
// Authorize a private channel
const auth = apinator.authorizeChannel(socketId, channelName)
// Query channels
const channels = await apinator.getChannels()
// Verify a webhook
const valid = apinator.verifyWebhook(signature, timestamp, body)Python SDK
The Python server SDK provides event publishing, channel authorization, and webhook verification. Zero dependencies — uses only the standard library. Requires Python 3.9+.
Installation
Install from PyPI.
pip install apinator-serverUsage
Initialize the client and use it to publish events or authorize channels.
from apinator import Apinator
apinator = Apinator(
app_id='your-app-id',
key='your-key',
secret='your-secret',
cluster='us'
)
# Publish an event
apinator.trigger('chat', 'message', {'text': 'Hello'})
# Authorize a private channel
auth = apinator.authorize_channel(socket_id, channel_name)
# Verify a webhook
valid = apinator.verify_webhook(signature, timestamp, body)Go SDK
The Go server SDK provides event publishing, channel authorization, and webhook verification. Zero dependencies — uses only the standard library. Requires Go 1.21+ and uses the functional options pattern.
Installation
Install with go get.
go get github.com/apinator-io/apinator-goUsage
Create a client with functional options and use it to publish events.
import "github.com/apinator-io/apinator-go"
client, err := apinator.New(
apinator.WithAppID("your-app-id"),
apinator.WithKey("your-key"),
apinator.WithSecret("your-secret"),
apinator.WithCluster("us"),
)
// Publish an event
err = client.Trigger(ctx, "chat", "message", map[string]string{
"text": "Hello",
})
// Authorize a channel
auth, err := client.AuthorizeChannel(socketID, channelName)PHP SDK
The PHP server SDK provides event publishing, channel authorization, and webhook verification. Zero dependencies, PHP 8.1+, with PSR-4 autoloading.
Installation
Install via Composer.
composer require apinator/apinator-phpUsage
Create a client instance and use it to publish events.
use Apinator\\Apinator;
$apinator = new Apinator([
'app_id' => 'your-app-id',
'key' => 'your-key',
'secret' => 'your-secret',
'cluster' => 'us'
]);
// Publish an event
$apinator->trigger('chat', 'message', ['text' => 'Hello']);
// Authorize a channel
$auth = $apinator->authorizeChannel($socketId, $channelName);Swift SDK
The Swift client SDK connects to Apinator via WebSocket from iOS and macOS apps. It uses URLSessionWebSocketTask and supports iOS 13+ / macOS 10.15+. Distributed via Swift Package Manager.
Installation
Add the package via SPM in Xcode or your Package.swift.
.package(url: "https://github.com/apinator-io/apinator-swift", from: "1.0.0")Usage
Create a client, connect, then subscribe to channels.
import ApinatorSDK
let client = RealtimeClient(
appKey: "your-app-key",
cluster: "us",
authEndpoint: "https://your-server.com/api/realtime/auth"
)
client.connect()
let channel = client.subscribe("notifications")
channel.bind("new-alert") { data in
print("Received: \(data)")
}Kotlin SDK
The Kotlin client SDK connects to Apinator via WebSocket from Android and JVM apps. Built on OkHttp WebSocket. Distributed via Gradle.
Installation
Add the dependency to your build.gradle.kts.
implementation("com.apinator:sdk:1.0.0")Usage
Create a client, connect, then subscribe and bind events.
import com.apinator.sdk.RealtimeClient
val client = RealtimeClient(
appKey = "your-app-key",
cluster = "us",
authEndpoint = "https://your-server.com/api/realtime/auth"
)
client.connect()
val channel = client.subscribe("notifications")
channel.bind("new-alert") { data ->
println("Received: $data")
}Authentication
Apinator uses HMAC-SHA256 for all authentication. There are two authentication contexts: API request signing (server-to-Apinator) and channel authorization (client subscription to private/presence channels).
API Request Signing
Every API request from your server must include three headers. The server SDKs generate these automatically.
The signature is computed over a string containing the timestamp, HTTP method, path, and an MD5 hash of the request body.
| Header | Description |
|---|---|
| X-Realtime-Key | Your application API key |
| X-Realtime-Timestamp | Unix timestamp (seconds). Must be within 300s of server time. |
| X-Realtime-Signature | HMAC-SHA256 hex digest of the signature string |
body_md5 = len(body) == 0 ? "" : hex(md5(body))
sig_string = "{timestamp}\n{METHOD}\n{path}\n{body_md5}"
signature = hex(hmac_sha256(secret, sig_string))Channel Authorization
When a client subscribes to a private or presence channel, it sends an auth request to your endpoint. Your server validates the user and returns an HMAC signature.
For private channels, the signature input is {socket_id}:{channel_name}. For presence channels, append :{channel_data} where channel_data is a JSON string with user_id and optional user_info.
The channel_data field is only required for presence channels. Omit it for private channels.
{
"auth": "app-key:hmac-signature",
"channel_data": "{\"user_id\":\"123\",\"user_info\":{\"name\":\"Alice\"}}"
}Webhooks
Webhooks notify your server when events occur in your Apinator application, such as channel occupancy changes or client events. Webhooks are delivered via HTTP POST with HMAC-SHA256 signed payloads.
Configuration
Configure webhooks in the Apinator dashboard under your app settings. Provide a URL and select which events to receive. Each webhook has a unique secret used for signature verification.
Webhooks are delivered with exponential backoff. Failed deliveries are retried up to 5 times.
Webhook Events
The following webhook event types are available.
| Event | Description |
|---|---|
| channel_occupied | First client subscribed to a channel |
| channel_vacated | Last client unsubscribed from a channel |
| member_added | A user joined a presence channel |
| member_removed | A user left a presence channel |
| client_event | A client event was triggered on a channel |
{
"event": "channel_occupied",
"channel": "chat-room",
"timestamp": 1700000000
}Signature Verification
Every webhook request includes an X-Webhook-Signature header. Verify this signature to ensure the request originated from Apinator.
The signature is computed as sha256={hmac_sha256(webhook_secret, "{timestamp}.{body}")}.
const valid = apinator.verifyWebhook(
req.headers['x-webhook-signature'],
req.headers['x-webhook-timestamp'],
req.body
)
if (!valid) {
return res.status(401).send('Invalid signature')
}Rate Limits
Apinator enforces rate limits on API requests, WebSocket messages, and concurrent connections. Limits vary by plan and are applied per-application using a sliding window counter.
Plan Limits
The following limits apply per application.
| Resource | Free | Pro | Business |
|---|---|---|---|
| Concurrent connections | 100 | 10,000 | 500,000 |
| Messages per day | 200,000 | 20,000,000 | Unlimited |
| API requests per second | 10 | 100 | 500 |
| Max message size | 10 KB | 10 KB | 256 KB |
| Max channels | 100 | 10,000 | Unlimited |
Response Headers
API responses include rate limit headers so you can monitor your usage.
When a rate limit is exceeded, the API returns HTTP 429 Too Many Requests with a Retry-After header.
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests per window |
| X-RateLimit-Remaining | Remaining requests in current window |
| X-RateLimit-Reset | Unix timestamp when the window resets |