Skip to main content

Overview

MechZie uses Firebase Cloud Messaging (FCM) for push notifications with Fast2SMS as an SMS fallback. All notifications are also stored in-app for retrieval via the API.

Setting Up Push Notifications

Register FCM Token

On app startup and whenever the FCM token refreshes, register it with the API:
final fcmToken = await FirebaseMessaging.instance.getToken();

await http.put(
  Uri.parse('$baseUrl/api/v1/notifications/fcm-token'),
  headers: {
    'Authorization': 'Bearer $accessToken',
    'Content-Type': 'application/json',
  },
  body: jsonEncode({
    'token': fcmToken,
    'device_info': 'Flutter Android 14',  // optional
  }),
);
curl -X PUT https://mechzie-api-production.run.app/api/v1/notifications/fcm-token \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "fcm-device-token-string",
    "device_info": "Flutter Android 14"
  }'
Call this on every app launch. The server tracks last_used_at and automatically prunes stale tokens. If you don’t re-register, the token may be cleaned up and the user will stop receiving push notifications.

Listen for Token Refresh

FirebaseMessaging.instance.onTokenRefresh.listen((newToken) async {
  await registerFcmToken(newToken);
});

Listing Notifications

Fetch in-app notifications (paginated):
GET /api/v1/notifications?page=1&limit=20
Authorization: Bearer ACCESS_TOKEN
Response:
{
  "data": [
    {
      "id": "uuid",
      "user_id": "uuid",
      "title": "Mechanic on the way",
      "body": "Ravi is heading to your location. ETA: 8 minutes.",
      "data": {
        "jobId": "uuid",
        "type": "job_status_change",
        "status": "en_route"
      },
      "is_read": false,
      "created_at": "2026-05-21T10:30:00.000Z"
    }
  ],
  "page": 1,
  "limit": 20,
  "total": 15
}

Marking as Read

Mark a single notification

PATCH /api/v1/notifications/{id}/read
Authorization: Bearer ACCESS_TOKEN

Mark all as read

PATCH /api/v1/notifications/read-all
Authorization: Bearer ACCESS_TOKEN

Notification Triggers

These events generate notifications for the relevant user:

Customer Notifications

TriggerTitle ExampleWhen
Mechanic accepted job”Mechanic assigned”Job status → accepted
Mechanic en route”Mechanic on the way”Job status → en_route
Mechanic arrived”Mechanic has arrived”Job status → arrived
Job completed”Service completed”Job status → completed
Payment failed”Payment failed”Razorpay payment.failed webhook
Refund processed”Refund issued”Admin initiates refund (FCM + email)
No mechanic found”No mechanics available”All dispatch tiers exhausted

Mechanic Notifications

TriggerTitle ExampleWhen
New job offer”New job nearby”Dispatch sends job:offer
Job cancelled by customer”Job cancelled”Customer cancels
Verification approved”Profile verified”Admin approves mechanic
Verification rejected”Verification update”Admin rejects mechanic
Document review”Document reviewed”Admin approves/rejects document

Notification Data Payload

The data field contains structured data for deep-linking. Use it to navigate the user to the relevant screen:
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
  final data = message.data;

  switch (data['type']) {
    case 'job_status_change':
      navigateToJob(data['jobId']);
      break;
    case 'job_offer':
      navigateToJobOffer(data['jobId']);
      break;
    case 'payment_failed':
      navigateToPayment(data['jobId']);
      break;
    case 'mechanic_verified':
      navigateToProfile();
      break;
    case 'refund_processed':
      navigateToPaymentHistory(data['jobId']);
      break;
  }
});

Delivery Channels

ChannelPriorityNotes
FCM PushPrimaryRequires registered token
Fast2SMSFallbackUsed when FCM delivery fails
Email (Resend)Specific eventsAdmin invites, refund confirmations
Socket.ioReal-timejob:status, payment:failed, etc. (see Real-time Events)
In-AppPersistentAlways stored in notifications table
Push notifications and Socket.io events often carry the same information. Socket.io is for real-time UI updates while the user is active. Push notifications reach users when the app is in the background. In-app notifications serve as a persistent history.

Unread Badge Count

The API doesn’t have a dedicated unread count endpoint, but you can calculate it from the notification list:
final response = await api.get('/notifications?page=1&limit=1');
// Use the total count of unread notifications
// Or filter client-side: data.where((n) => !n.is_read).length
For a proper unread count, fetch the first page and count items where is_read == false. Consider caching this and updating it when you mark notifications as read.