Notifications Module — Vendor surface
Vendor-facing HTTP surface for mobile device registration so the vendor app can receive push notifications (new-order alerts, payout updates, application status, etc.). The wider…
Vendor-facing HTTP surface for mobile device registration so the vendor app can receive push notifications (new-order alerts, payout updates, application status, etc.). The wider notifications module is event-driven — it listens for domain events, resolves recipients, renders templates, and dispatches to FCM/email — but the only vendor-callable endpoints register/unregister an FCM token bound to the logged-in user's vendor app.
Source:
api-modules/notifications/src/controllers/vendor-device.controller.ts.
Conventions
Authentication
Both endpoints require a Better-Auth bearer session.
Authorization: Bearer <session-token>Devices are scoped by session.user.id (not by active vendor). A single user who is a member of multiple vendor organizations re-uses the same device row — push delivery happens based on the event payload, not the registration call.
Tenant scoping
The device row is stamped with appKind: "vendor" so the platform can address the vendor audience separately from the storefront customer audience. The same physical device may register both a "customer" row (via /store/devices) and a "vendor" row (via this surface) when the same OS account uses both apps.
Response envelope
{
"data": <payload>,
"message": "Success",
"statusCode": 200,
"metadata": { /* optional */ }
}Error envelope
statusCode | errorCode examples |
|---|---|
| 400 | BAD_REQUEST, VALIDATION_ERROR |
| 401 | UNAUTHORIZED |
| 500 | INTERNAL_SERVER_ERROR |
Domain types
DeviceResponse
type DeviceResponse = {
id: string;
platform: "android" | "ios";
appKind: "vendor";
lastSeenAt: string; // ISO
};Vendor devices
Base path: /vendor/devices. The vendor app registers its FCM token after login and is expected to call DELETE on logout to stop unrelated pushes leaking to a shared device.
POST /vendor/devices — Register vendor device
Idempotent upsert keyed by (userId, token) — re-registering the same token refreshes lastSeenAt instead of creating a duplicate row. The row is stamped with appKind="vendor".
Body
{
"platform": "android", // "android" | "ios"
"token": "fcm_abcdef..." // 10..4096 chars, FCM registration token
}| Field | Type | Constraints |
|---|---|---|
platform | "android" | "ios" | Required |
token | string | Trimmed; 10..4096 chars |
Response 201 — DeviceResponse.
{
"data": {
"id": "01J9...",
"platform": "android",
"appKind": "vendor",
"lastSeenAt": "2026-05-13T09:14:22.000Z"
},
"message": "Success",
"statusCode": 201
}Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_ERROR | Body fails zod (bad platform, token too short) |
| 401 | UNAUTHORIZED | No session |
DELETE /vendor/devices — Unregister vendor device
Idempotent: deleting an unknown token is a 200 with { ok: true }. The token is matched only against rows owned by the calling user — cross-user tokens silently return the same ok body (no leak of token existence).
Body
{ "token": "fcm_abcdef..." }| Field | Type | Constraints |
|---|---|---|
token | string | Trimmed; 1..4096 chars |
Response 200
{ "data": { "ok": true }, "message": "Success", "statusCode": 200 }Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_ERROR | Body fails zod |
| 401 | UNAUTHORIZED | No session |
What the vendor app actually receives
The vendor app does not subscribe to channels through this surface — push and email recipients are resolved server-side from domain events. The events that produce a vendor-targeted push or email today include:
| Event | Channel | Typical content |
|---|---|---|
order.placed | email + push | "New order on <product>" |
order.vendor.cancelled (when admin-initiated) | Order was cancelled by admin | |
vendor.application.approved / .rejected | Onboarding decision (sent to the applicant user) | |
| Payout lifecycle events | Manual-admin payout flow updates |
The full event catalogue, audience-resolution rules, and broadcast (admin push/email) endpoints live in notifications.md — they are not callable from the vendor surface.
Related modules
auth—userIdon the device row references the Better-Auth user.notifications-push-fcm— provider module that actually dispatches to FCM using these device rows.order— emits theorder.*events that produce vendor-targeted pushes. Seeorder.md.vendor— emitsvendor.application.*events that produce onboarding emails. Seevendor.md.
Invoice Module — Vendor
HTTP surface for a vendor to download and regenerate the GST tax invoice for their own sub-order.
Order Module — Vendor surface
Vendor-facing HTTP surface for sub-order fulfillment (assign shipping provider, mark fulfilled, mark delivered, cancel), returns processing (approve / reject / pickup / receive /…