Unified Order Service
Home
Getting Started
  • Core API
  • Drone API
Resources
Home
Getting Started
  • Core API
  • Drone API
Resources
    • Core API Reference
    • Orders API Reference
    • Order Items API
    • Channels API Reference
    • Places API Reference
    • Storage Locations API Reference
    • Webhooks API Reference
      • Order Failed Webhook - Operation Type Specification
    • API Keys Reference

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

FieldTypeRequiredFormatDescription
message_idstringYesmsg-{timestamp}-{random}Unique message identifier
order_idstringYesUUIDOrder identifier
parent_order_idstringYesUUIDParent order identifier (usually same as order_id)
visible_idstringYesString/NumberVisible order identifier (order number, reference ID, or ID)
event_typestringYesEvent type enumType of event that triggered the notification
delivery_datestringYesYYYY-MM-DDDelivery date in ISO date format
start_timestringYesHH:MMTimeslot start time in 24-hour format
end_timestringYesHH:MMTimeslot end time in 24-hour format
place_idstringYesUUIDPlace 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:

FieldTypeRequiredDescription
previous_statusstringYesPrevious order status
new_statusstringYesNew order status
order_numberstringYesOrder number for display

Valid Status Values:

  • pending
  • processing
  • picking
  • picked
  • retrieving
  • shipped
  • collected
  • completed
  • cancelled
  • failed

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:

  1. Primary Source: order.shipping_info.slot.start_time / end_time
  2. Salesforce Format: order.metadata.delivery_slot_start / delivery_slot_end
  3. Drone API Format: order.shipping_info.pickup_details.time_slot
  4. Fallback: 00:00 / 23:59 if no timeslot data available

Date Extraction Priority

The system extracts delivery dates using the following priority order:

  1. Primary Source: order.shipping_info.slot.date
  2. Salesforce Format: order.metadata.delivery_slot_start
  3. Drone API Format: order.shipping_info.slot.delivery_date
  4. Order Date: order.order_date
  5. Fallback: Current date if no date available

Order ID Extraction

The system extracts order IDs using the following priority order:

  1. Primary: order.id
  2. Fallback: order.order_number
  3. 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

FieldFallback ValueCondition
delivery_dateCurrent date (YYYY-MM-DD)No date extractable
start_time00:00No start time extractable
end_time23:59No end time extractable
order_idEmpty stringNo order ID available
parent_order_idSame as order_idNo 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_id in 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.

Last Updated: 12/1/25, 11:31 AM