Basics
Push notifications
Registering a device, the shape of every push, and the full list of events each app receives.
Tote sends pushes through Firebase Cloud Messaging (FCM). Each push carries a visible notification (title and body) and a data payload the app uses to decide where to navigate.
Registering the device#
After login, send the FCM token to profile/notification-token for the app's audience — customer, driver or merchant:
{ "token": "fcm-token…", "platform": "android", "device_id": "c3a1…" }
platformisandroid,iosorweb.device_idis a stable id for the install. Sending the samedevice_idagain replaces its token, so call this whenever FCM rotates the token.- On logout, send the same
device_idso the server forgets the device; see Logging out.
The data payload#
{ "type": "order", "event": "status_update", "user": "customer", "order_id": "1042", "status": "out_for_delivery" }
- Every value is a string, including ids — FCM only allows strings. Parse ids before using them.
typesays which kind of record the push is about;eventsays what happened.useris the audience the push was built for:customer,driverormerchant.- The other keys depend on
type:
type |
Extra keys |
|---|---|
order |
order_id; status on status_update |
return |
return_id, order_id, store_id |
driver_store |
store_id, driver_id |
friendship |
friendship_id, sender_id, receiver_id; status on friend_request_answered |
store |
store_id |
staff |
none |
Route on type and event, and ignore pairs you do not know: new events are added over time.
Events by app#
Customer app#
type |
event |
When |
|---|---|---|
order |
status_update |
The order moved to a new status |
order |
order_paid |
Payment confirmed |
order |
payment_failed |
A payment attempt failed |
return |
return_opened |
The store opened a Counter Return the customer must confirm |
return |
return_dropped_off, return_store_responded, return_approved, return_rejected, return_refunded, return_expired |
The Return moved on |
friendship |
friend_request, friend_request_answered |
Friend requests |
Merchant app (the store team)#
Store pushes go to the owner and every active staff member of the store.
type |
event |
When |
|---|---|---|
order |
new_order |
An order was placed |
order |
order_paid |
The order's payment was confirmed |
order |
order_cancelled |
The order was cancelled |
return |
return_opened, return_confirmed, return_approved, return_rejected |
A Return needs attention or was decided |
driver_store |
join_request, invitation_accepted, invitation_rejected |
A driver asked to join, or answered an invitation |
store |
store_forced_offline, store_back_online |
Tote took the store offline for a while, or brought it back |
staff |
staff_deactivated |
Sent to a staff member whose account the owner just deactivated |
Driver app#
type |
event |
When |
|---|---|---|
order |
order_assigned |
A store assigned an order to the driver |
order |
order_cancelled |
An order assigned to the driver was cancelled |
driver_store |
store_invitation |
A store invited the driver |
driver_store |
join_request_approved, join_request_rejected |
A store answered the driver's request |
Language#
The title and body are written in the platform's default language, not the device's: the server does not store a language preference per account yet. If the app needs its own wording, build it from type, event and the ids.
The notification list#
Every push sent to an account is also kept on the server, so the app can show an inbox — including pushes that arrived while the device was offline or had no FCM token. Each audience has profile/notifications endpoints to list (newest first, paginated), mark as read and delete them: customer, driver, merchant.
{
"id": "9d2c…",
"data": { "title": "Order Status Updated", "body": "…", "type": "order", "event": "status_update", "user": "customer", "order_id": 1042, "status": "accepted" },
"read_at": null,
"created_at": "2026-10-01T18:30:00.000000Z"
}
data holds the push's title and body next to the same keys as the push payload, so tapping an inbox entry can open the same screen the push would. Unlike the push itself, ids here are numbers. read_at is null until the app marks the entry read.
The staff_deactivated push is the one exception: it is sent to the devices only, because the account is being switched off.