Device Management¶
Complete guide to registering and managing FCM device tokens for push notifications in the Reserva platform.
Overview¶
The device management system enables push notification delivery by managing FCM (Firebase Cloud Messaging) tokens across user devices.
Frontend Integration Guide
For complete Firebase setup, service worker configuration, and frontend implementation examples, see Push Notification Integration.
Key Features:
- Device Registration - Register FCM tokens for push notification delivery
- Multi-Device Support - Same user can have multiple registered devices
- Automatic Deduplication - Upsert handling prevents duplicate token entries
- Cross-Platform - Supports web, Android, and iOS devices
- Token Refresh - Update tokens when Firebase refreshes them
- Device Lifecycle - Activate, deactivate, or remove devices
Key Concepts:
- FCM Token = Unique device identifier from Firebase for push delivery
- Device = A registered FCM token with metadata (platform, name, etc.)
- User Type = Either
staff(admin portal) orcustomer(customer portal) - Active Device = Device enabled to receive push notifications
Subscription Plan Limits¶
No Device Limits
Device registration has no subscription plan limits. All plans (FREE, PRO, ENTERPRISE) can register unlimited devices.
| Feature | FREE | PRO | ENTERPRISE |
|---|---|---|---|
| Devices per User | Unlimited | Unlimited | Unlimited |
| Push Notifications | Unlimited | Unlimited | Unlimited |
Notes:
- Push notifications are always unlimited on all plans
- Device registration is available for both staff and customer users
- No monthly quota on device registrations
Device Platforms¶
| Platform | Value | Description |
|---|---|---|
| Web | web |
Browser-based (Chrome, Firefox, Edge, Safari) |
| Android | android |
Android mobile app |
| iOS | ios |
iOS mobile app |
| Unknown | unknown |
Platform not specified |
Register Device (Staff)¶
Register a new FCM device token for push notifications. Used by staff users in the admin portal.
Endpoint¶
Authentication: Required (Staff JWT token)
Access: Any authenticated staff user
Request Body¶
{
"fcm_token": "dGhpc19pc19hX3NhbXBsZV9mY21fdG9rZW4...",
"platform": "web",
"device_name": "Chrome on Windows",
"app_version": "1.0.0",
"device_info": {
"browser": "Chrome",
"os": "Windows 10",
"screen": "1920x1080"
}
}
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fcm_token |
string | Yes | Firebase Cloud Messaging token (100-4096 chars) |
platform |
string | No | Device platform: web, android, ios (default: web) |
device_name |
string | No | Human-readable device name (max 200 chars) |
app_version |
string | No | Application version (max 20 chars) |
device_info |
object | No | Additional device metadata (browser, OS, etc.) |
Response¶
Response Fields:
| Field | Description |
|---|---|
id |
Device record ID in database |
is_new |
true if new device created, false if existing updated |
message |
Status message |
Behavior¶
The endpoint uses upsert logic based on the FCM token:
| Scenario | Behavior |
|---|---|
| Token is new | Creates new device record |
| Token exists for same user | Updates device metadata |
| Token exists for different user | Updates ownership to current user |
Important Notes:
- FCM token must be between 100-4096 characters
- Re-registering resets
consecutive_failurescounter to 0 - Device is automatically set to
is_active: trueon registration
Register Device (Customer)¶
Register a new FCM device token for customer users in the customer portal.
Endpoint¶
Authentication: Required (Customer JWT token)
Access: Authenticated customers only
Request Body¶
Same as Register Device (Staff).
Response¶
Same as Register Device (Staff).
List User's Devices (Staff)¶
Retrieve all registered devices for the current staff user.
Endpoint¶
Authentication: Required (Staff JWT token)
Query Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
include_inactive |
boolean | false |
Include inactive devices in response |
Response¶
{
"items": [
{
"id": "507f1f77bcf86cd799439011",
"platform": "web",
"device_name": "Chrome on Windows",
"app_version": "1.0.0",
"is_active": true,
"last_used_at": "2025-01-15T10:30:00Z",
"notification_count": 45,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-15T10:30:00Z"
},
{
"id": "507f1f77bcf86cd799439012",
"platform": "android",
"device_name": "Samsung Galaxy S21",
"app_version": "1.0.0",
"is_active": true,
"last_used_at": "2025-01-14T08:15:00Z",
"notification_count": 23,
"created_at": "2025-01-05T00:00:00Z",
"updated_at": "2025-01-14T08:15:00Z"
}
],
"total": 2
}
Response Fields:
| Field | Description |
|---|---|
id |
Device record ID |
platform |
Device platform (web, android, ios, unknown) |
device_name |
Human-readable device name |
app_version |
Application version |
is_active |
Whether device is active for notifications |
last_used_at |
Last notification sent to this device |
notification_count |
Total notifications sent to this device |
created_at |
When device was first registered |
updated_at |
Last update timestamp |
Note: FCM token is intentionally excluded from the response for security.
List User's Devices (Customer)¶
Retrieve all registered devices for the current customer user.
Endpoint¶
Authentication: Required (Customer JWT token)
Query Parameters¶
Same as List User's Devices (Staff).
Response¶
Same as List User's Devices (Staff).
Update Device (Staff)¶
Update device information such as refreshed FCM token, device name, or active status.
Endpoint¶
Authentication: Required (Staff JWT token)
Path Parameters¶
| Parameter | Type | Description |
|---|---|---|
device_id |
string | Device ID to update (MongoDB ObjectId) |
Request Body¶
{
"fcm_token": "new_refreshed_token...",
"device_name": "Chrome on macOS",
"app_version": "1.1.0",
"is_active": true
}
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fcm_token |
string | No | Updated FCM token (100-4096 chars) |
device_name |
string | No | Updated device name (max 200 chars) |
app_version |
string | No | Updated app version (max 20 chars) |
is_active |
boolean | No | Enable/disable device for notifications |
All fields are optional. Only provided fields are updated.
Response¶
{
"id": "507f1f77bcf86cd799439011",
"platform": "web",
"device_name": "Chrome on macOS",
"app_version": "1.1.0",
"is_active": true,
"last_used_at": "2025-01-15T10:30:00Z",
"notification_count": 45,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-15T11:00:00Z"
}
Token Refresh Handling¶
When updating the FCM token:
- System checks if new token exists on another device
- If found, the conflicting device record is deleted (token moved)
token_refreshed_atis recordedconsecutive_failuresis reset to 0
Important: Users can only update their own devices. Attempting to update another user's device returns 403 Forbidden.
Update Device (Customer)¶
Update device information for customer users.
Endpoint¶
Authentication: Required (Customer JWT token)
Path Parameters¶
Same as Update Device (Staff).
Request Body¶
Same as Update Device (Staff).
Response¶
Same as Update Device (Staff).
Unregister Device (Staff)¶
Remove a device (delete FCM token) to stop receiving push notifications.
Endpoint¶
Authentication: Required (Staff JWT token)
Path Parameters¶
| Parameter | Type | Description |
|---|---|---|
device_id |
string | Device ID to unregister (MongoDB ObjectId) |
Response¶
Important:
- Users can only delete their own devices
- This is a hard delete - the device record is permanently removed
- The device will no longer receive push notifications
- Attempting to delete another user's device returns
403 Forbidden
Unregister Device (Customer)¶
Remove a device for customer users.
Endpoint¶
Authentication: Required (Customer JWT token)
Path Parameters¶
Same as Unregister Device (Staff).
Response¶
Same as Unregister Device (Staff).
Device Lifecycle¶
Registration Flow¶
flowchart TD
A[User logs in] --> B[Request notification permission]
B --> C{Permission granted?}
C -->|No| D[Cannot register device]
C -->|Yes| E[Generate FCM token]
E --> F[Call /devices/register]
F --> G{Token exists?}
G -->|No| H[Create new device]
G -->|Yes| I[Update existing device]
H --> J[Device registered]
I --> J
J --> K[Ready for push notifications]
Token Refresh Flow¶
flowchart TD
A[Firebase refreshes token] --> B[Detect token change]
B --> C[Call PUT /devices/{id}]
C --> D{Token conflicts?}
D -->|No| E[Update token]
D -->|Yes| F[Delete conflicting device]
F --> E
E --> G[Reset failure counter]
G --> H[Device updated]
Deactivation Flow¶
Devices can become inactive in several ways:
| Trigger | Action |
|---|---|
| User manually disables | PUT /devices/{id} with is_active: false |
| User logs out | DELETE /devices/{id} |
| Consecutive failures | System marks device inactive after multiple failures |
| Token expiration | Firebase invalidates token, push fails |
Error Handling¶
Common Errors¶
| Error | Status | Cause | Solution |
|---|---|---|---|
Authentication required |
401 | Missing or invalid JWT | Include valid Bearer token |
User has no tenant access |
403 | Staff user without tenant | Assign user to a tenant |
Device not found |
404 | Invalid device_id | Use valid device ID from list endpoint |
Cannot delete another user's device |
403 | Device ownership mismatch | Users can only manage their own devices |
FCM token cannot be empty |
422 | Empty or whitespace-only token | Provide valid FCM token |
Invalid ObjectId format |
422 | Malformed device_id | Use valid MongoDB ObjectId format |
Error Response Format¶
Best Practices¶
For Device Registration¶
DO:
- Register device immediately after user grants notification permission
- Store device ID locally for future updates/unregistration
- Include meaningful
device_namefor user identification - Track
app_versionfor debugging and analytics - Re-register device on app launch to refresh token
DON'T:
- Register devices without notification permission
- Store FCM tokens in insecure locations
- Skip token refresh handling
- Assume tokens never change
For Token Management¶
DO:
- Listen for Firebase token refresh events
- Update token via
PUT /devices/{id}when it changes - Handle token conflicts gracefully (let backend resolve)
- Clear local token data on logout
DON'T:
- Ignore token refresh callbacks from Firebase
- Create duplicate device records for token changes
- Keep stale tokens registered
For User Logout¶
DO:
- Call
DELETE /devices/{id}before clearing auth tokens - Clear local FCM token storage
- Unregister service worker if appropriate
DON'T:
- Leave devices registered after logout
- Skip device cleanup on account deletion
Frontend Integration¶
JavaScript Example¶
class DeviceManager {
constructor(apiBaseUrl, getAuthToken) {
this.apiBaseUrl = apiBaseUrl;
this.getAuthToken = getAuthToken;
this.deviceId = localStorage.getItem('device_id');
}
async register(fcmToken, isCustomer = false) {
const endpoint = isCustomer
? '/api/v1/devices/register/customer'
: '/api/v1/devices/register';
const response = await fetch(`${this.apiBaseUrl}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await this.getAuthToken()}`
},
body: JSON.stringify({
fcm_token: fcmToken,
platform: 'web',
device_name: this.getDeviceName(),
app_version: '1.0.0'
})
});
if (!response.ok) {
throw new Error('Device registration failed');
}
const data = await response.json();
this.deviceId = data.id;
localStorage.setItem('device_id', data.id);
localStorage.setItem('fcm_token', fcmToken);
return data;
}
async updateToken(newToken, isCustomer = false) {
if (!this.deviceId) {
return this.register(newToken, isCustomer);
}
const endpoint = isCustomer
? `/api/v1/devices/customer/${this.deviceId}`
: `/api/v1/devices/${this.deviceId}`;
const response = await fetch(`${this.apiBaseUrl}${endpoint}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await this.getAuthToken()}`
},
body: JSON.stringify({ fcm_token: newToken })
});
if (!response.ok) {
// Device not found, re-register
if (response.status === 404) {
return this.register(newToken, isCustomer);
}
throw new Error('Token update failed');
}
localStorage.setItem('fcm_token', newToken);
return response.json();
}
async unregister(isCustomer = false) {
if (!this.deviceId) return;
const endpoint = isCustomer
? `/api/v1/devices/customer/${this.deviceId}`
: `/api/v1/devices/${this.deviceId}`;
await fetch(`${this.apiBaseUrl}${endpoint}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${await this.getAuthToken()}`
}
});
localStorage.removeItem('device_id');
localStorage.removeItem('fcm_token');
this.deviceId = null;
}
async listDevices(isCustomer = false, includeInactive = false) {
const endpoint = isCustomer
? '/api/v1/devices/customer'
: '/api/v1/devices';
const response = await fetch(
`${this.apiBaseUrl}${endpoint}?include_inactive=${includeInactive}`,
{
headers: {
'Authorization': `Bearer ${await this.getAuthToken()}`
}
}
);
return response.json();
}
getDeviceName() {
const ua = navigator.userAgent;
const browser = this.getBrowser(ua);
const os = this.getOS(ua);
return `${browser} on ${os}`;
}
getBrowser(ua) {
if (ua.includes('Chrome')) return 'Chrome';
if (ua.includes('Firefox')) return 'Firefox';
if (ua.includes('Safari')) return 'Safari';
if (ua.includes('Edge')) return 'Edge';
return 'Browser';
}
getOS(ua) {
if (ua.includes('Windows')) return 'Windows';
if (ua.includes('Mac')) return 'macOS';
if (ua.includes('Linux')) return 'Linux';
if (ua.includes('Android')) return 'Android';
if (ua.includes('iOS')) return 'iOS';
return 'Unknown';
}
}
React Hook Example¶
import { useState, useCallback, useEffect } from 'react';
import { useAuth } from './useAuth';
export function useDevices() {
const { token, isCustomer } = useAuth();
const [devices, setDevices] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchDevices = useCallback(async (includeInactive = false) => {
if (!token) return;
setLoading(true);
setError(null);
try {
const endpoint = isCustomer
? '/api/v1/devices/customer'
: '/api/v1/devices';
const response = await fetch(
`${API_URL}${endpoint}?include_inactive=${includeInactive}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
const data = await response.json();
setDevices(data.items);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, [token, isCustomer]);
const removeDevice = useCallback(async (deviceId) => {
const endpoint = isCustomer
? `/api/v1/devices/customer/${deviceId}`
: `/api/v1/devices/${deviceId}`;
await fetch(`${API_URL}${endpoint}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
setDevices(prev => prev.filter(d => d.id !== deviceId));
}, [token, isCustomer]);
useEffect(() => {
fetchDevices();
}, [fetchDevices]);
return {
devices,
loading,
error,
fetchDevices,
removeDevice
};
}
API Reference Summary¶
| Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/devices/register |
POST | Staff JWT | Register FCM token (staff) |
/devices/register/customer |
POST | Customer JWT | Register FCM token (customer) |
/devices |
GET | Staff JWT | List staff's devices |
/devices/customer |
GET | Customer JWT | List customer's devices |
/devices/{id} |
PUT | Staff JWT | Update device (staff) |
/devices/customer/{id} |
PUT | Customer JWT | Update device (customer) |
/devices/{id} |
DELETE | Staff JWT | Unregister device (staff) |
/devices/customer/{id} |
DELETE | Customer JWT | Unregister device (customer) |
Related Documentation¶
| Document | Description |
|---|---|
| Push Notification Integration | Frontend FCM setup and integration guide |
| Notification Management | Sending and scheduling notifications |
| Notification Settings | Customize notification templates and preferences |
| Customer Authentication | Customer login and JWT token handling |
| Staff Authentication | Staff login and JWT token handling |
Next Steps:
- Set up Firebase in your frontend app (see Push Notification Integration)
- Request notification permission from user
- Generate FCM token using Firebase SDK
- Register device:
POST /devices/register - Handle token refresh events
- Unregister device on logout:
DELETE /devices/{id}