Setting Up Geo-Zone Push Notifications in 1C-Bitrix
Push notifications by geo-zone are a specific case of geofencing focused on the delivery channel. The goal: when a user enters a set radius around a point, they receive a push notification on their phone. This article covers the server-side implementation in Bitrix.
Push Delivery Stack
Two channels exist for mobile push notifications:
- FCM (Firebase Cloud Messaging) — Android and iOS (via Firebase APNs proxy)
- APNs (Apple Push Notification service) — iOS directly
For websites without a mobile app, Web Push (W3C standard) exists, but it requires an open browser or Service Worker support and does not work in iOS Safari prior to iOS 16.4.
Bitrix has a built-in push.sender module — but it is intended for Bitrix24 and the Push & Pull server. For custom push notifications in a mobile app, FCM is used directly from PHP.
Registering and Storing Device Tokens
Before sending a push, you need to obtain the user's device token. The app requests permission on first launch, receives a token from FCM/APNs, and then sends it to the server.
Endpoint for token registration (/local/ajax/register-token.php):
$userId = $USER->GetID();
$deviceToken = $data['token'];
$platform = $data['platform']; // 'android' or 'ios'
// Save to HL-iblock DeviceTokens
\Local\Push\DeviceTokenTable::add([
'UF_USER_ID' => $userId,
'UF_TOKEN' => $deviceToken,
'UF_PLATFORM' => $platform,
'UF_UPDATED' => new \Bitrix\Main\Type\DateTime(),
]);
One user may have multiple tokens (phone + tablet). Tokens expire — FCM returns a NotRegistered error, which should trigger deletion of the token from the database.
Geo-Zone Trigger
A geo-zone event arrives from the mobile app (for native geofencing logic, see the geofencing notifications article). The server-side receives the event and looks up all active tokens for the user:
$tokens = \Local\Push\DeviceTokenTable::getList([
'filter' => ['=UF_USER_ID' => $userId, '=UF_ACTIVE' => true],
'select' => ['UF_TOKEN', 'UF_PLATFORM'],
])->fetchAll();
foreach ($tokens as $token) {
\Local\Push\Sender::send(
$token['UF_TOKEN'],
$token['UF_PLATFORM'],
$zone['UF_PUSH_TITLE'],
$zone['UF_PUSH_BODY'],
['zone_id' => $zoneId, 'action' => 'open_promo']
);
}
Sending via FCM HTTP v1 API
Google is migrating from the legacy FCM API to HTTP v1 (OAuth2). Example of sending a single push:
namespace Local\Push;
class Sender {
public static function send(
string $token,
string $platform,
string $title,
string $body,
array $data = []
): bool {
$accessToken = self::getOAuthToken(); // OAuth2 via Service Account JSON
$message = [
'message' => [
'token' => $token,
'notification' => [
'title' => $title,
'body' => $body,
],
'data' => array_map('strval', $data),
],
];
$http = new \Bitrix\Main\Web\HttpClient();
$http->setHeader('Authorization', 'Bearer ' . $accessToken);
$http->setHeader('Content-Type', 'application/json');
$response = $http->post(
'https://fcm.googleapis.com/v1/projects/' . FCM_PROJECT_ID . '/messages:send',
json_encode($message)
);
$result = json_decode($response, true);
return isset($result['name']); // name is present on success
}
}
For getOAuthToken(), use the Google Client Library or a custom JWT-signing implementation with a Service Account.
Custom Sound and Icon
For Android notifications, you can set a notification channel, sound, and icon via the android section in the FCM payload. For iOS — via the apns section. These parameters are defined during mobile app development; the server-side only passes the values.
Delivery Monitoring
FCM HTTP v1 returns a result immediately, but this is only confirmation that the message was accepted for processing, not proof of delivery. Tracking notification opens requires analytics on the app side (Firebase Analytics or a custom endpoint).
| Stage | Time |
|---|---|
| Token storage (HL-iblock + API) | 3–4 h |
| FCM HTTP v1 integration | 4–6 h |
| Geo-zone event handler | 2–3 h |
| Deduplication / cooldown logic | 2–3 h |
| Testing on real devices | 3–5 h |







