Інтеграція 1С-Бітрікс з Firebase Cloud Messaging
Firebase Cloud Messaging (FCM) — інфраструктура Google для push-сповіщень на Android, iOS та у браузері. На відміну від OneSignal, FCM — це низькорівневий транспорт без вбудованого UI та сегментації. Інтеграція з 1С-Бітрікс будується повністю на кастомному коді: SDK на фронтенді реєструє токен пристрою, PHP-бекенд надсилає сповіщення через FCM HTTP v1 API.
FCM HTTP v1 API vs Legacy API
Google відключив Legacy HTTP API (ключ server key) у червні 2024 року. Всі нові інтеграції використовують HTTP v1 API з авторизацією через OAuth 2.0 Service Account. Якщо в проєкті залишилася стара інтеграція через https://fcm.googleapis.com/fcm/send — вона вже не працює.
HTTP v1 endpoint: POST https://fcm.googleapis.com/v1/projects/{project_id}/messages:send
Авторизація — Bearer-токен, що отримується з Service Account JSON через Google Auth Library.
Service Account і авторизація
У Firebase Console → Project Settings → Service Accounts → Generate new private key. Завантажуємо JSON-файл, розміщуємо поза webroot:
/var/www/site/storage/firebase/service-account.json
Токен отримуємо через 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'];
}
}
Встановлюємо залежність: composer require google/auth. Токен кешуємо — він діє 1 годину. Перегенерація при кожному запиті — марнотратство.
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;
}
Реєстрація FCM-токена на фронтенді
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);
}
}
// Обробник foreground-сповіщень (коли вкладка відкрита)
onMessage(messaging, (payload) => {
new Notification(payload.notification.title, {
body: payload.notification.body,
icon: '/local/templates/main/images/push-icon.png'
});
});
Для background-сповіщень потрібен Service Worker /firebase-messaging-sw.js у корені сайту:
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({ /* конфіг */ });
const messaging = firebase.messaging();
messaging.onBackgroundMessage((payload) => {
self.registration.showNotification(payload.notification.title, {
body: payload.notification.body,
data: payload.data,
});
});
Зберігання FCM-токенів
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'),
];
}
}
При оновленні токена (FCM змінює токен при перевстановленні застосунку) — знаходимо старий токен і замінюємо, а не дублюємо запис.
Відправка сповіщення
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 вимагає рядки
];
// Платформо-специфічні налаштування
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']);
}
}
}
Топіки vs індивідуальні токени
FCM підтримує відправку за топіками (/topics/promo_electronics) — зручно для масових розсилок без зберігання токенів. Підписка на топік:
POST https://iid.googleapis.com/iid/v1:batchAdd
{
"to": "/topics/promo_electronics",
"registration_tokens": ["TOKEN1", "TOKEN2"]
}
Для транзакційних сповіщень (замовлення конкретного користувача) — тільки індивідуальні токени.
Терміни
| Завдання | Термін |
|---|---|
| Service Account, FCM HTTP v1 клієнт, кеш токена | 2–3 дні |
| Реєстрація токенів (web + android/ios) | 3–4 дні |
| Відправка за подіями замовлень + обробка помилок | 2–3 дні |
| Service Worker, foreground/background сповіщення | 2–3 дні |
| Управління підпискою з ЛК, топіки | 3–5 днів |
| Повний комплекс | 3–4 тижні |







