Socket Events
Monitor live WebSocket channels in the console and subscribe to authenticated real-time event streams from your own application.
Socket Events
Fleetbase uses SocketCluster to broadcast real-time events over WebSocket connections. The Socket Events panel in the Developer Console lets you browse available channels and watch live event output directly in the console. Navigate to Developers → Socket Events.

Channel Names
Every channel is named {type}.{id}. Fleetbase publishes each event under both the record's UUID and its public id (for example order_abc123), so subscribe with whichever one your system stores.
Organization and API key channels
| Channel | What it carries |
|---|---|
company.{company_id} | Every event for your organization, whichever record it concerns. The simplest channel to start with. |
api.{key_id} | Events caused by requests made with that API key. key_id is the key's UUID, as shown in this panel. |
Record channels
Subscribe to a single record to receive only its events. These are the record types you can subscribe to:
| Channel | Record |
|---|---|
order.{id} | An order |
driver.{id} | A driver: its own events (location, geofences) and the events of every order assigned to it |
vehicle.{id} / trailer.{id} | A vehicle or trailer |
device.{id} | A telematics device |
payload.{id}, waypoint.{id}, entity.{id} | An order's payload, one of its stops, or one of its items |
place.{id}, contact.{id}, fleet.{id} | A place, contact or fleet |
zone.{id}, service_area.{id} | A zone or service area |
tracking_number.{id}, tracking_status.{id} | A tracking number or one of its status updates |
service_rate.{id}, service_quote.{id}, purchase_rate.{id} | Rates, quotes and purchases |
position.{uuid} | A recorded position. Positions have no public id, so use the UUID. |
Relationship channels
These carry the events of every record linked to someone, which is often what an integration needs:
| Channel | What it carries |
|---|---|
customer.{customer_id} | Events for every order whose customer is that contact or vendor |
facilitator.{id} | Events for every order that vendor or contact facilitates |
vendor.{id} | Events for records that belong to that vendor |
For a driver's orders, subscribe to the driver's own channel, driver.{id} (for example driver.driver_abc123). The older driverAssigned.{driver_id} channel still receives the same order events but is deprecated.
Event names
An event's name is {type}.{action}, and arrives in the event field of each message (see What an event looks like):
- Every record:
{type}.created,{type}.updatedand{type}.deleted, for examplevehicle.updated. - Orders:
- Lifecycle:
order.ready,order.dispatched,order.dispatch_failed,order.started,order.completed,order.failedandorder.canceled. - Driver changes:
order.driver_assignedandorder.driver_unassigned. - Edits after the order was placed:
order.changed. See Order Changes.
- Lifecycle:
- Stops and items:
waypoint.activity,waypoint.completed,entity.activityandentity.completed. - Locations:
driver.location_changed,vehicle.location_changedandtrailer.location_changedon the driver's, vehicle's or trailer's channel.device.telemetry_updatedcarries a device's telemetry. - Geofences:
geofence.entered,geofence.exitedandgeofence.dwelled, published on the driver's and the vehicle's channels. - Comments: new comments are published on the commented record's channel. See Comments.
The same event can arrive on several channels: an order dispatch reaches company.…, order.… and the assigned driver's driver.…. Subscribe to the narrowest channel that covers what you need.
For example:
// One driver: their location, geofences and every order assigned to them
socket.subscribe('driver.driver_abc123');
// A vehicle's live position: filter its channel for location updates
const vehicle = socket.subscribe('vehicle.vehicle_abc123');
for await (const message of vehicle) {
if (message.event === 'vehicle.location_changed') {
updateMarker(message.data);
}
}Public tracking channels (tracking.…) are reserved for the customer tracking page and cannot be subscribed to with an API key.
Listening on a Channel
Click Listen on any row to open the real-time event view for that channel.

The channel view displays a black terminal-style output panel. As events arrive they appear as timestamped lines in green monospace text. If no events have fired yet a spinner shows Awaiting events….
Each line shows the time the event was received and its JSON payload, formatted for readability.
Listening on a Custom Channel
Click Listen on custom channel in the header to enter any channel name manually — useful when you know the exact channel identifier you want to observe. The console subscribes with your signed-in session, so you can only listen on channels your organization is allowed to see.

Connecting Programmatically
Your application connects to the Fleetbase socket server with socketcluster-client, subscribes to the channels it needs and receives each event as Fleetbase publishes it. These are the same events your webhooks receive, pushed over a connection your application keeps open instead of posted to an endpoint.
Connection details
| Setting | Value |
|---|---|
| API host | https://api.fleetbase.io (the SDKs' default) |
| Socket server | wss://socket.fleetbase.io/socketcluster/: hostname: 'socket.fleetbase.io', port: 443, secure: true |
| Socket authentication | Off for now. POST /v1/socket/token answers 404 and a connection without a token can subscribe. Follow the token flow below anyway: it connects without a token today and keeps working unchanged when authentication is switched on. |
| Allowed origins | Any. Browser apps on your own domain can connect directly. |
| Setting | Value |
|---|---|
| API host | Your own API host, for example https://api.your-domain.com |
| Socket server | Your WebSocket ingress, for example hostname: 'socket.your-domain.com', port: 443, secure: true. The container listens on port 8000 at /socketcluster/, and Docker Compose maps it to host port 38000 (for local development: hostname: 'localhost', port: 38000, secure: false). |
| Socket authentication | Off unless you set SOCKETCLUSTER_AUTH_ENABLED=true. See Authentication Modes and System Setup → Socket. |
| Allowed origins | Whatever you allow in SOCKETCLUSTER_OPTIONS. See Origin Restrictions. |
The token flow
Channel subscriptions can be authenticated. Your application presents a short-lived socket token when it connects, and the socket server only lets it subscribe to channels that token is allowed to see. The flow has three parts:
- Your server exchanges your secret API key for a socket token:
POST /v1/socket/token. - Your server hands only that token (never the API key) to the browser or device.
- The browser connects with
socketcluster-client, presents the token, subscribes, and asks your server for a fresh token about 60 seconds before the current one expires.
Never put a Fleetbase API key in browser or mobile code. Anyone can read it from the page source or network tab and use it against your account. Keep the key on your server and give clients only the short-lived socket token.
Step 1 — Mint a token on your server
POST /v1/socket/token authenticates with your secret API key like any other API call and returns:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 900,
"expires_at": "2026-01-01T12:15:00+00:00"
}Expose it to your own front end through an endpoint that checks your user's session first:
// server.js — runs on your backend only
import express from 'express';
const app = express();
const FLEETBASE_API = 'https://api.fleetbase.io'; // Fleetbase Cloud. Self-hosted: your own API host
app.post('/realtime-token', requireSignedInUser, async (req, res) => {
const response = await fetch(`${FLEETBASE_API}/v1/socket/token`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.FLEETBASE_SECRET_KEY}`,
'Content-Type': 'application/json',
},
});
// 404: socket authentication is off on this instance (Fleetbase Cloud today).
// Tell the browser to connect without a token.
if (response.status === 404) {
return res.json({ token: null });
}
if (!response.ok) {
return res.status(response.status).json({ error: 'Could not mint a socket token' });
}
// { token, expires_in, expires_at }
res.json(await response.json());
});// server.js — runs on your backend only
import Fleetbase from '@fleetbase/sdk';
// Fleetbase Cloud is the default host. Self-hosted: pass { host: 'https://api.your-domain.com' }.
const fleetbase = new Fleetbase(process.env.FLEETBASE_SECRET_KEY);
app.post('/realtime-token', requireSignedInUser, async (req, res) => {
// { token, expires_in, expires_at }
res.json(await fleetbase.socket.token());
});// Runs on your backend only
$fleetbase = new \Fleetbase\Sdk\Fleetbase(getenv('FLEETBASE_SECRET_KEY'));
$minted = $fleetbase->socket->token(); // POST /v1/socket/token
header('Content-Type: application/json');
echo json_encode([
'token' => $minted->token,
'expires_in' => $minted->expires_in,
'expires_at' => $minted->expires_at,
]);curl -X POST https://api.fleetbase.io/v1/socket/token \
-H "Authorization: Bearer $FLEETBASE_SECRET_KEY"An instance that has not enabled socket authentication answers POST /v1/socket/token with 404. That includes Fleetbase Cloud today, and any self-hosted instance where SOCKETCLUSTER_AUTH_ENABLED is off (the default). Treat a 404 as "connect without a token": the Express example answers { token: null }, and the browser code below connects anonymously when it gets one. If you use an SDK, catch the 404 the same way. Code written this way keeps working, unchanged, when authentication is switched on.
Step 2 — Connect from the browser
Keep the token in memory only (not localStorage, not the URL). The cleanest way is a small in-memory authEngine: SocketCluster calls loadToken() before every connect and reconnect, so the token travels in the handshake and your subscriptions are authorized from the first message.
// realtime.js — browser code. No API key anywhere in here.
import { create } from 'socketcluster-client';
// Ask YOUR backend for a token.
async function fetchSocketToken() {
const response = await fetch('/realtime-token', { method: 'POST', credentials: 'include' });
if (!response.ok) {
throw new Error(`Socket token request failed: ${response.status}`);
}
return response.json(); // { token, expires_in, expires_at }, or { token: null } when socket auth is off
}
// In-memory token store. Refresh ~60 s before expiry.
let current = null; // { token, refreshAt }
function remember({ token, expires_in }) {
current = { token, refreshAt: Date.now() + (expires_in - 60) * 1000 };
return token;
}
const authEngine = {
saveToken: async (_name, token) => token,
removeToken: async () => {
const token = current ? current.token : null;
current = null;
return token;
},
loadToken: async () => {
if (current && Date.now() < current.refreshAt) {
return current.token;
}
const minted = await fetchSocketToken();
// No token: socket authentication is off, so connect anonymously.
return minted.token ? remember(minted) : null;
},
};
export const socket = create({
hostname: 'socket.fleetbase.io', // Fleetbase Cloud. Self-hosted: your socket host
port: 443,
secure: true,
path: '/socketcluster/',
authEngine,
});
// Refresh the token on a live connection without dropping subscriptions.
async function refreshToken() {
const minted = await fetchSocketToken();
if (minted.token) {
await socket.authenticate(remember(minted));
}
}
setInterval(() => {
if (current && Date.now() >= current.refreshAt) {
refreshToken().catch((error) => console.error('Socket token refresh failed', error));
}
}, 15000);
// If the server drops our authentication (for example the token expired while offline),
// fetch a new token and authenticate again. Subscriptions are restored automatically.
(async () => {
for await (const _event of socket.listener('deauthenticate')) {
refreshToken().catch((error) => console.error('Socket re-authentication failed', error));
}
})();If you would rather not use an authEngine, you can call socket.authenticate(token) yourself — but do it before subscribing, and again on every reconnect. Subscriptions sent before the token arrives are treated as anonymous and are rejected when the server enforces authentication.
Step 3 — Subscribe to channels
import { socket } from './realtime.js';
const COMPANY_ID = 'your-company-uuid'; // shown in Developers → Socket Events
const channel = socket.subscribe(`company.${COMPANY_ID}`);
(async () => {
for await (const event of channel) {
console.log(`Event received [${event.event}]:`, event.data);
}
})();
// A rejected subscription (wrong company, unknown channel, missing token) ends up here.
(async () => {
for await (const { error } of channel.listener('subscribeFail')) {
console.warn(`Subscription to ${channel.name} was rejected:`, error);
}
})();The same pattern works for an API key channel or a single record:
const keyChannel = socket.subscribe(`api.${KEY_ID}`); // the key's UUID
const orderChannel = socket.subscribe(`order.${ORDER_ID}`); // uuid or public id, e.g. order_abc123What an event looks like
Every event uses the same envelope as a webhook payload: event is the event name, such as order.created, order.dispatched or order.canceled, and data is the record.
{
"id": "event_6ac8789429617",
"api_version": "2024-03-12",
"event": "order.updated",
"created_at": "2026-10-09 05:16:04",
"data": {
"id": "order_tjhpyo5mjs",
"status": "driver_enroute"
}
}Subscribing from your backend
You do not need a browser to subscribe. A Node.js service can hold the connection itself, for example to update your own database as orders change. On a server the API key is already safe, so the service mints its own token, with no /realtime-token endpoint in between:
// listener.js: runs on your backend, Node.js 18 or later
import { create } from 'socketcluster-client';
const FLEETBASE_API = 'https://api.fleetbase.io'; // Fleetbase Cloud. Self-hosted: your own API host
const COMPANY_ID = process.env.FLEETBASE_COMPANY_ID; // shown in Developers → Socket Events
async function mintToken() {
const response = await fetch(`${FLEETBASE_API}/v1/socket/token`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.FLEETBASE_SECRET_KEY}` },
});
if (response.status === 404) {
return null; // socket authentication is off: connect without a token
}
if (!response.ok) {
throw new Error(`Socket token request failed: ${response.status}`);
}
return (await response.json()).token;
}
const socket = create({
hostname: 'socket.fleetbase.io', // Fleetbase Cloud. Self-hosted: your socket host
port: 443,
secure: true,
path: '/socketcluster/',
// A fresh token on every connect and reconnect.
authEngine: {
saveToken: async (_name, token) => token,
removeToken: async () => null,
loadToken: () => mintToken(),
},
});
const channel = socket.subscribe(`company.${COMPANY_ID}`);
for await (const event of channel) {
if (event.event.startsWith('order.')) {
console.log(event.event, event.data.id, event.data.status);
}
}When authentication is on, tokens expire after expires_in seconds (15 minutes by default). A long-running service should re-authenticate before then, with the same refresh loop as the browser example.
Webhooks are still the right tool for delivery that must survive downtime on your side: a socket only delivers events while you are connected, and events missed while disconnected are not replayed. Many integrations use both: the socket for live updates, and webhooks as the record.
Who Can Subscribe to What
A token minted with an API key is scoped to the company that owns the key:
| Channel | Allowed for an API-key token when… |
|---|---|
company.{company_id} | it is the key's own company (UUID or public id) |
api.{key_id} | it is the key's own channel |
{resource}.{id} (e.g. order.…, driver.…, vehicle.…, place.…) | the record belongs to the key's company. A test-mode key resolves records in the sandbox. |
| Anything else | denied (unknown channel types are rejected) |
Other notes:
- Anonymous subscriptions (no token, or an expired token) are rejected when the server runs in
enforcemode. - Clients cannot publish. In
enforcemode only the Fleetbase API publishes events;socket.transmitPublish(),transmit()andinvoke()from clients are refused. - Expiry. When a token expires and is not refreshed, the server removes the socket from its channels. Refreshing about 60 seconds before
expires_atavoids that. - Driver and customer tokens (minted by the Navigator and Storefront apps) only see that driver's or customer's own records.
Authentication Modes
Once socket authentication is switched on (SOCKETCLUSTER_AUTH_ENABLED=true with a shared key; it is off by default, which behaves like off), each Fleetbase instance runs its socket server in one of three modes, set with SOCKETCLUSTER_AUTH_MODE:
| Mode | What happens |
|---|---|
off | Every subscription is allowed. Tokens are still verified when presented. |
log | Subscriptions are checked and allowed, but every one that would be denied is logged. Used during migration. |
enforce | Only authorized subscriptions succeed. Anonymous subscriptions and client publishing are refused. |
Fleetbase Cloud currently runs with socket authentication off. Code that follows this guide works in every mode. If you are moving an existing integration over, see Migrating to Authenticated Socket Channels. Self-hosters configure the switch and the mode in System Setup → Socket.
Origin Restrictions
This applies to self-hosted instances. Fleetbase Cloud accepts connections from any origin.
In production, also restrict which origins can connect to your socket server. Configure this via the SOCKETCLUSTER_OPTIONS environment variable in your docker-compose.override.yml:
environment:
SOCKETCLUSTER_OPTIONS: '{"origins":"https://yourdomain.com:*"}'Origins limit who can open a connection; socket tokens decide what each connection may receive. See System Setup → Socket for full configuration details.