When your project outgrows third-party push services, you need a direct integration with Firebase Cloud Messaging (FCM). But FCM's HTTP v1 API demands a custom OAuth 2.0 setup, token management, and platform-specific payloads. Unlike OneSignal, FCM is a low-level transport without built-in UI or segmentation. We specialize in building this integration for high-load projects, ensuring stable delivery even under heavy traffic. The final cost is determined after a thorough analysis of your infrastructure and requirements.
How to Set Up FCM HTTP v1 API in 1C-Bitrix
Google has deprecated the Legacy HTTP API (server key). All new integrations must use HTTP v1 API with OAuth 2.0 authorization via a Service Account. If your project still uses https://fcm.googleapis.com/fcm/send, it will stop working. The HTTP v1 endpoint is POST https://fcm.googleapis.com/v1/projects/{project_id}/messages:send. Authorization is done with a Bearer token obtained from a Service Account JSON file through 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, e.g., /var/www/site/storage/firebase/service-account.json. Obtain the token via JWT. Install the dependency: composer require google/auth. Cache the token — it is valid for 1 hour. Regenerating it on every request is wasteful.
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'];
}
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;
}
}
For the FCM HTTP v1 API, PHP 8.1 or higher is required. Ensure the curl extension is enabled.
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);
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);
}
}
onMessage(messaging, (payload) => {
new Notification(payload.notification.title, {
body: payload.notification.body,
icon: '/local/templates/main/images/push-icon.png'
});
});
For background notifications, you need a Service Worker at /firebase-messaging-sw.js 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 and Sending Notifications
Database Table Structure for 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 updated (FCM changes tokens on app reinstall), search by the old token and replace it, rather than duplicating the record.
Sending a Notification
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),
];
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) {
FcmTokenTable::updateByToken($token, ['ACTIVE' => 'N']);
}
}
}
Common Problems During FCM Integration
The following errors are frequently encountered during setup:
- Invalid Service Account: The JSON file is corrupted or has incorrect permissions. Ensure the file is readable by the web server and contains the correct project_id.
- Stale token: If the client does not refresh the token, notifications stop arriving. Implement token refresh on the client and handle 404 errors on the server.
- Quota exceeded: FCM has a limit on requests per second. For bulk sends, use topics and add pauses between requests.
Topics vs. Individual Tokens: What to Choose?
FCM supports sending to topics (/topics/promo_electronics) which is convenient for bulk campaigns without storing tokens. Subscription to a topic is done via REST API. For transactional notifications (e.g., a specific user's order), use individual tokens only. Topics are better for promotional blasts, while individual tokens are for personalized alerts.
How to Handle FCM Errors?
| Common Error | Cause | Solution |
|---|---|---|
| 404 NOT_FOUND | Token is stale | Deactivate the token in the database |
| 403 Forbidden | Invalid Service Account or project | Check configuration and permissions |
| 400 InvalidToken | Token does not match format | Verify that the token was obtained from Firebase SDK |
For bulk sends, topics can be used, but individual tokens offer more control and personalization.
Work Process
- Analysis — Study the website architecture, define push notification scenarios.
- Design — Design the token storage structure, integration with Bitrix events.
- Implementation — Set up Service Account, write token registration and sending logic.
- Testing — Verify on all platforms (web, Android, iOS), emulate error scenarios.
- Deployment — Deploy to production, set up monitoring.
What's Included in the Work
- Development of a frontend module for FCM token registration.
- Implementation of a PHP sending service supporting HTTP v1 API with token caching.
- Creation of a token storage table with ORM mapping.
- Integration with the 1C-Bitrix event system (order creation, status change).
- Configuration of a Service Worker for background notifications.
- Documentation for deployment and maintenance.
- 30-day warranty on the integration.
Time Estimates
| Task | Duration |
|---|---|
| Service Account, FCM HTTP v1 client, token cache | 2–3 days |
| Token registration (web + android/ios) | 3–4 days |
| Event-driven sending (order events) + error handling | 2–3 days |
| Service Worker, foreground/background notifications | 2–3 days |
| Subscription management from user profile, topics | 3–5 days |
| Full package | 3–4 weeks |
Timeframes are approximate and depend on project complexity. Exact deadlines are determined after an audit.
Get a consultation on implementing FCM in your project. Order a turnkey integration with a 30-day warranty.







