Integration of 1C-Bitrix with Firebase Cloud Messaging

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1177
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

1C-Bitrix Integration with Firebase Cloud Messaging

Firebase Cloud Messaging (FCM) is Google's infrastructure for push notifications on Android, iOS, and in the browser. Unlike OneSignal, FCM is a low-level transport with no built-in UI or segmentation. The integration with 1C-Bitrix is built entirely on custom code: the frontend SDK registers a device token, and the PHP backend sends notifications via the FCM HTTP v1 API.

FCM HTTP v1 API vs Legacy API

Google shut down the Legacy HTTP API (the server key) in June 2024. All new integrations use the HTTP v1 API with OAuth 2.0 Service Account authorization. If your project still has an old integration via https://fcm.googleapis.com/fcm/send — it is no longer functional.

HTTP v1 endpoint: POST https://fcm.googleapis.com/v1/projects/{project_id}/messages:send

Authorization — a Bearer token obtained from the Service Account JSON via the Google Auth Library.

Service Account and Authorization

In Firebase Console → Project Settings → Service Accounts → Generate new private key. Download the JSON file and place it outside the webroot:

/var/www/site/storage/firebase/service-account.json

Obtain the token via JWT:

use Google\Auth\Credentials\ServiceAccountCredentials;

class FcmAuthService
{
    private ServiceAccountCredentials $credentials;

    public function __construct(string $serviceAccountPath)
    {
        $this->credentials = new ServiceAccountCredentials(
            'https://www.googleapis.com/auth/firebase.messaging',
            json_decode(file_get_contents($serviceAccountPath), true)
        );
    }

    public function getAccessToken(): string
    {
        $token = $this->credentials->fetchAuthToken();
        return $token['access_token'];
    }
}

Install the dependency: composer require google/auth. Cache the token — it is valid for 1 hour. Regenerating it on every request is wasteful.

public function getCachedToken(): string
{
    $cacheKey = 'fcm_access_token';
    $cached   = \Bitrix\Main\Data\Cache::createInstance();

    if ($cached->initCache(3500, $cacheKey, '/fcm/')) {
        return $cached->getVars()['token'];
    }

    $token = $this->getAccessToken();
    $cached->startDataCache();
    $cached->endDataCache(['token' => $token]);

    return $token;
}

Registering the FCM Token on the Frontend

import { initializeApp } from 'firebase/app';
import { getMessaging, getToken, onMessage } from 'firebase/messaging';

const app = initializeApp({
    apiKey: "...",
    authDomain: "project.firebaseapp.com",
    projectId: "project-id",
    messagingSenderId: "123456789",
    appId: "1:123456789:web:abc"
});

const messaging = getMessaging(app);

// Request permission and register token
async function initPush() {
    try {
        const token = await getToken(messaging, {
            vapidKey: 'YOUR_VAPID_KEY'
        });

        if (token) {
            await fetch('/local/api/fcm/register', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-Bitrix-Csrf-Token': BX.bitrix_sessid()
                },
                body: JSON.stringify({ fcm_token: token, platform: 'web' })
            });
        }
    } catch (err) {
        console.warn('Push permission denied:', err);
    }
}

// Foreground notification handler (when the tab is open)
onMessage(messaging, (payload) => {
    new Notification(payload.notification.title, {
        body: payload.notification.body,
        icon: '/local/templates/main/images/push-icon.png'
    });
});

For background notifications, a Service Worker /firebase-messaging-sw.js is required in the site root:

importScripts('https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/10.7.0/firebase-messaging-compat.js');

firebase.initializeApp({ /* config */ });
const messaging = firebase.messaging();

messaging.onBackgroundMessage((payload) => {
    self.registration.showNotification(payload.notification.title, {
        body: payload.notification.body,
        data: payload.data,
    });
});

Storing FCM Tokens

class FcmTokenTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName(): string { return 'local_fcm_tokens'; }

    public static function getMap(): array
    {
        return [
            new \Bitrix\Main\ORM\Fields\IntegerField('ID',       ['primary' => true, 'autocomplete' => true]),
            new \Bitrix\Main\ORM\Fields\IntegerField('USER_ID'),
            new \Bitrix\Main\ORM\Fields\StringField('TOKEN',     ['required' => true]),
            new \Bitrix\Main\ORM\Fields\StringField('PLATFORM'), // web, android, ios
            new \Bitrix\Main\ORM\Fields\DatetimeField('CREATED_AT'),
            new \Bitrix\Main\ORM\Fields\DatetimeField('LAST_USED_AT'),
            new \Bitrix\Main\ORM\Fields\StringField('ACTIVE'),
        ];
    }
}

When a token is refreshed (FCM rotates tokens upon app reinstallation) — locate the old token and replace it rather than creating a duplicate record.

Sending Notifications

class FcmService
{
    private FcmAuthService $auth;
    private string $projectId;

    public function sendToUser(int $userId, string $title, string $body, array $data = []): void
    {
        $tokens = FcmTokenTable::getList([
            'filter' => ['USER_ID' => $userId, 'ACTIVE' => 'Y'],
            'select' => ['TOKEN', 'PLATFORM'],
        ])->fetchAll();

        foreach ($tokens as $tokenRow) {
            $this->sendToToken($tokenRow['TOKEN'], $title, $body, $data, $tokenRow['PLATFORM']);
        }
    }

    private function sendToToken(string $token, string $title, string $body, array $data, string $platform): void
    {
        $message = [
            'token'        => $token,
            'notification' => ['title' => $title, 'body' => $body],
            'data'         => array_map('strval', $data), // FCM requires strings
        ];

        // Platform-specific settings
        if ($platform === 'android') {
            $message['android'] = [
                'priority'     => 'high',
                'notification' => ['channel_id' => 'orders', 'icon' => 'ic_notification'],
            ];
        } elseif ($platform === 'ios') {
            $message['apns'] = [
                'headers' => ['apns-priority' => '10'],
                'payload' => ['aps' => ['sound' => 'default', 'badge' => 1]],
            ];
        }

        $accessToken  = $this->auth->getCachedToken();
        $projectId    = $this->projectId;
        $url          = "https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send";

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => json_encode(['message' => $message]),
            CURLOPT_HTTPHEADER     => [
                'Content-Type: application/json',
                "Authorization: Bearer {$accessToken}",
            ],
        ]);

        $response   = json_decode(curl_exec($ch), true);
        $httpCode   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode === 404 || ($response['error']['code'] ?? 0) === 404) {
            // Token is stale — deactivate
            FcmTokenTable::updateByToken($token, ['ACTIVE' => 'N']);
        }
    }
}

Topics vs Individual Tokens

FCM supports sending to topics (/topics/promo_electronics) — convenient for bulk messaging without storing tokens. Subscribing to a topic:

POST https://iid.googleapis.com/iid/v1:batchAdd
{
    "to": "/topics/promo_electronics",
    "registration_tokens": ["TOKEN1", "TOKEN2"]
}

For transactional notifications (orders belonging to a specific user) — individual tokens only.

Timeline

Task Duration
Service Account, FCM HTTP v1 client, token caching 2–3 days
Token registration (web + android/ios) 3–4 days
Sending on order events + error handling 2–3 days
Service Worker, foreground/background notifications 2–3 days
Subscription management from account, topics 3–5 days
Full scope 3–4 weeks