FCM Notifications API Reference
This page documents the FCM notification payload formats and event specifications for the Firebase Cloud Messaging system in the Unified Order Service.
Notification Payload Schema
All FCM notifications use data-only payloads with no title or body. The mobile application is responsible for processing the data and presenting appropriate notifications to users.
Base Payload Structure
All FCM notifications use a default.data wrapper structure.
Important: Due to Firebase Cloud Messaging's requirement that all data payload values must be strings, the nested structure is sent as a stringified JSON in the default key. Mobile applications must parse this string to access the nested data structure.
Actual FCM Payload (as sent):
{
"default": "{\"data\":{\"message_id\":\"string\",\"order_id\":\"string\",\"parent_order_id\":\"string\",\"visible_id\":\"string\",\"event_type\":\"string\",\"delivery_date\":\"string\",\"start_time\":\"string\",\"end_time\":\"string\",\"place_id\":\"string\"}}"
}
Parsed Structure (for mobile app processing):
{
"default": {
"data": {
"message_id": "string",
"order_id": "string",
"parent_order_id": "string",
"visible_id": "string",
"event_type": "string",
"delivery_date": "string",
"start_time": "string",
"end_time": "string",
"place_id": "string"
}
}
}
Field Specifications
| Field | Type | Required | Format | Description |
|---|---|---|---|---|
message_id | string | Yes | msg-{timestamp}-{random} | Unique message identifier |
order_id | string | Yes | UUID | Order identifier |
parent_order_id | string | Yes | UUID | Parent order identifier (usually same as order_id) |
visible_id | string | Yes | String/Number | Visible order identifier (order number, reference ID, or ID) |
event_type | string | Yes | Event type enum | Type of event that triggered the notification |
delivery_date | string | Yes | YYYY-MM-DD | Delivery date in ISO date format |
start_time | string | Yes | HH:MM | Timeslot start time in 24-hour format |
end_time | string | Yes | HH:MM | Timeslot end time in 24-hour format |
place_id | string | Yes | UUID | Place identifier |
Event Types
ORDER_CREATED
Event Type: event_order_created
Triggered When: A new order is created in the system
Actual FCM Payload Schema:
{
"default": "{\"data\":{\"message_id\":\"msg-1725707721000-abc123def\",\"order_id\":\"742ba33c-0c02-43e7-80b0-eb795c14dc6f\",\"parent_order_id\":\"742ba33c-0c02-43e7-80b0-eb795c14dc6f\",\"visible_id\":\"ORDER-20250707-1021\",\"event_type\":\"event_order_created\",\"delivery_date\":\"2025-07-07\",\"start_time\":\"14:00\",\"end_time\":\"16:00\",\"place_id\":\"1ee68c4e-7008-4227-a92d-e1b13ac64f30\"}}"
}
Parsed Structure (after JSON.parse(data.default).data):
{
"message_id": "msg-1725707721000-abc123def",
"order_id": "742ba33c-0c02-43e7-80b0-eb795c14dc6f",
"parent_order_id": "742ba33c-0c02-43e7-80b0-eb795c14dc6f",
"visible_id": "ORDER-20250707-1021",
"event_type": "event_order_created",
"delivery_date": "2025-07-07",
"start_time": "14:00",
"end_time": "16:00",
"place_id": "1ee68c4e-7008-4227-a92d-e1b13ac64f30"
}
Additional Fields: None
ORDER_STATUS_CHANGED
Event Type: event_order_status_changed
Triggered When: Order status changes (any status transition)
Payload Schema:
{
"message_id": "msg-1725707721000-xyz789ghi",
"order_id": "742ba33c-0c02-43e7-80b0-eb795c14dc6f",
"parent_order_id": "742ba33c-0c02-43e7-80b0-eb795c14dc6f",
"visible_id": "ORDER-20250707-1021",
"event_type": "event_order_status_changed",
"delivery_date": "2025-07-07",
"start_time": "14:00",
"end_time": "16:00",
"place_id": "1ee68c4e-7008-4227-a92d-e1b13ac64f30",
"previous_status": "pending",
"new_status": "processing",
"order_number": "ORDER-20250707-1021"
}
Additional Fields:
| Field | Type | Required | Description |
|---|---|---|---|
previous_status | string | Yes | Previous order status |
new_status | string | Yes | New order status |
order_number | string | Yes | Order number for display |
Valid Status Values:
pendingprocessingpickingpickedretrievingshippedcollectedcompletedcancelledfailed
TIMESLOT_STARTED
Event Type: event_timeslot_started
Triggered When: Delivery timeslot begins (typically 2:00 PM)
Payload Schema:
{
"message_id": "msg-1725714000000-def456ghi",
"order_id": "742ba33c-0c02-43e7-80b0-eb795c14dc6f",
"parent_order_id": "742ba33c-0c02-43e7-80b0-eb795c14dc6f",
"visible_id": "ORDER-20250707-1021",
"event_type": "event_timeslot_started",
"delivery_date": "2025-07-07",
"start_time": "14:00",
"end_time": "16:00",
"place_id": "1ee68c4e-7008-4227-a92d-e1b13ac64f30"
}
Additional Fields: None
UNPICKED_ORDERS_REMINDER
Event Type: event_unpicked_orders_reminder
Triggered When: Orders remain unpicked after timeslot reminders (2:15, 2:30, 2:45 PM)
Payload Schema:
{
"message_id": "msg-1725714900000-ghi789jkl",
"order_id": "742ba33c-0c02-43e7-80b0-eb795c14dc6f",
"parent_order_id": "742ba33c-0c02-43e7-80b0-eb795c14dc6f",
"visible_id": "ORDER-20250707-1021",
"event_type": "event_unpicked_orders_reminder",
"delivery_date": "2025-07-07",
"start_time": "14:00",
"end_time": "16:00",
"place_id": "1ee68c4e-7008-4227-a92d-e1b13ac64f30"
}
Additional Fields: None
Reminder Schedule:
- 2:15 PM: First reminder
- 2:30 PM: Second reminder
- 2:45 PM: Final reminder
ORDER_IDS_LISTED
Event Type: event_order_ids_listed
Triggered When: Multiple order IDs need to be communicated
Payload Schema:
{
"message_id": "msg-1725714900000-jkl123mno",
"order_id": "742ba33c-0c02-43e7-80b0-eb795c14dc6f",
"parent_order_id": "742ba33c-0c02-43e7-80b0-eb795c14dc6f",
"visible_id": "ORDER-20250707-1021",
"event_type": "event_order_ids_listed",
"delivery_date": "2025-07-07",
"start_time": "14:00",
"end_time": "16:00",
"place_id": "1ee68c4e-7008-4227-a92d-e1b13ac64f30"
}
Additional Fields: None
Data Extraction Rules
Timeslot Extraction Priority
The system extracts timeslot information using the following priority order:
- Primary Source:
order.shipping_info.slot.start_time/end_time - Salesforce Format:
order.metadata.delivery_slot_start/delivery_slot_end - Drone API Format:
order.shipping_info.pickup_details.time_slot - Fallback:
00:00/23:59if no timeslot data available
Date Extraction Priority
The system extracts delivery dates using the following priority order:
- Primary Source:
order.shipping_info.slot.date - Salesforce Format:
order.metadata.delivery_slot_start - Drone API Format:
order.shipping_info.slot.delivery_date - Order Date:
order.order_date - Fallback: Current date if no date available
Order ID Extraction
The system extracts order IDs using the following priority order:
- Primary:
order.id - Fallback:
order.order_number - Default: Empty string if neither available
Message ID Format
Message IDs follow the pattern: msg-{timestamp}-{random}
- timestamp: JavaScript timestamp (Date.now())
- random: 9-character random string (base36)
Example: msg-1725707721000-abc123def
Error Handling
Missing Data Fallbacks
| Field | Fallback Value | Condition |
|---|---|---|
delivery_date | Current date (YYYY-MM-DD) | No date extractable |
start_time | 00:00 | No start time extractable |
end_time | 23:59 | No end time extractable |
order_id | Empty string | No order ID available |
parent_order_id | Same as order_id | No parent ID available |
Invalid Data Handling
- Invalid timestamps: Converted to ISO format or fallback to current date
- Invalid time formats: Converted to HH:MM format or fallback values
- Missing required fields: Populated with fallback values, notification still sent
Notification Delivery
Device Token Requirements
- FCM notifications require valid device tokens registered via the subscriptions API
- Tokens must be associated with the specific
place_idin the notification - Invalid or expired tokens are automatically filtered out
Delivery Guarantees
- Best Effort: FCM provides best-effort delivery, not guaranteed
- Retry Logic: System includes automatic retry for failed deliveries
- Failure Handling: Failed notifications are logged but don't block order processing
Delivery Timing
- Order Creation: Immediate notification after order creation
- Status Changes: Immediate notification after status update
- Timeslot Started: Triggered by scheduled job every 15 minutes
- Reminders: Triggered by scheduled job every 15 minutes
Integration Examples
Mobile App Message Handler
// React Native FCM message handler
import messaging from '@react-native-firebase/messaging';
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
const { data } = remoteMessage;
// Extract the payload from the stringified default structure
// FCM sends the nested structure as a JSON string in the 'default' key
let payload;
if (data.default) {
try {
// Parse the stringified JSON to get the nested structure
const parsedDefault = JSON.parse(data.default);
payload = parsedDefault.data;
} catch (error) {
console.error('Failed to parse FCM default payload:', error);
return;
}
} else {
// Fallback for legacy flat structure
payload = data;
}
// Validate required fields
if (!payload.message_id || !payload.event_type || !payload.place_id) {
console.error('Invalid FCM payload:', payload);
return;
}
// Process based on event type
switch (payload.event_type) {
case 'event_order_created':
await processOrderCreated(payload);
break;
case 'event_order_status_changed':
await processStatusChange(payload);
break;
case 'event_timeslot_started':
await processTimeslotStarted(payload);
break;
case 'event_unpicked_orders_reminder':
await processUnpickedReminder(payload);
break;
default:
console.warn('Unknown event type:', payload.event_type);
}
});
Payload Validation
function validateFCMPayload(data) {
// Extract the payload from the stringified default structure
let payload;
if (data.default) {
try {
// Parse the stringified JSON to get the nested structure
const parsedDefault = JSON.parse(data.default);
payload = parsedDefault.data;
} catch (error) {
throw new Error('Failed to parse FCM default payload: ' + error.message);
}
} else {
// Fallback for legacy flat structure
payload = data;
}
const requiredFields = [
'message_id', 'order_id', 'parent_order_id',
'event_type', 'delivery_date', 'start_time',
'end_time', 'place_id'
];
const missingFields = requiredFields.filter(field => !payload[field]);
if (missingFields.length > 0) {
throw new Error(`Missing required fields: ${missingFields.join(', ')}`);
}
// Validate date format
if (!/^\d{4}-\d{2}-\d{2}$/.test(payload.delivery_date)) {
throw new Error('Invalid delivery_date format, expected YYYY-MM-DD');
}
// Validate time format
if (!/^\d{2}:\d{2}$/.test(payload.start_time) || !/^\d{2}:\d{2}$/.test(payload.end_time)) {
throw new Error('Invalid time format, expected HH:MM');
}
return true;
}
Processing Status Change Events
async function processStatusChange(data) {
// Validate status change specific fields
if (!data.previous_status || !data.new_status) {
console.error('Missing status change fields:', data);
return;
}
// Update local order state
await updateOrderStatus(data.order_id, data.new_status);
// Show notification to user
const notification = {
title: 'Order Status Updated',
body: `Order #${data.order_number} is now ${data.new_status}`,
data: {
orderId: data.order_id,
previousStatus: data.previous_status,
newStatus: data.new_status
}
};
await showNotification(notification);
}
Testing and Debugging
Test Payload Generation
// Generate test payload for order creation (actual FCM format)
function generateTestOrderCreatedPayload() {
const data = {
message_id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
order_id: '742ba33c-0c02-43e7-80b0-eb795c14dc6f',
parent_order_id: '742ba33c-0c02-43e7-80b0-eb795c14dc6f',
event_type: 'event_order_created',
delivery_date: '2025-07-07',
start_time: '14:00',
end_time: '16:00',
place_id: '1ee68c4e-7008-4227-a92d-e1b13ac64f30'
};
// Return actual FCM format with stringified nested structure
return {
default: JSON.stringify({ data })
};
}
// Helper function to parse received FCM payload
function parseReceivedFCMPayload(fcmData) {
if (fcmData.default) {
try {
const parsedDefault = JSON.parse(fcmData.default);
return parsedDefault.data;
} catch (error) {
console.error('Failed to parse FCM payload:', error);
return null;
}
}
// Fallback for legacy format
return fcmData;
}
Payload Logging
// Log FCM payload for debugging
function logFCMPayload(data) {
console.log('FCM Notification Received:', {
messageId: data.message_id,
eventType: data.event_type,
orderId: data.order_id,
placeId: data.place_id,
deliveryInfo: `${data.delivery_date} ${data.start_time}-${data.end_time}`,
additionalFields: Object.keys(data).filter(key =>
!['message_id', 'event_type', 'order_id', 'place_id',
'delivery_date', 'start_time', 'end_time'].includes(key)
).reduce((obj, key) => {
obj[key] = data[key];
return obj;
}, {})
});
}
The FCM notification system provides consistent, structured data payloads that enable mobile applications to present timely, relevant information to users about their orders and delivery schedules.