How to Migrate FCM to HTTP v1 API in Laravel: A Step-by-Step Guide

How to Migrate FCM to HTTP v1 API in Laravel: A Step-by-Step Guide

Posted By

kamlesh paul

on

Dec 11, 2024

In my recent projects, I relied on the Larafirebase package to send push notifications using the legacy FCM APIs. It worked well—until it didn’t. I kept using the same snippet to send notifications, but as the legacy FCM APIs were deprecated, I had to migrate FCM to HTTP v1 API in Laravel.

return Larafirebase::withTitle('Test Title')
    ->withBody('Test body')
    ->sendMessage($this->deviceTokens);

At first, it seemed fine, but this turned out to be a mistake. Why? If the package changes or a part of its API gets deprecated, I’d have to change this code everywhere in my project. That’s not ideal. In this migration, I set out to fix that problem.

Table of contents

The Solution: Centralizing the Push Notification Logic

To future-proof my setup, I created a PushNotificationService class. Now, if I need to switch to another service, I’ll only need to change one place in my code. This way, adding any new service becomes much easier. It’s a great way to centralize your notification logic.

The Problem: Deprecated API

  • The issue started when I began getting this error:
{
  "multicast_id": 8248538656958747000,
  "success": 0,
  "failure": 1,
  "canonical_ids": 0,
  "results": [
    {
      "error": "DeprecatedApi"
    }
  ]
}

I discovered that the package I was using relied on the old FCM API, which was officially deprecated on June 20, 2023. The full shutdown started on July 22, 2024. The hunt for a solution began.

Migrating to the HTTP v1 API

After researching the replacement, I found Google’s migration guide. This guide explained how the new HTTP v1 API offers better security, more efficient message customization, and extended support across platforms.

However, there was no direct PHP example for generating access tokens, so I had to dig deeper and found that Google’s official package, google/apiclient, could do the job.

The New Push Notification Setup

  • Here’s how I implemented the new notification service using the HTTP v1 API in Laravel:
PushNotificationService.php
<?php
 
namespace App\Services;
 
use Google\Client;
use Illuminate\Support\Facades\Http;
 
 
class PushNotificationService
{
    private const BASE_URL = 'https://fcm.googleapis.com/v1/projects/<projectName>/messages:send';
 
    public static function send(
        array  $deviceTokens,
        string $title,
        string $body, ?string $clickAction = null,
        ?array $data = [],
        bool   $highPriority = false,
        ?int    $badgeCount = null
    ): array
    {
        $results = [];
 
        if(count($deviceTokens) === 0) {
            return $results;
        }
 
        foreach ($deviceTokens as $token) {
            $result = (new self)->sendFirebaseNotification($token, $title, $body, $clickAction, $data, $highPriority, $badgeCount);
            $results[] = [
                'token' => $token,
                'status' => $result['status'],
                'message' => $result['message'],
            ];
        }
 
        return $results;
    }
 
    private function sendFirebaseNotification(
        string $token,
        string  $title,
        string  $body,
        ?string $clickAction = null,
        ?array  $data = [],
        bool    $highPriority = false,
        ?int    $badgeCount = null
    ): array
    {
        $payload = [
            'message' => [
                'token' => $token,
                'notification' => [
                    'title' => $title,
                    'body' => $body,
                ],
                // Android specific settings
                'android' => [
                    'priority' => $highPriority ? 'high' : 'normal',
                ],
                // iOS specific settings
                'apns' => [
                    'headers' => [
                        'apns-priority' => $highPriority ? '10' : '5',
                    ],
                    'payload' => [
                        'aps' => [
                            'alert' => [
                                'title' => $title,
                                'body' => $body,
                            ],
                            'sound' => 'default',
                            'content-available' => 1,
                        ],
                    ],
                ],
            ],
        ];
 
        if ($clickAction) {
            $payload['message']['notification']['click_action'] = $clickAction;
        }
 
        if (count($data)) {
            $payload['message']['data'] = $this->arrayToString($data); // https://firebase.google.com/docs/cloud-messaging/migrate-v1#example-nested-json-data
        }
 
        if (!is_null($badgeCount)) {
            $payload['message']['apns']['payload']['aps']['badge'] = $badgeCount;
        }
 
        $token = $this->getAccessToken();
 
        $response = Http::withToken($token)
            ->post(self::BASE_URL, $payload);
 
        if ($response->successful()) {
            return [
                'status' => 'success',
                'message' => 'Notification sent successfully',
            ];
        } else {
            return [
                'status' => 'error',
                'message' => $response->body() ?? 'Error sending notification',
            ];
        }
    }
 
    private function getAccessToken():string
    {
        // Cache the access token for 55 minutes (3300 seconds)
        return \Cache::remember('firebase_access_token', 55 * 60, function () {
            $client = new Client();
            $client->setAuthConfig(base_path('service-account-file.json'));
            $client->addScope('https://www.googleapis.com/auth/firebase.messaging');
            $client->useApplicationDefaultCredentials();
            $token = $client->fetchAccessTokenWithAssertion();
            return $token['access_token'];
        });
    }
 
    private function arrayToString(?array $data): array
    {
        $formattedData = [];
        if ($data) {
            foreach ($data as $key => $value) {
                if (is_array($value) || is_object($value)) {
                    $value = json_encode($value);
                }
                $formattedData[$key] = (string) $value;
            }
        }
 
        return $formattedData;
    }
 
}

<projectName> from BASE_URL and base_path('service-account-file.json') or you can put this file into storage_path() as well

This is the new, more efficient way to send notifications using the HTTP v1 API in Laravel. Feel free to copy and use this code in your projects. For more details, check out Google’s official migration guide.

Usage Example

Once you have set up the PushNotificationService as described, integrating it into your Laravel application is straightforward. Below is an example demonstrating how to send a push notification using the service.

Sending a Notification

  • Let’s say you want to send a push notification to a list of device tokens. Here’s how you can use the PushNotificationService:
NotificationController.php
<?php
 
namespace App\Http\Controllers;
 
use App\Services\PushNotificationService;
use Illuminate\Http\Request;
 
class NotificationController extends Controller
{
    public function sendNotification(Request $request)
    {
        // Sample data
        $deviceTokens = [
            'device_token_1',
            'device_token_2',
        ];
 
        $title = 'Test Notification';
        $body = 'This is a test notification sent using HTTP v1 API.';
        $clickAction = 'https://yourapp.com/some-page';
        $data = ['key' => 'value'];
        $highPriority = true;
        $badgeCount = 5;
 
        // Send notification
        $results = PushNotificationService::send(
            $deviceTokens,
            $title,
            $body,
            $clickAction,
            $data,
            $highPriority,
            $badgeCount
        );
 
        return response()->json($results);
    }
}

Explanation

  • deviceTokens: An array of device tokens to which the notification will be sent.
  • title: The title of the notification.
  • body: The body content of the notification.
  • clickAction: (Optional) A URL that the user will be directed to when they click on the notification.
  • data: (Optional) Additional data to include in the notification payload.
  • highPriority: (Optional) Whether the notification should be sent with high priority.
  • badgeCount: (Optional) The badge count for iOS notifications.

Conclusion

Migrating from the legacy FCM API to the HTTP v1 API was a necessary step to ensure long-term compatibility and security in my Laravel projects. By centralizing the push notification logic into a PushNotificationService, I’ve future-proofed my code, making it easier to adapt to changes or add new services down the line. While the process required some research and adjustments, the enhanced security, better message customization, and extended support across platforms made it worth the effort. If you’re still using the old API, now is the time to make the switch.

Share this article

35 views