SimpliRide WebSocket API
Real-time communication API for the SimpliRide ride-hailing platform. Built on Socket.IO with Redis scaling and Firebase persistence.
Authentication
All connections require userId and userType in query parameters.
const socket = io('https://realtime.simpliride.com/trip', {
query: {
userId: 'user-uuid',
userType: 'RIDER' // or 'DRIVER', 'SUPPORT', 'ADMIN'
}
});
// For drivers, you can also specify vehicle type
const driverSocket = io('https://realtime.simpliride.com/driver', {
query: {
userId: 'driver-uuid',
userType: 'DRIVER',
vehicleType: 'STANDARD' // SAVER, STANDARD, COMFORT, PRIORITY_PICKUP, XL, PREMIUM, PREMIUM_SUV
}
});
User Types
Valid user types: RIDER, DRIVER, SUPPORT, ADMIN
Connection Handling
After successful connection, you'll receive a connected event with session details.
Received immediately after successful authentication.
{
"sessionId": "socket-session-id",
"userId": "user-uuid",
"userType": "RIDER",
"timestamp": 1702828800000
}
Error Handling
Errors are emitted on the error event with a code and message.
socket.on('error', (error) => {
console.error('Socket error:', error.code, error.message);
// error.code: 'RATE_LIMIT', 'FORBIDDEN', 'INVALID_TOKEN', etc.
});
| Error Code |
Description |
Action |
RATE_LIMIT |
Too many requests |
Wait and retry |
FORBIDDEN |
Action not allowed for user type |
Check permissions |
MISSING_USER_ID |
userId is required but not provided |
Provide userId in query |
MISSING_USER_TYPE |
userType is required but not provided |
Provide userType in query |
VALIDATION_ERROR |
Invalid payload format |
Fix payload |
/trip Namespace
Main namespace for trip lifecycle events. Used by both riders and drivers.
Connection URL
wss://realtime.simpliride.com/trip
trip:subscribe
Subscribe to real-time updates for a specific trip.
Request Payload
| Parameter | Type | Required | Description |
| tripId |
string (UUID) |
Required |
The trip ID to subscribe to |
Response
{ "success": true, "tripId": "uuid", "subscribed": true }
Example
socket.emit('trip:subscribe', { tripId: 'trip-uuid' }, (response) => {
if (response.success) {
console.log('Subscribed to trip updates');
}
});
trip:request (Listen)
Received by drivers when a new trip request is available.
Payload
{
"tripId": "uuid",
"riderId": "uuid",
"riderName": "John Doe",
"riderRating": 4.8,
"pickup": {
"latitude": 6.5244,
"longitude": 3.3792,
"address": "123 Main Street, Lagos"
},
"dropoff": {
"latitude": 6.4541,
"longitude": 3.3947,
"address": "456 Victoria Island, Lagos"
},
"estimatedFare": 2500.00,
"estimatedDistance": 8.5,
"estimatedDuration": 25,
"vehicleType": "COMFORT",
"paymentMethod": "CARD",
"expiresAt": 1702828830000,
"timestamp": 1702828800000
}
trip:accept
Driver accepts a trip request. Drivers only.
Request Payload
| Parameter | Type | Required | Description |
| tripId |
string (UUID) |
Required |
Trip ID to accept |
| latitude |
number |
Optional |
Driver's current latitude |
| longitude |
number |
Optional |
Driver's current longitude |
Example
socket.emit('trip:accept', {
tripId: 'trip-uuid',
latitude: 6.5244,
longitude: 3.3792
}, (response) => {
if (response.success) {
console.log('Trip accepted!');
}
});
trip:accepted (Listen)
Broadcast to all trip subscribers when driver accepts.
{
"tripId": "uuid",
"driverId": "uuid",
"driverLocation": {
"latitude": 6.5244,
"longitude": 3.3792
},
"timestamp": 1702828800000
}
trip:reject
Driver declines a trip request. Drivers only.
| Parameter | Type | Required | Description |
| tripId |
string (UUID) |
Required |
Trip ID to reject |
| reason |
string |
Optional |
Reason for rejection |
trip:cancel
Cancel an active trip. Can be used by both riders and drivers.
| Parameter | Type | Required | Description |
| tripId |
string (UUID) |
Required |
Trip ID to cancel |
| reason |
string |
Optional |
Cancellation reason |
trip:status (Listen)
Received when trip status changes.
{
"tripId": "uuid",
"status": "DRIVER_EN_ROUTE",
"previousStatus": "DRIVER_ASSIGNED",
"driverId": "uuid",
"timestamp": 1702828800000
}
Trip Status Values
| Status | Description |
| PENDING | Trip created, searching for drivers |
| DRIVER_ASSIGNED | Driver accepted the trip |
| DRIVER_EN_ROUTE | Driver heading to pickup |
| DRIVER_ARRIVED | Driver at pickup location |
| TRIP_STARTED | Rider picked up, trip in progress |
| COMPLETED | Trip completed |
| CANCELLED | Trip cancelled |
trip:eta (Listen)
ETA updates during the trip.
{
"tripId": "uuid",
"etaMinutes": 5,
"etaSeconds": 320,
"distanceMeters": 2500,
"trafficCondition": "MODERATE",
"timestamp": 1702828800000
}
Emergency SOS
Trigger an emergency SOS. Available to both riders and drivers.
| Parameter | Type | Required | Description |
| tripId |
string (UUID) |
Required |
Trip ID |
| type |
string |
Required |
SOS, ACCIDENT, HARASSMENT, MEDICAL, OTHER |
| latitude |
number |
Optional |
Current latitude |
| longitude |
number |
Optional |
Current longitude |
| description |
string |
Optional |
Additional details |
Support agent joins an emergency to monitor the trip. Support/Admin only.
| Parameter | Type | Required | Description |
| tripId |
string (UUID) |
Required |
Trip ID with active emergency |
socket.emit('support:join', { tripId: 'trip-uuid' });
Support agent leaves emergency monitoring. Support/Admin only.
| Parameter | Type | Required | Description |
| tripId |
string (UUID) |
Required |
Trip ID to stop monitoring |
socket.emit('support:leave', { tripId: 'trip-uuid' });
Trip Sharing
Subscribe to shared trip updates (for emergency contacts viewing shared trip).
// No authentication required for shared trip viewing
socket.emit('tripShare:subscribe', { shareToken: 'share-token-from-link' });
// Listen for updates
socket.on('tripShare:update', (update) => {
console.log('Shared trip update:', update);
});
Unsubscribe from shared trip updates.
| Parameter | Type | Required | Description |
| shareToken |
string |
Required |
Share token from the shared link |
socket.emit('tripShare:unsubscribe', { shareToken: 'share-token' });
/driver Namespace
Driver-specific namespace for location updates, availability, and trip notifications.
Connection URL
wss://realtime.simpliride.com/driver
Driver Only
This namespace only accepts connections with userType: 'DRIVER'
location:update
Update driver's current location. Should be sent every 3-5 seconds when online.
| Parameter | Type | Required | Description |
| latitude |
number |
Required |
Current latitude (-90 to 90) |
| longitude |
number |
Required |
Current longitude (-180 to 180) |
| speed |
number |
Optional |
Speed in km/h |
| heading |
number |
Optional |
Heading in degrees (0-360) |
| accuracy |
number |
Optional |
GPS accuracy in meters |
| tripId |
string |
Optional |
If on active trip, broadcasts to rider |
Example
socket.emit('location:update', {
latitude: 6.5244,
longitude: 3.3792,
speed: 45.5,
heading: 180,
accuracy: 10,
tripId: 'active-trip-uuid' // optional
});
availability:update
Toggle driver availability to receive trip requests.
| Parameter | Type | Required | Description |
| available |
boolean |
Required |
true = online, false = offline |
| latitude |
number |
Required |
Current latitude |
| longitude |
number |
Required |
Current longitude |
| vehicleType |
string |
Optional |
SAVER, STANDARD, COMFORT, PRIORITY_PICKUP, XL, PREMIUM, PREMIUM_SUV |
Example
// Go online
socket.emit('availability:update', {
available: true,
latitude: 6.5244,
longitude: 3.3792,
vehicleType: 'COMFORT'
});
// Go offline
socket.emit('availability:update', {
available: false,
latitude: 6.5244,
longitude: 3.3792
});
vehicle:update
Change active vehicle type.
socket.emit('vehicle:update', { vehicleType: 'PREMIUM' });
Driver Trip Events
Notify rider that driver has arrived at pickup.
socket.emit('trip:arrived:pickup', { tripId: 'trip-uuid' });
Mark trip as started after rider boards.
socket.emit('trip:started', { tripId: 'trip-uuid' });
Mark trip as completed.
socket.emit('trip:completed', {
tripId: 'trip-uuid',
finalFare: 2500.00
});
driver:location (Listen - for Riders)
Riders receive driver location updates when subscribed to a trip.
{
"driverId": "uuid",
"latitude": 6.5244,
"longitude": 3.3792,
"speed": 45.5,
"heading": 180,
"timestamp": 1702828800000
}
/chat Namespace
In-trip messaging between rider and driver.
Connection URL
wss://realtime.simpliride.com/chat
chat:join
Join a trip's chat room.
socket.emit('chat:join', { tripId: 'trip-uuid' });
chat:message
Send a chat message.
| Parameter | Type | Required | Description |
| tripId |
string |
Required |
Trip ID |
| message |
string |
Required |
Message content (max 500 chars) |
| messageType |
string |
Optional |
text (default), location, image |
chat:message (Listen)
Receive chat messages.
{
"id": "message-uuid",
"tripId": "trip-uuid",
"senderId": "user-uuid",
"senderType": "RIDER",
"message": "I'm at the blue gate",
"messageType": "text",
"timestamp": 1702828800000
}
chat:typing
Send typing indicator.
socket.emit('chat:typing', { tripId: 'trip-uuid', isTyping: true });
chat:history
Get chat history for a trip.
socket.emit('chat:history', { tripId: 'trip-uuid', limit: 50 }, (response) => {
console.log('Chat history:', response.messages);
});
chat:read
Mark a message as read and notify the sender.
| Parameter | Type | Required | Description |
| tripId |
string |
Required |
Trip ID |
| messageId |
string |
Required |
Message ID to mark as read |
socket.emit('chat:read', { tripId: 'trip-uuid', messageId: 'msg-uuid' });
chat:read (Listen)
Received when other party reads a message.
{
"tripId": "trip-uuid",
"messageId": "msg-uuid",
"readBy": "user-uuid",
"timestamp": 1702828800000
}
/voice Namespace
WebRTC-based voice calling between rider and driver during active trips. The gateway acts as a signaling server while actual audio is peer-to-peer.
WebRTC Voice Calling
Voice calls use WebRTC for peer-to-peer audio. The gateway handles signaling (offer/answer/ICE candidates) while audio streams directly between devices.
Connection URL
wss://realtime.simpliride.com/voice
voice:getIceServers
Get ICE server configuration for WebRTC. Call this before initiating a call to get STUN/TURN servers.
Response
{
"iceServers": [
{ "urls": "stun:stun.l.google.com:19302" },
{ "urls": "stun:stun1.l.google.com:19302" },
{
"urls": "turn:turn.example.com:3478",
"username": "user",
"credential": "pass"
}
]
}
Example
socket.emit('voice:getIceServers', {}, (response) => {
const peerConnection = new RTCPeerConnection({
iceServers: response.iceServers
});
});
voice:initiate
Start a voice call with the other participant in an active trip.
| Parameter | Type | Required | Description |
| tripId |
string (UUID) |
Required |
Active trip ID |
Response
{
"success": true,
"callId": "call-uuid",
"calleeId": "other-user-uuid",
"calleeType": "DRIVER"
}
Error Codes
| Code | Description |
| USER_BUSY | User is already in another call |
| CALLEE_BUSY | Other participant is in another call |
| CALLEE_OFFLINE | Other participant is not connected |
| NOT_TRIP_PARTICIPANT | You are not part of this trip |
| VOICE_DISABLED | Voice calling is disabled |
Example
// 1. Get ICE servers
socket.emit('voice:getIceServers', {}, async (iceResponse) => {
// 2. Create peer connection
const pc = new RTCPeerConnection({ iceServers: iceResponse.iceServers });
// 3. Add local audio track
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getTracks().forEach(track => pc.addTrack(track, stream));
// 4. Initiate call
socket.emit('voice:initiate', { tripId: 'trip-uuid' }, async (response) => {
if (response.success) {
const callId = response.callId;
// 5. Create and send offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
socket.emit('voice:offer', { callId, tripId: 'trip-uuid', offer });
}
});
});
voice:incoming (Listen)
Received when someone is calling you.
{
"callId": "call-uuid",
"tripId": "trip-uuid",
"callerId": "caller-uuid",
"callerType": "RIDER",
"timestamp": 1702828800000
}
Example
socket.on('voice:incoming', (data) => {
// Show incoming call UI
showIncomingCallDialog({
callId: data.callId,
callerType: data.callerType,
onAccept: () => acceptCall(data.callId, data.tripId),
onReject: () => rejectCall(data.callId, data.tripId)
});
});
voice:accept
Accept an incoming call.
| Parameter | Type | Required | Description |
| callId |
string (UUID) |
Required |
Call ID from voice:incoming |
| tripId |
string (UUID) |
Required |
Trip ID |
Response
{ "success": true, "callId": "call-uuid" }
voice:accepted (Listen)
Received by caller when call is accepted.
{
"callId": "call-uuid",
"tripId": "trip-uuid",
"acceptedBy": "callee-uuid",
"timestamp": 1702828800000
}
voice:reject
Reject an incoming call.
| Parameter | Type | Required | Description |
| callId |
string (UUID) |
Required |
Call ID |
| tripId |
string (UUID) |
Required |
Trip ID |
| reason |
string |
Optional |
Rejection reason |
voice:rejected (Listen)
Received by caller when call is rejected.
{
"callId": "call-uuid",
"tripId": "trip-uuid",
"rejectedBy": "callee-uuid",
"reason": "BUSY",
"timestamp": 1702828800000
}
voice:end
End an active call.
| Parameter | Type | Required | Description |
| callId |
string (UUID) |
Required |
Call ID |
| tripId |
string (UUID) |
Required |
Trip ID |
| reason |
string |
Optional |
NORMAL, CANCELLED, NETWORK_ERROR |
voice:ended (Listen)
Received when call ends.
{
"callId": "call-uuid",
"tripId": "trip-uuid",
"endedBy": "user-uuid",
"reason": "NORMAL",
"duration": 125,
"timestamp": 1702828800000
}
End Reasons
| Reason | Description |
| NORMAL | Call ended normally |
| REJECTED | Call was rejected |
| BUSY | Callee was busy |
| NO_ANSWER | No answer (timeout) |
| FAILED | Connection failed |
| CANCELLED | Caller cancelled |
| NETWORK_ERROR | Network issue |
voice:missed (Listen)
Received when call times out (30 seconds without answer).
{
"callId": "call-uuid",
"tripId": "trip-uuid",
"timestamp": 1702828800000
}
WebRTC Signaling Events
These events relay WebRTC signaling data between peers.
voice:offer
Send WebRTC offer (SDP) to callee. Sent by caller after initiating call.
| Parameter | Type | Required | Description |
| callId |
string |
Required |
Call ID |
| tripId |
string |
Required |
Trip ID |
| offer |
RTCSessionDescription |
Required |
WebRTC offer SDP |
voice:offer (Listen)
Received by callee with the WebRTC offer.
{
"callId": "call-uuid",
"tripId": "trip-uuid",
"offer": {
"type": "offer",
"sdp": "v=0\r\no=- 46117..."
},
"timestamp": 1702828800000
}
Handling Offer
socket.on('voice:offer', async (data) => {
// Set remote description
await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer));
// Create answer
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
// Send answer back
socket.emit('voice:answer', {
callId: data.callId,
tripId: data.tripId,
answer: answer
});
});
voice:answer
Send WebRTC answer (SDP) to caller. Sent by callee after receiving offer.
| Parameter | Type | Required | Description |
| callId |
string |
Required |
Call ID |
| tripId |
string |
Required |
Trip ID |
| answer |
RTCSessionDescription |
Required |
WebRTC answer SDP |
voice:answer (Listen)
Received by caller with the WebRTC answer.
socket.on('voice:answer', async (data) => {
await peerConnection.setRemoteDescription(
new RTCSessionDescription(data.answer)
);
// Connection should now establish
});
voice:ice-candidate
Send ICE candidate to peer. Called for each candidate discovered.
| Parameter | Type | Required | Description |
| callId |
string |
Required |
Call ID |
| tripId |
string |
Required |
Trip ID |
| candidate |
RTCIceCandidate |
Required |
ICE candidate object |
Example
// Send ICE candidates as they're discovered
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.emit('voice:ice-candidate', {
callId: callId,
tripId: tripId,
candidate: event.candidate
});
}
};
voice:ice-candidate (Listen)
Received ICE candidate from peer.
socket.on('voice:ice-candidate', async (data) => {
if (data.candidate) {
await peerConnection.addIceCandidate(
new RTCIceCandidate(data.candidate)
);
}
});
Additional Voice Events
voice:mute
Toggle mute state and notify the other participant.
| Parameter | Type | Required | Description |
| callId |
string |
Required |
Call ID |
| tripId |
string |
Required |
Trip ID |
| muted |
boolean |
Required |
Mute state |
voice:muted (Listen)
Received when other participant toggles mute.
{
"callId": "call-uuid",
"tripId": "trip-uuid",
"userId": "user-uuid",
"muted": true,
"timestamp": 1702828800000
}
voice:busy (Listen)
Received when the callee is already in another call.
{
"callId": "call-uuid",
"tripId": "trip-uuid",
"calleeId": "user-uuid",
"timestamp": 1702828800000
}
voice:history
Get call history for a trip.
| Parameter | Type | Required | Description |
| tripId |
string |
Required |
Trip ID |
| limit |
number |
Optional |
Max results (default: 10) |
Response
{
"calls": [
{
"callId": "call-uuid",
"callerId": "user-uuid",
"callerType": "RIDER",
"calleeId": "driver-uuid",
"calleeType": "DRIVER",
"state": "ENDED",
"duration": 125,
"endReason": "NORMAL",
"initiatedAt": 1702828600000,
"endedAt": 1702828725000
}
],
"tripId": "trip-uuid"
}
Complete Voice Call Flow Example
// === CALLER SIDE ===
class VoiceCaller {
constructor(socket, tripId) {
this.socket = socket;
this.tripId = tripId;
this.peerConnection = null;
this.callId = null;
}
async startCall() {
// 1. Get ICE servers
const iceServers = await new Promise(resolve => {
this.socket.emit('voice:getIceServers', {}, resolve);
});
// 2. Create peer connection
this.peerConnection = new RTCPeerConnection({
iceServers: iceServers.iceServers
});
// 3. Set up ICE candidate handling
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.socket.emit('voice:ice-candidate', {
callId: this.callId,
tripId: this.tripId,
candidate: event.candidate
});
}
};
// 4. Handle incoming audio
this.peerConnection.ontrack = (event) => {
const remoteAudio = document.getElementById('remoteAudio');
remoteAudio.srcObject = event.streams[0];
};
// 5. Add local audio
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getTracks().forEach(track => {
this.peerConnection.addTrack(track, stream);
});
// 6. Initiate call
const response = await new Promise(resolve => {
this.socket.emit('voice:initiate', { tripId: this.tripId }, resolve);
});
if (!response.success) {
throw new Error(response.error);
}
this.callId = response.callId;
// 7. Create and send offer
const offer = await this.peerConnection.createOffer();
await this.peerConnection.setLocalDescription(offer);
this.socket.emit('voice:offer', {
callId: this.callId,
tripId: this.tripId,
offer: offer
});
// 8. Listen for answer
this.socket.on('voice:answer', async (data) => {
await this.peerConnection.setRemoteDescription(
new RTCSessionDescription(data.answer)
);
});
// 9. Listen for ICE candidates
this.socket.on('voice:ice-candidate', async (data) => {
if (data.candidate) {
await this.peerConnection.addIceCandidate(
new RTCIceCandidate(data.candidate)
);
}
});
}
endCall() {
this.socket.emit('voice:end', {
callId: this.callId,
tripId: this.tripId,
reason: 'NORMAL'
});
this.peerConnection?.close();
}
}
// === CALLEE SIDE ===
class VoiceCallee {
constructor(socket) {
this.socket = socket;
this.peerConnection = null;
// Listen for incoming calls
socket.on('voice:incoming', (data) => {
this.handleIncomingCall(data);
});
}
async handleIncomingCall(data) {
// Show UI and wait for user action
const accepted = await showIncomingCallUI(data);
if (!accepted) {
this.socket.emit('voice:reject', {
callId: data.callId,
tripId: data.tripId
});
return;
}
// Accept call
this.socket.emit('voice:accept', {
callId: data.callId,
tripId: data.tripId
});
// Set up peer connection (similar to caller)
// ...handle voice:offer, create answer, exchange ICE candidates
}
}
/support Namespace
Real-time support chat for users (riders/drivers) to communicate with admin support staff. Messages are persisted in Firebase and synced with the admin service.
Connection URL
wss://realtime.simpliride.com/support
User Types
Support chat is available for RIDER and DRIVER user types.
support:create-conversation
Create a new support conversation.
Request Payload
| Parameter | Type | Required | Description |
| subject |
string |
Required |
Subject/title of the conversation |
| priority |
string |
Optional |
Priority: low, normal, high, urgent (default: normal) |
| relatedTripId |
string (UUID) |
Optional |
Related trip ID if applicable |
Response
{ "success": true, "conversationId": "uuid", "message": "Conversation created" }
Example
socket.emit('support:create-conversation', {
subject: 'Issue with my last trip',
priority: 'normal',
relatedTripId: 'trip-uuid'
}, (response) => {
if (response.success) {
console.log('Conversation created:', response.conversationId);
}
});
support:join
Join a conversation room to receive real-time updates.
| Parameter | Type | Required | Description |
| conversationId |
string (UUID) |
Required |
The conversation ID to join |
socket.emit('support:join', { conversationId: 'conv-uuid' });
support:message
Send a message in a conversation.
Request Payload
| Parameter | Type | Required | Description |
| conversationId |
string (UUID) |
Required |
The conversation ID |
| content |
string |
Required |
Message content |
| attachmentUrl |
string |
Optional |
URL of attached file |
| attachmentType |
string |
Optional |
Type: image, document, audio |
| replyToMessageId |
string (UUID) |
Optional |
ID of message being replied to |
Example
socket.emit('support:message', {
conversationId: 'conv-uuid',
content: 'Hello, I need help with my trip'
}, (response) => {
if (response.success) {
console.log('Message sent:', response.messageId);
}
});
support:message (Listen)
Received when a new message arrives from admin.
{
"messageId": "uuid",
"conversationId": "uuid",
"senderId": "admin-uuid",
"senderType": "ADMIN",
"senderName": "Support Agent",
"content": "Hello, how can I help you?",
"attachmentUrl": null,
"attachmentType": null,
"timestamp": 1702828800000
}
support:typing
Send typing indicator.
| Parameter | Type | Required | Description |
| conversationId |
string (UUID) |
Required |
The conversation ID |
| isTyping |
boolean |
Required |
Whether user is typing |
socket.emit('support:typing', { conversationId: 'conv-uuid', isTyping: true });
support:typing (Listen)
Received when admin is typing.
{
"conversationId": "uuid",
"adminId": "admin-uuid",
"adminName": "Support Agent",
"isTyping": true
}
support:status (Listen)
Received when conversation status changes.
{
"conversationId": "uuid",
"status": "RESOLVED",
"resolvedAt": "2024-01-15T10:30:00Z",
"timestamp": 1702828800000
}
Status Values
| Status | Description |
| OPEN | New conversation, not yet assigned |
| IN_PROGRESS | Assigned to admin and being handled |
| RESOLVED | Issue resolved, awaiting feedback |
| CLOSED | Conversation closed |
support:assigned (Listen)
Received when an admin is assigned to the conversation.
{
"conversationId": "uuid",
"adminId": "admin-uuid",
"adminName": "Support Agent",
"timestamp": 1702828800000
}
support:history
Get message history for a conversation.
| Parameter | Type | Required | Description |
| conversationId |
string (UUID) |
Required |
The conversation ID |
| limit |
number |
Optional |
Max messages (default: 50) |
Response
{
"success": true,
"messages": [
{
"messageId": "uuid",
"senderId": "user-uuid",
"senderType": "USER",
"content": "Hello",
"timestamp": 1702828800000
}
],
"hasMore": false
}
support:conversations
Get list of user's support conversations.
| Parameter | Type | Required | Description |
| limit |
number |
Optional |
Max conversations (default: 20) |
Response
{
"success": true,
"conversations": [
{
"conversationId": "uuid",
"subject": "Trip Issue",
"status": "IN_PROGRESS",
"priority": "NORMAL",
"assignedAdminName": "Support Agent",
"lastMessageAt": "2024-01-15T10:30:00Z",
"unreadCount": 2
}
]
}
Admin Internal API
The admin service communicates with the realtime gateway via HTTP to send messages and manage conversations in real-time.
Internal API
These endpoints are for admin-service to realtime-gateway communication only. They require the x-internal-api-key header.
Base URL
/api/v1/internal/support-chat
Available Endpoints
| Method | Endpoint | Description |
| POST |
/conversations/{id}/messages |
Send message from admin to user |
| POST |
/conversations/{id}/status |
Update conversation status |
| POST |
/conversations/{id}/assign |
Assign admin to conversation |
| POST |
/conversations/{id}/typing |
Send admin typing indicator |
| POST |
/conversations/{id}/read |
Mark messages as read |
| POST |
/conversations/sync |
Sync conversation to Firebase |
| POST |
/conversations/{id}/messages/fetch |
Fetch messages from Firebase |
Complete Support Chat Example
// Connect to support namespace
const socket = io('https://realtime.simpliride.com/support', {
query: { userId: 'user-uuid', userType: 'RIDER' },
transports: ['websocket']
});
let currentConversationId = null;
// Create a new support conversation
function createConversation(subject, relatedTripId = null) {
socket.emit('support:create-conversation', {
subject,
priority: 'normal',
relatedTripId
}, (response) => {
if (response.success) {
currentConversationId = response.conversationId;
joinConversation(currentConversationId);
}
});
}
// Join conversation room
function joinConversation(conversationId) {
socket.emit('support:join', { conversationId }, (response) => {
if (response.success) {
loadHistory(conversationId);
}
});
}
// Load message history
function loadHistory(conversationId) {
socket.emit('support:history', { conversationId, limit: 50 }, (response) => {
if (response.success) {
displayMessages(response.messages);
}
});
}
// Send a message
function sendMessage(content) {
socket.emit('support:message', {
conversationId: currentConversationId,
content
}, (response) => {
if (response.success) {
console.log('Message sent:', response.messageId);
}
});
}
// Listen for incoming messages
socket.on('support:message', (message) => {
if (message.conversationId === currentConversationId) {
displayNewMessage(message);
socket.emit('support:read', { conversationId: currentConversationId });
}
});
// Listen for admin typing
socket.on('support:typing', (data) => {
if (data.conversationId === currentConversationId) {
showTypingIndicator(data.adminName, data.isTyping);
}
});
// Listen for status changes
socket.on('support:status', (data) => {
if (data.conversationId === currentConversationId) {
updateConversationStatus(data.status);
}
});
// Listen for admin assignment
socket.on('support:assigned', (data) => {
if (data.conversationId === currentConversationId) {
showNotification(`${data.adminName} is now handling your request`);
}
});
Android (Kotlin)
// build.gradle
implementation 'io.socket:socket.io-client:2.1.0'
// Usage
import io.socket.client.IO
import io.socket.client.Socket
import org.json.JSONObject
class SocketManager {
private lateinit var socket: Socket
fun connect(userId: String, userType: String) {
val options = IO.Options().apply {
query = "userId=$userId&userType=$userType"
transports = arrayOf("websocket")
reconnection = true
reconnectionAttempts = 5
reconnectionDelay = 1000
}
socket = IO.socket("https://realtime.simpliride.com/trip", options)
socket.on(Socket.EVENT_CONNECT) {
Log.d("Socket", "Connected!")
}
socket.on("trip:request") { args ->
val tripRequest = args[0] as JSONObject
// Handle new trip request
}
socket.on("trip:status") { args ->
val status = args[0] as JSONObject
// Handle status update
}
socket.connect()
}
fun acceptTrip(tripId: String, lat: Double, lng: Double) {
val data = JSONObject().apply {
put("tripId", tripId)
put("latitude", lat)
put("longitude", lng)
}
socket.emit("trip:accept", data) { response ->
val result = response[0] as JSONObject
if (result.getBoolean("success")) {
// Trip accepted
}
}
}
fun updateLocation(lat: Double, lng: Double, speed: Float?, tripId: String?) {
val data = JSONObject().apply {
put("latitude", lat)
put("longitude", lng)
speed?.let { put("speed", it) }
tripId?.let { put("tripId", it) }
}
socket.emit("location:update", data)
}
fun disconnect() {
socket.disconnect()
}
}
iOS (Swift)
// Package.swift or CocoaPods
// pod 'Socket.IO-Client-Swift', '~> 16.0'
import SocketIO
class SocketManager {
private var manager: SocketManager!
private var socket: SocketIOClient!
func connect(userId: String, userType: String) {
manager = SocketManager(
socketURL: URL(string: "https://realtime.simpliride.com")!,
config: [
.log(true),
.compress,
.connectParams([
"userId": userId,
"userType": userType
])
]
)
socket = manager.socket(forNamespace: "/trip")
socket.on(clientEvent: .connect) { data, ack in
print("Connected!")
}
socket.on("trip:request") { [weak self] data, ack in
guard let tripRequest = data[0] as? [String: Any] else { return }
// Handle new trip request
}
socket.on("trip:status") { [weak self] data, ack in
guard let status = data[0] as? [String: Any] else { return }
// Handle status update
}
socket.connect()
}
func acceptTrip(tripId: String, location: CLLocationCoordinate2D) {
let data: [String: Any] = [
"tripId": tripId,
"latitude": location.latitude,
"longitude": location.longitude
]
socket.emitWithAck("trip:accept", data).timingOut(after: 10) { response in
if let result = response[0] as? [String: Any],
let success = result["success"] as? Bool, success {
// Trip accepted
}
}
}
func updateLocation(_ location: CLLocation, tripId: String?) {
var data: [String: Any] = [
"latitude": location.coordinate.latitude,
"longitude": location.coordinate.longitude,
"speed": max(0, location.speed * 3.6), // m/s to km/h
"heading": location.course
]
if let tripId = tripId {
data["tripId"] = tripId
}
socket.emit("location:update", data)
}
func disconnect() {
socket.disconnect()
}
}
Web (JavaScript/TypeScript)
// npm install socket.io-client
import { io, Socket } from 'socket.io-client';
interface TripRequest {
tripId: string;
riderId: string;
riderName: string;
pickup: { latitude: number; longitude: number; address: string };
dropoff: { latitude: number; longitude: number; address: string };
estimatedFare: number;
}
class SimpliRideSocket {
private socket: Socket | null = null;
connect(userId: string, userType: 'RIDER' | 'DRIVER'): void {
this.socket = io('https://realtime.simpliride.com/trip', {
query: { userId, userType },
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
});
this.socket.on('connect', () => {
console.log('Connected to SimpliRide Gateway');
});
this.socket.on('connected', (data) => {
console.log('Session established:', data);
});
this.socket.on('trip:request', (trip: TripRequest) => {
console.log('New trip request:', trip);
// Show trip to driver
});
this.socket.on('trip:status', (status) => {
console.log('Trip status update:', status);
// Update UI
});
this.socket.on('trip:eta', (eta) => {
console.log('ETA update:', eta);
});
this.socket.on('driver:location', (location) => {
console.log('Driver location:', location);
// Update map marker
});
this.socket.on('error', (error) => {
console.error('Socket error:', error);
});
this.socket.on('disconnect', (reason) => {
console.log('Disconnected:', reason);
});
}
subscribeToTrip(tripId: string): Promise<boolean> {
return new Promise((resolve) => {
this.socket?.emit('trip:subscribe', { tripId }, (response: any) => {
resolve(response.success);
});
});
}
acceptTrip(tripId: string, latitude?: number, longitude?: number): Promise<any> {
return new Promise((resolve, reject) => {
this.socket?.emit('trip:accept', { tripId, latitude, longitude }, (response: any) => {
if (response.success) {
resolve(response);
} else {
reject(new Error(response.error || 'Failed to accept trip'));
}
});
});
}
cancelTrip(tripId: string, reason?: string): void {
this.socket?.emit('trip:cancel', { tripId, reason });
}
triggerEmergency(tripId: string, type: string, latitude?: number, longitude?: number): void {
this.socket?.emit('emergency:trigger', { tripId, type, latitude, longitude });
}
disconnect(): void {
this.socket?.disconnect();
this.socket = null;
}
}
// Usage
const socket = new SimpliRideSocket();
socket.connect('user-id', 'RIDER');
Flutter (Dart)
// pubspec.yaml
// socket_io_client: ^2.0.3
import 'package:socket_io_client/socket_io_client.dart' as IO;
class SocketService {
late IO.Socket socket;
void connect(String userId, String userType) {
socket = IO.io(
'https://realtime.simpliride.com/trip',
IO.OptionBuilder()
.setTransports(['websocket'])
.setQuery({'userId': userId, 'userType': userType})
.enableReconnection()
.setReconnectionAttempts(5)
.setReconnectionDelay(1000)
.build(),
);
socket.onConnect((_) {
print('Connected to SimpliRide Gateway');
});
socket.on('connected', (data) {
print('Session: $data');
});
socket.on('trip:request', (data) {
print('New trip request: $data');
// Handle trip request
});
socket.on('trip:status', (data) {
print('Status update: $data');
});
socket.on('driver:location', (data) {
print('Driver location: $data');
// Update map
});
socket.onError((error) {
print('Socket error: $error');
});
socket.onDisconnect((_) {
print('Disconnected');
});
socket.connect();
}
Future<bool> subscribeToTrip(String tripId) async {
final completer = Completer<bool>();
socket.emitWithAck('trip:subscribe', {'tripId': tripId}, ack: (response) {
completer.complete(response['success'] ?? false);
});
return completer.future;
}
void acceptTrip(String tripId, double lat, double lng) {
socket.emitWithAck('trip:accept', {
'tripId': tripId,
'latitude': lat,
'longitude': lng,
}, ack: (response) {
if (response['success']) {
print('Trip accepted!');
}
});
}
void updateLocation(double lat, double lng, {double? speed, String? tripId}) {
socket.emit('location:update', {
'latitude': lat,
'longitude': lng,
if (speed != null) 'speed': speed,
if (tripId != null) 'tripId': tripId,
});
}
void sendMessage(String tripId, String message) {
socket.emit('chat:message', {
'tripId': tripId,
'message': message,
});
}
void disconnect() {
socket.disconnect();
}
}
Need Help?
Try out events in real-time with our interactive test console.
Open Test Console