Each block below is one network call, in the order it happens during a booking. For every call: URL, direction, payload, response, and what it does.
Customer creates a new booking. Server picks a driver, inserts the row, then pushes booking_assigned on the driver's socket.
POST /api/mobile/customer/booking/request Authorization: Bearer <firebase_id_token> Content-Type: application/json
{
"pickup_location_title": "Home",
"pickup_location_address": "12 King St, Sydney",
"pickup_map_id": "ChIJxxxxxxxxxxx",
"pickup_latitude": -33.8688,
"pickup_longitude": 151.2093,
"no_of_passengers": 2,
"note": null,
"scheduled_date": null,
"drop_offs": [
{
"drop_off_location_title": "Airport",
"drop_off_location_address": "Sydney Airport",
"drop_off_map_id": "ChIJxxxxxxxxxxx",
"drop_off_latitude": -33.9399,
"drop_off_longitude": 151.1753
}
]
}
{
"meta": { "code": 201, "status": "success", "timestamp": "..." },
"code": "CREATED",
"data": { "booking_id": "uuid", "driver_id": "uuid" },
"user_message": { "message": "Booking request submitted successfully." }
}
Server tells the assigned driver about the new ride. Fired automatically after Step 1.
WS /ws/driver?token=<firebase_id_token>
Server → Driver
{
"type": "booking_assigned",
"booking_id": "uuid",
"booking_status": "requested",
"booking_type": "instant",
"date": "2026-05-12T10:30:00.000Z",
"no_of_passengers": 2,
"pickup_location": {
"title": "Home",
"address": "12 King St, Sydney NSW",
"map_id": "ChIJP3Sa8ziYEmsRUKgyFmh9AQM",
"latitude": -33.8688,
"longitude": 151.2093
},
"dropoff_location": {
"title": "Sydney Airport",
"address": "Mascot NSW 2020",
"map_id": "ChIJ-aOOcQO6EmsRUaff7uXNFnQ",
"latitude": -33.9399,
"longitude": 151.1753
}
}
Driver accepts an instant booking by sending how many seconds until pickup. DB: arrival_time = N, booking_status = "confirmation". Triggers Step 4. Starts the customer-response timer (default 2 min — configurable via the CUSTOMER_WAITING_TIME env var, in seconds).
WS /ws/driver?token=<firebase_id_token>
Driver → Server
{
"type": "submit_arrival_time",
"booking_id": "uuid",
"arrival_time": 300
}
// Driver gets ack (socket stays open, UI flips status to "confirmation"): { "type": "arrival_time_accepted", "booking_id": "uuid", "booking_status": "confirmation", "arrival_time": { "value": 5, "unit": "minutes", "label": "5 min", "seconds": 300 } } // Customer gets `arrival_time_offered` (Step 4). // On failure: { "type": "error", "code": "BOOKING_NOT_ASSIGNABLE" | "INVALID_PAYLOAD", "message": "..." }
Server forwards the driver's arrival time to the customer with a friendly display shape.
WS /ws/customer?token=<firebase_id_token>
Server → Customer
{
"type": "arrival_time_offered",
"booking_id": "uuid",
"booking_status": "confirmation",
"arrival_time": {
"value": 5,
"unit": "minutes",
"label": "5 min",
"seconds": 300
},
"no_of_passengers": 2,
"pickup_location": {
"title": "Home",
"address": "12 King St, Sydney NSW",
"map_id": "ChIJP3Sa8ziYEmsRUKgyFmh9AQM",
"latitude": -33.8688,
"longitude": 151.2093
},
"dropoff_location": {
"title": "Sydney Airport",
"address": "Mascot NSW 2020",
"map_id": "ChIJ-aOOcQO6EmsRUaff7uXNFnQ",
"latitude": -33.9399,
"longitude": 151.1753
}
}
Customer confirms the offer. DB: booking_status = "on_the_way". Cancels the customer-response timer. Triggers Step 6.
WS /ws/customer?token=<firebase_id_token>
Customer → Server
{ "type": "accept", "booking_id": "uuid" }
// Customer gets ack (socket stays open for the ride): { "type": "accept_accepted", "booking_id": "uuid", "booking_status": "on_the_way" } // Driver gets `customer_accepted` (Step 6). // On failure: { "type": "error", "code": "BOOKING_NOT_ACCEPTABLE", "message": "..." }
Server tells the driver the ride is on. Includes the customer's name.
WS /ws/driver?token=<firebase_id_token>
Server → Driver
{
"type": "customer_accepted",
"booking_id": "uuid",
"booking_status": "on_the_way",
"no_of_passengers": 2,
"customer_name": "Alice",
"pickup_location": {
"title": "Home",
"address": "12 King St, Sydney NSW",
"map_id": "ChIJP3Sa8ziYEmsRUKgyFmh9AQM",
"latitude": -33.8688,
"longitude": 151.2093
},
"dropoff_location": {
"title": "Sydney Airport",
"address": "Mascot NSW 2020",
"map_id": "ChIJ-aOOcQO6EmsRUaff7uXNFnQ",
"latitude": -33.9399,
"longitude": 151.1753
}
}
Driver ends the ride. DB: booking_status = "completed". Triggers Step 8.
WS /ws/driver?token=<firebase_id_token>
Driver → Server
{ "type": "mark_as_completed", "booking_id": "uuid", "status": "completed" }
// Driver gets ack (socket stays open for the next assignment): { "type": "complete_accepted", "booking_id": "uuid", "status": "completed" } // Customer gets `ride_completed` (Step 8), socket closes 200ms later. // On failure: { "type": "error", "code": "BOOKING_NOT_COMPLETABLE", "message": "..." }
Server tells the customer the ride is finished. Customer socket auto-closes ~200ms later.
WS /ws/customer?token=<firebase_id_token>
Server → Customer
{ "type": "ride_completed", "booking_id": "uuid", "status": "completed" }
Driver refuses a requested booking. Each booking gets up to 3 driver attempts total (initial + 2 retries), with cumulative exclusion — no driver is asked twice. An attempt is consumed by an explicit decline, no response within the 30-second driver-response window, or no eligible driver available.
If attempts remain AND a replacement is found → new driver gets booking_assigned, declining driver gets { reassigned: true } (customer sees nothing). If attempts are exhausted OR no eligible driver is left → booking is cancelled with ride_cancelled_by = "admin" and customer gets driver_cancelled with reason_code: "NO_DRIVER_AVAILABLE"; declining driver gets { reassigned: false }.
WS /ws/driver?token=<firebase_id_token>
Driver → Server
{
"type": "decline_booking",
"booking_id": "uuid",
// exactly one of:
"cancellation_template_id": "uuid", // pre-saved template (admin or driver's own)
"cancellation_description": "free-text reason" // up to 512 chars
}
{ "type": "decline_accepted", "booking_id": "uuid", "reassigned": true | false }
Server tells the customer the booking ran out of driver-assignment attempts — i.e. up to 3 drivers were tried and the last one either declined, didn't respond within the 30-second window, or no eligible driver was left. A single decline is never surfaced; only the terminal cancellation is. Customer socket auto-closes ~200ms later.
WS /ws/customer?token=<firebase_id_token>
Server → Customer
{
"type": "driver_cancelled",
"booking_id": "uuid",
"status": "cancelled",
"reason": "No driver available after multiple attempts.",
"reason_code": "NO_DRIVER_AVAILABLE"
}
Customer cancels their own booking. Allowed from requested or confirmation. Scheduled bookings: blocked within 1 hr of pickup. DB: booking_status = "cancelled", ride_cancelled_by = "customer". Customer socket auto-closes ~200 ms after the ack.
WS /ws/customer?token=<firebase_id_token>
Customer → Server
{ "type": "reject", "booking_id": "uuid" }
// Customer gets ack, then socket terminates ~200ms later: { "type": "reject_accepted", "booking_id": "uuid", "status": "cancelled" } // Driver gets: { "type": "customer_rejected", "booking_id": "uuid", "status": "cancelled" } // On failure: { "type": "error", "code": "REJECT_WINDOW_CLOSED" | "BOOKING_NOT_REJECTABLE", "message": "..." }
If the customer doesn't accept or reject within the customer-response window (default 2 min, configurable via CUSTOMER_WAITING_TIME) after Step 4, the server auto-accepts on their behalf — same effect as a manual accept. DB: booking_status = "on_the_way". Customer socket stays open for the ride.
WS /ws/customer?token=<firebase_id_token> WS /ws/driver?token=<firebase_id_token>
Server → Customer AND Server → Driver
// Customer side (socket stays open): { "type": "offer_auto_accepted", "booking_id": "uuid", "booking_status": "on_the_way" } // Driver side — same shape as a manual customer_accepted, plus auto_accepted flag: { "type": "customer_accepted", "booking_id": "uuid", "booking_status": "on_the_way", "no_of_passengers": 2, "customer_name": "Alice", "customer_phone": "...", "customer_phone_code": { ... }, "pickup_location": { ... }, "dropoff_location": { ... }, "auto_accepted": true }
Driver accepts a scheduled booking. No arrival time involved. Triggers booking_confirmed on the customer side.
WS /ws/driver?token=<firebase_id_token>
Driver → Server
{ "type": "accept_booking", "booking_id": "uuid" }
{ "type": "accept_accepted", "booking_id": "uuid" }
Server tells the customer their scheduled booking has been confirmed by a driver.
WS /ws/customer?token=<firebase_id_token>
Server → Customer
{
"type": "booking_confirmed",
"booking_id": "uuid",
"booking_status": "confirmation",
"scheduled_date": "2026-05-13T08:00:00.000Z",
"no_of_passengers": 2,
"driver_name": "Bob",
"pickup_location": {
"title": "Home",
"address": "12 King St, Sydney NSW",
"map_id": "ChIJP3Sa8ziYEmsRUKgyFmh9AQM",
"latitude": -33.8688,
"longitude": 151.2093
},
"dropoff_location": {
"title": "Sydney Airport",
"address": "Mascot NSW 2020",
"map_id": "ChIJ-aOOcQO6EmsRUaff7uXNFnQ",
"latitude": -33.9399,
"longitude": 151.1753
}
}
One-to-one chat between the assigned customer and driver, on the same WS sockets. Only available while the booking is on_the_way — before that (driver assigned, arrival-time offer, etc.) chat is disabled. Stored in process memory only — no DB. Chat history is wiped the moment the booking enters a terminal state (completed, cancelled, driver_cancelled, customer reject). On reconnect, the latest history is delivered as part of the snapshot via resync. Bounded to 200 messages × 1000 chars per message.
Send a text message to the other party. Allowed only while the booking is on_the_way and the sender is the booking's customer or driver.
WS /ws/driver OR /ws/customer
Client → Server (then Server fans out to both sides)
{
"type": "chat_message",
"booking_id": "uuid",
"text": "Hi, I'm at the front entrance."
}
{
"type": "chat_message",
"booking_id": "uuid",
"from": "driver" | "customer",
"text": "Hi, I'm at the front entrance.",
"sent_at": "2026-05-12T10:32:15.000Z"
}
// On failure:
{ "type": "error", "code": "BOOKING_NOT_CHATTABLE" | "INVALID_PAYLOAD", "message": "..." }
The snapshot sent in response to resync includes the in-memory chat_history array, so a reconnecting client can rehydrate the conversation:
{
"type": "snapshot",
...
"chat_history": [
{ "from": "customer", "text": "I'm at the gate.", "sent_at": "..." },
{ "from": "driver", "text": "Two minutes away.", "sent_at": "..." }
]
}
Every booking-detail payload now carries the other party's phone so each side can call the other if needed.
{
...
"customer_name": "Alice",
"customer_phone": "+64211234567",
"customer_phone_code": {
"id": "nz000000-0000-0000-0000-000000000000",
"name": "New Zealand",
"code": "NZ",
"ph_code": "+64",
"flag": "https://.../uploads/flags/nz.png"
}
}
{
...
"driver_name": "Bob",
"driver_phone": "+64219876543",
"driver_phone_code": { "id": "...", "name": "...", "code": "...", "ph_code": "+64", "flag": "..." }
}
Server's first message after a successful WS handshake. Lets the client confirm auth + know its own ID.
WS /ws/driver OR /ws/customer
Server → Client
// Driver: { "type": "connected", "driver_id": "uuid" } // Customer: { "type": "connected", "customer_id": "uuid" }
After a network drop the client reconnects and asks the server to rebuild its state from DB. Useful when the client missed a push while offline.
WS /ws/driver OR /ws/customer
Client → Server
// Driver — server finds its most-recent active booking automatically: { "type": "resync" } // Customer — must include booking_id: { "type": "resync", "booking_id": "uuid" }
// Driver: { "type": "snapshot", "assignment": { "booking_id": "uuid", "booking_status": "requested" | "confirmation" | "on_the_way", "booking_type": "instant" | "scheduled", "date": "ISO string", "no_of_passengers": 2, "arrival_time": { "value": 5, "unit": "minutes", "label": "5 min", "seconds": 300 } | null, "customer_name": "Alice" | null, "pickup_location": { "title": "Home", "address": "12 King St, Sydney NSW", "map_id": "ChIJP3Sa8ziYEmsRUKgyFmh9AQM", "latitude": -33.8688, "longitude": 151.2093 }, "dropoff_location": { "title": "Sydney Airport", "address": "Mascot NSW 2020", "map_id": "ChIJ-aOOcQO6EmsRUaff7uXNFnQ", "latitude": -33.9399, "longitude": 151.1753 } } } // `assignment: null` means no active booking. // Customer: { "type": "snapshot", "booking_id": "uuid", "booking_status": "...", "no_of_passengers": 2, "arrival_time": { "value": 5, "unit": "minutes", "label": "5 min", "seconds": 300 } | null, "pickup_location": { "title": "Home", "address": "12 King St, Sydney NSW", "map_id": "ChIJP3Sa8ziYEmsRUKgyFmh9AQM", "latitude": -33.8688, "longitude": 151.2093 }, "dropoff_location": { "title": "Sydney Airport", "address": "Mascot NSW 2020", "map_id": "ChIJ-aOOcQO6EmsRUaff7uXNFnQ", "latitude": -33.9399, "longitude": 151.1753 } }
App-level keepalive. Server also sends WS-level ping frames every 30 s and terminates sockets that miss a pong.
WS /ws/driver OR /ws/customer
Client → Server
{ "type": "ping" }
{ "type": "pong" }
Any failed action returns one of these. Some errors also terminate the socket (auth failures, session replacement).
Server → Client
{ "type": "error", "code": "<CODE>", "message": "human readable" }
UNAUTHORIZED — no token INVALID_TOKEN — bad/expired Firebase token SESSION_REPLACED — same user connected from elsewhere INVALID_JSON — message wasn't valid JSON INVALID_PAYLOAD — required field missing/wrong type BOOKING_NOT_ASSIGNABLE — submit_arrival_time on wrong booking/state BOOKING_NOT_ACCEPTABLE — accept on wrong booking/state BOOKING_NOT_REJECTABLE — reject too late or wrong booking BOOKING_NOT_DECLINABLE — decline_booking on wrong booking/state BOOKING_NOT_COMPLETABLE — mark_as_completed on wrong booking/state REJECT_WINDOW_CLOSED — scheduled reject within 1hr of pickup INVALID_TEMPLATE — bad cancellation_template_id UNKNOWN_MESSAGE_TYPE — unrecognised `type` INTERNAL_ERROR — unexpected server-side error while handling the message BOOKING_NOT_CHATTABLE — chat sent before the booking is on_the_way, or for a booking that isn't yours / has ended
requested ──submit_arrival_time / accept_booking──▶ confirmation
│
├──customer accept──▶ on_the_way ──mark_as_completed──▶ completed
│
├──customer reject──▶ cancelled (by "customer")
│
└──⏱ customer timeout─▶ on_the_way (auto-accepted; window from CUSTOMER_WAITING_TIME, default 2 min)
requested ──[3rd driver attempt declines / times out / no driver left]─▶ cancelled (by "admin", reason_code="NO_DRIVER_AVAILABLE")
requested ──customer reject────────────────────────────────────────────▶ cancelled (by "customer")