FleetbaseFleetbase

Set Up Real-Time Tracking

Subscribe to authenticated Fleetbase socket channels to display live driver locations and order status updates in your own application or dashboard.

Set Up Real-Time Tracking

Fleetbase broadcasts real-time events — driver location updates, order status changes, and platform events — over WebSocket using SocketCluster. This recipe shows how to subscribe to those channels from your own application and render live driver positions on a map.

What You'll Build

A small server that mints short-lived socket tokens, plus a browser page that connects to the Fleetbase socket server with one of those tokens, subscribes to your organisation's event channel, and plots incoming driver locations on a Leaflet.js map in real time.

Prerequisites

  • A Fleetbase account with Fleet-Ops installed and at least one active driver using the Navigator app
  • A secret API key and your company UUID from the Developer Console. The key stays on your server.
  • A small backend you control (any language) that can call the Fleetbase API and serve your page
  • Your socket server hostname (for self-hosted: your domain; for Fleetbase Cloud: provided in your account settings)

How the Socket Layer Works

Fleetbase maintains these channels you can subscribe to:

ChannelWhat it receives
company.{company_id}All events for your organisation, regardless of key
api.{key_id}Events scoped to a specific API key
{resource}.{id}Events for one record, e.g. order.{id} or driver.{id}

Driver location updates are published to company.{company_id} as drivers move. The event type is driver.location_changed and the payload includes the driver's current latitude, longitude, bearing, and speed.

Subscriptions are authenticated. Your server exchanges its API key for a short-lived socket token (POST /v1/socket/token), the browser connects with that token, and the socket server checks every subscription against it. An API-key token may subscribe to its own company channel, its own api.{key_id} channel, and channels of records in the same company. Anonymous subscriptions are rejected when the server runs in enforce mode.

You can monitor live events directly in the console at Developers → Socket Events before writing any code.

Never ship your API key to the browser. Everything that needs the key — minting socket tokens and loading driver lists — happens on your server in this recipe.

Step 1 — Install the Client Library

npm install socketcluster-client

Or via CDN in a browser page:

<script src="https://cdn.jsdelivr.net/npm/socketcluster-client/socketcluster-client.min.js"></script>

Step 2 — Add Two Endpoints to Your Server

Your server mints socket tokens and proxies the initial driver list, so the API key never leaves it. This example uses Express; any backend works the same way.

// server.js — runs on your backend only
import express from 'express';

const app = express();
const FLEETBASE_API = 'https://api.your-instance.com'; // your Fleetbase API host
const headers = {
    Authorization: `Bearer ${process.env.FLEETBASE_SECRET_KEY}`,
    'Content-Type': 'application/json',
};

// Short-lived socket token for the browser: { token, expires_in, expires_at }
app.post('/realtime-token', requireSignedInUser, async (req, res) => {
    const response = await fetch(`${FLEETBASE_API}/v1/socket/token`, { method: 'POST', headers });
    if (!response.ok) {
        return res.status(response.status).json({ error: 'Could not mint a socket token' });
    }
    res.json(await response.json());
});

// Last known driver positions, fetched server-side with the key.
app.get('/active-drivers', requireSignedInUser, async (req, res) => {
    const response = await fetch(`${FLEETBASE_API}/v1/drivers?status=active`, { headers });
    res.status(response.status).json(await response.json());
});

app.use(express.static('public'));
app.listen(3000);

requireSignedInUser stands for whatever authentication your application already has. Do not hand tokens to anonymous visitors: anyone holding a token can read your company's events until it expires.

With the official SDKs the mint call is one line: await fleetbase.socket.token() in @fleetbase/sdk, or $fleetbase->socket->token() in fleetbase/fleetbase-php.

Step 3 — Connect With the Token

The browser keeps the token in memory and gives it to SocketCluster through an authEngine. SocketCluster calls loadToken() before every connect and reconnect, so the token is part of the handshake and subscriptions are authorized immediately. A timer refreshes the token about 60 seconds before it expires.

import { create } from 'socketcluster-client';

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 }
}

let current = null; // { token, refreshAt } — memory only, never localStorage
function remember({ token, expires_in }) {
    current = { token, refreshAt: Date.now() + (expires_in - 60) * 1000 };
    return token;
}

const socket = create({
    hostname: 'socket.your-instance.com', // or your Fleetbase Cloud socket host
    secure: true,
    port: 443,
    authEngine: {
        saveToken: async (_name, token) => token,
        removeToken: async () => {
            const token = current ? current.token : null;
            current = null;
            return token;
        },
        loadToken: async () => (current && Date.now() < current.refreshAt ? current.token : remember(await fetchSocketToken())),
    },
});

async function refreshToken() {
    await socket.authenticate(remember(await fetchSocketToken()));
}

// Refresh ~60 s before expiry, and re-authenticate if the server drops our token.
setInterval(() => {
    if (current && Date.now() >= current.refreshAt) {
        refreshToken().catch(console.error);
    }
}, 15000);

(async () => {
    for await (const _event of socket.listener('deauthenticate')) {
        refreshToken().catch(console.error);
    }
})();

(async () => {
    for await (const _event of socket.listener('connect')) {
        console.log('Connected to Fleetbase socket server');
    }
})();

SocketCluster automatically reconnects on disconnect and resubscribes your channels. Because loadToken() runs on every reconnect, a fresh token is fetched if the old one is about to expire.

Step 4 — Subscribe to the Organisation Channel

const COMPANY_ID = 'your-company-uuid'; // from Developers → Socket Events

async function subscribeToCompanyChannel() {
    const channel = socket.subscribe(`company.${COMPANY_ID}`);

    (async () => {
        for await (const { error } of channel.listener('subscribeFail')) {
            console.warn('Subscription rejected — check the company UUID and that the token was minted for this company', error);
        }
    })();

    for await (const event of channel) {
        handleSocketEvent(event);
    }
}

function handleSocketEvent(event) {
    console.log(`Event received [${event.event}]:`, event.data);

    switch (event.event) {
        case 'driver.location_changed':
            updateDriverMarker(event.data);
            break;
        case 'order.dispatched':
            onOrderDispatched(event.data);
            break;
        case 'order.completed':
            onOrderCompleted(event.data);
            break;
    }
}

subscribeToCompanyChannel();

Step 5 — Render Driver Locations on a Map

This example uses Leaflet.js, a lightweight open-source mapping library. Save it as public/index.html next to the server from Step 2. It loads the current positions through your /active-drivers endpoint, then applies live updates from the socket.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Live Driver Tracking</title>
  <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
  <style>
    #map { height: 100vh; width: 100%; }
  </style>
</head>
<body>
  <div id="map"></div>

  <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/socketcluster-client/socketcluster-client.min.js"></script>
  <script>
    const COMPANY_ID = 'your-company-uuid';

    // Initialise map
    const map = L.map('map').setView([1.3521, 103.8198], 12);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '© OpenStreetMap contributors'
    }).addTo(map);

    // Track markers by driver ID
    const driverMarkers = {};

    function updateDriverMarker(driver) {
        const { id, name, location } = driver;
        const { coordinates } = location; // [lng, lat] in GeoJSON order
        const latLng = [coordinates[1], coordinates[0]];

        if (driverMarkers[id]) {
            driverMarkers[id].setLatLng(latLng);
        } else {
            driverMarkers[id] = L.marker(latLng).bindPopup(`<strong>${name}</strong>`).addTo(map);
        }
    }

    // Short-lived socket token from YOUR server — the API key never reaches the page.
    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();
    }

    let current = null;
    function remember({ token, expires_in }) {
        current = { token, refreshAt: Date.now() + (expires_in - 60) * 1000 };
        return token;
    }

    const socket = socketClusterClient.create({
        hostname: 'socket.your-instance.com',
        secure: true,
        port: 443,
        authEngine: {
            saveToken: async (_name, token) => token,
            removeToken: async () => { const token = current && current.token; current = null; return token || null; },
            loadToken: async () => (current && Date.now() < current.refreshAt ? current.token : remember(await fetchSocketToken())),
        },
    });

    setInterval(async () => {
        if (current && Date.now() >= current.refreshAt) {
            await socket.authenticate(remember(await fetchSocketToken()));
        }
    }, 15000);

    // Current positions first (proxied by your server), then live updates.
    (async () => {
        const response = await fetch('/active-drivers', { credentials: 'include' });
        const { data: drivers = [] } = await response.json();
        drivers.filter((driver) => driver.location).forEach(updateDriverMarker);

        const channel = socket.subscribe(`company.${COMPANY_ID}`);
        for await (const event of channel) {
            if (event.event === 'driver.location_changed') {
                updateDriverMarker(event.data);
            }
        }
    })();
  </script>
</body>
</html>

Socket events only deliver updates from when you connect, which is why the page loads the last known positions first. That request goes through your own /active-drivers endpoint so the API key stays on the server.

Location Event Payload

The driver.location_changed event payload looks like this:

{
  "event": "driver.location_changed",
  "data": {
    "id": "driver_01H...",
    "public_id": "DRV-001",
    "name": "Alex Tan",
    "phone": "+6591234567",
    "status": "active",
    "location": {
      "type": "Point",
      "coordinates": [103.8198, 1.3521]
    },
    "heading": 145,
    "speed": 42.5
  }
}

Coordinates follow GeoJSON order: [longitude, latitude]. Swap them when passing to Leaflet's [lat, lng] convention.

Subscribing to a Specific API Key Channel

If you only want events tied to a specific integration (rather than all organisation events), subscribe to api.{key_id} instead. The token must be minted with that same key:

const KEY_ID = 'your-api-key-uuid'; // shown in Developers → Socket Events

const channel = socket.subscribe(`api.${KEY_ID}`);

Following a Single Order or Driver

To follow one record, subscribe to its own channel. The record must belong to the same company as the key that minted the token:

const orderChannel = socket.subscribe('order.order_abc123');
const driverChannel = socket.subscribe('driver.driver_abc123');

Origin Restrictions

In production, restrict which origins can connect to your socket server. See System Setup → Socket for configuration details, including the socket authentication mode.

Next Steps

Set Up Real-Time Tracking | Fleetbase