Imagine: you run an e-commerce store on 1C-Bitrix with a mobile app. A customer approaches your physical store — you want to send them a personalized offer via a geofenced push. Without geofencing this is impossible. We solve this end-to-end: from token storage to FCM integration and error handling. Contact us — we will assess your project free of charge.
How the server side processes geozone events
A geozone event arrives from the mobile app. On the server, we receive the event, identify the user, and look up all their active tokens in the HL-block DeviceTokens. For each token we build a push via the FCM HTTP v1 API. This API is three times more reliable than the Legacy API and supports OAuth2.
Which push delivery stack we use
For mobile push notifications there are two channels:
- FCM (Firebase Cloud Messaging) — Android and iOS (via Firebase APNs proxy)
- APNs (Apple Push Notification service) — iOS directly
For a website without a mobile app there are Web Push (W3C standard), but they require an open browser or Service Worker support and do not work in iOS Safari before iOS 16.4.
Bitrix has a built-in module push.sender — but it is designed for Bitrix24 and the Push & Pull server. For custom pushes in a mobile app we use FCM directly from PHP.
Device token registration and storage
Before sending a push, you need to obtain the device token. The app requests permission on first launch, gets the token from FCM/APNs, 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-block DeviceTokens \Local\Push\DeviceTokenTable::add([ 'UF_USER_ID' => $userId, 'UF_TOKEN' => $deviceToken, 'UF_PLATFORM' => $platform, 'UF_UPDATED' => new \Bitrix\Main\Type\DateTime(), ]); One user can have multiple tokens (phone + tablet). Tokens expire — FCM returns a NotRegistered error, which should trigger deletion of the token from the database. According to FCM documentation, HTTP v1 API is recommended for new projects.
Why proper token storage matters
Improper storage leads to sending to inactive devices, increasing error counts, and risking a block from FCM. We maintain an update history, delete tokens after errors, and keep them current.
Geozone trigger
The geozone event comes from the mobile app (native geofencing logic). The server 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 one 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 present on success } } For getOAuthToken() we use the Google Client Library or a custom JWT signature with a Service Account.
Custom sound and icon
For Android notifications you can set a notification channel, sound, and icon via the android section of the FCM payload. For iOS, use the apns section. These parameters are defined during mobile app development; the server only passes the values.
Delivery monitoring
FCM HTTP v1 returns a result immediately, but that only confirms acceptance for processing, not actual delivery. To track notification opens, you need analytics on the app side (Firebase Analytics or a custom endpoint).
How to set up geofenced push: step-by-step
- Create an HL-block DeviceTokens with fields: UF_USER_ID (int), UF_TOKEN (string), UF_PLATFORM (string), UF_ACTIVE (bool), UF_UPDATED (datetime).
- Implement an endpoint for token registration: accepts POST request with token and platform, saves the record.
- Write a handler for geozone entry: when an event arrives from the app, get the userId, find all active tokens.
- For each token call
Sender::send()with the notification title and body. - Implement a cooldown: before sending, check when the last notification was sent for this user+zone pair.
Example cooldown implementation
$lastSent = \Local\Push\SentHistoryTable::getRow([ 'filter' => ['=UF_USER_ID' => $userId, '=UF_ZONE_ID' => $zoneId], 'select' => ['UF_SENT_AT'], ]); if ($lastSent && (time() - $lastSent['UF_SENT_AT']->getTimestamp()) < 600) { return; // do not send more than once every 10 minutes } What's included in the work
- Setting up HL-blocks for storing tokens and geozone history
- Token registration endpoint
- FCM HTTP v1 integration with OAuth2
- Geozone event handler
- Deduplication and cooldown logic
- Testing on real devices
- API and setup documentation
Process overview
| Stage | Description | Duration |
|---|---|---|
| Analysis | Gather requirements, agree on geozone schema | 1 day |
| Design | Design HL-blocks and API | 1 day |
| Development | Write code, integrate with FCM | 2–3 days |
| Testing | Verify on Android and iOS | 1–2 days |
| Deployment | Deploy to server, monitor | 1 day |
| Component | Time |
|---|---|
| Token storage (HL-block + API) | 3–4 h |
| FCM HTTP v1 integration | 4–6 h |
| Geozone event handler | 2–3 h |
| Deduplication / cooldown logic | 2–3 h |
| Testing on real devices | 3–5 h |
Timelines and cost
Basic setup takes 2 to 5 days depending on complexity. Cost is calculated individually after project assessment. We guarantee stable operation and post-deployment support. Request a consultation — we will evaluate your project free of charge.

