Real-time events
The Socket.IO connection for agent locations, ride offers and live updates.
Tracking and the offer/response cycle run over a Socket.IO (v4) connection to the same host as the REST API. Use any Socket.IO v4 client: JavaScript/TypeScript, Python, Java, Swift and others.
Connect from your backend only. The socket authenticates with your API key, which must never ship inside an agent or passenger app. Your backend relays events between RideWiseCore and your apps.
Connecting
import { io } from "socket.io-client";
const socket = io(process.env.RWC_API_URL!, {
auth: { apiKey: process.env.RWC_API_KEY },
});
socket.on("connect", () => console.log("connected", socket.id));
socket.on("connect_error", (err) => console.error("refused:", err.message));
socket.on("disconnect", (reason) => console.warn("disconnected:", reason));Handshake errors
If the handshake fails, you receive connect_error with one of these messages:
err.message | Fix |
|---|---|
Missing API key | Pass auth: { apiKey } |
Invalid or revoked API key | Use an active key |
A payment method must be on file before matching or tracking is available | Add a payment method in the dashboard |
The free trial ended on YYYY-MM-DD — … | Add a payment method in the dashboard. See Free trial |
This tenant has cancelled their subscription — … | Subscription cancelled |
Payment could not be processed after two attempts — … | Suspended for non-payment. See Billing |
Socket.IO reconnects automatically after network drops. Authentication errors aren't retried usefully, so fix the cause and reconnect.
Rooms and fan-out
Every socket authenticated with one of your keys joins your private tenant room. Every server event below goes to all sockets in that room, so:
- You can run several backend instances, each with its own socket. They all receive every event.
- Only one instance should act on each
ride:offer. Deliver it to the agent once, for example by routing onagentIdor using a shared lock in your own system.
Events you send
agent:location
Report an agent's position and status. Send this every 3–10 seconds for each online agent. See Agent tracking.
socket.emit(
"agent:location",
{
agentId: "agt_1001",
lat: 40.7128,
lng: -74.0060,
status: "available",
vehicleType: "sedan",
heading: 92,
speed: 11.4,
},
(ack) => { /* { ok: true } or { ok: false, error } */ },
);Prop
Type
ride:respond
Relay an agent's answer to a ride:offer.
socket.emit(
"ride:respond",
{ matchId: "9f1c2d4e-…", agentId: "agt_1001", response: "accept" },
(ack) => {
if (!ack.ok) {
// "Offer is no longer active": it expired, was cancelled, or was already answered.
// Tell the agent the ride is gone.
}
},
);Prop
Type
Acknowledgements
Both events accept an optional acknowledgement callback, which RideWiseCore calls with:
type Ack = { ok: true } | { ok: false; error: string };Always pass one when you need to know the outcome, especially for ride:respond.
Events you receive
ride:offer
An agent has been selected and reserved for a match. Deliver the offer to that agent's app. They have expiresInMs to answer before the offer moves on.
{
"matchId": "9f1c2d4e-6b7a-4c3d-8e9f-0a1b2c3d4e5f",
"agentId": "agt_1001",
"pickup": { "lat": 40.713, "lng": -74.0055 },
"vehicleType": "sedan",
"expiresInMs": 15000
}Show the agent a countdown slightly shorter than expiresInMs to allow for network delay between RideWiseCore, your backend and the agent app.
match:update
A match reached a final state. The payload is the full match object.
status | What to do |
|---|---|
matched | Assign matchedAgentId to the trip in your system and notify the passenger |
unmatched | Tell the passenger no agent is available, or retry with a larger radius |
cancelled | Confirmation of your own cancel |
agent:update
Echoed to your room every time a agent:location is stored. Use it to drive live maps in your dashboards or passenger apps.
{
"agentId": "agt_1001",
"lat": 40.7128,
"lng": -74.0060,
"status": "available",
"vehicleType": "sedan",
"heading": 92,
"speed": 11.4,
"updatedAt": "2026-09-23T08:15:04.502Z"
}RideWiseCore changes some statuses itself, for example setting an agent to busy when they accept, or offline when they go silent. It doesn't emit a agent:update for those changes. Use match:update for accepts, and treat an agent as gone when you stop sending their locations.
TypeScript types
Copy these into your project for a fully typed client:
import type { Socket } from "socket.io-client";
export type AgentStatus = "available" | "busy" | "offline";
export type Ack = { ok: true } | { ok: false; error: string };
export interface AgentLocation {
agentId: string;
lat: number;
lng: number;
status?: AgentStatus;
vehicleType: string;
heading?: number;
speed?: number;
}
export interface AgentRecord extends Required<Pick<AgentLocation, "agentId" | "lat" | "lng" | "vehicleType">> {
status: AgentStatus;
heading?: number;
speed?: number;
updatedAt: string;
}
export interface RideOffer {
matchId: string;
agentId: string;
pickup: { lat: number; lng: number };
vehicleType: string;
expiresInMs: number;
}
export interface Match {
id: string;
tenantId: string;
pickup: { lat: number; lng: number };
vehicleType: string;
radiusMeters: number;
status: "searching" | "matched" | "unmatched" | "cancelled";
currentCandidateAgentId: string | null;
matchedAgentId: string | null;
triedAgentIds: string[];
createdAt: string;
updatedAt: string;
}
export interface ServerToClientEvents {
"ride:offer": (offer: RideOffer) => void;
"match:update": (match: Match) => void;
"agent:update": (agent: AgentRecord) => void;
}
export interface ClientToServerEvents {
"agent:location": (payload: AgentLocation, ack?: (result: Ack) => void) => void;
"ride:respond": (
payload: { matchId: string; agentId: string; response: "accept" | "decline" },
ack?: (result: Ack) => void,
) => void;
}
export type RwcSocket = Socket<ServerToClientEvents, ClientToServerEvents>;