How Push Notifications Recovered 18% of Abandoned Carts
One of our clients—an electronics e‑commerce store with 300,000 unique monthly visitors—was losing up to 40% of abandoned carts. After implementing push notifications via OneSignal, the cart recovery conversion rate increased by 18%. We send 5,000+ personalized notifications daily. The average CTR is 12% (vs. 3% for email). The integration paid for itself in 2 weeks, and average customer LTV grew by 25% thanks to repeat purchases. Our team has 5+ years of experience in Bitrix development and has completed over 50 successful push notification integrations.
OneSignal uses the Web Push Protocol to deliver push notifications to the browser. Even when the browser is closed, the Service Worker processes the message and displays it. This fundamentally saves resources: no mail server or SMS gateway is needed.
Architecture and Data Storage
Browser → OneSignal SDK (subscription, obtains player_id) → POST /local/api/onesignal/register (save player_id in b_user) → Bitrix event (order, delivery, discount) → PHP → OneSignal REST API → push to user.
The key element is player_id (or subscription_id in the new SDK). One user may have multiple ones (different browsers and devices).
For storing subscriptions we use a separate table local_push_subscriptions:
CREATE TABLE local_push_subscriptions (
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
USER_ID INT,
SUBSCRIPTION_ID VARCHAR(200) NOT NULL UNIQUE,
PLATFORM ENUM('web','android','ios') DEFAULT 'web',
ACTIVE CHAR(1) DEFAULT 'Y',
CREATED_AT DATETIME,
LAST_USED_AT DATETIME,
INDEX idx_user (USER_ID)
);
USER_ID can be NULL for unauthenticated visitors—they are identified by SUBSCRIPTION_ID. Upon login, we “join” anonymous subscriptions to the account (event OnAfterUserAuthorize).
// При авторизации (событие OnAfterUserAuthorize)
AddEventHandler('main', 'OnAfterUserAuthorize', function(array $fields) {
if (empty($fields['USER_ID'])) return;
// Ищем подписку по сессии, привязываем к пользователю
LocalPushSubscriptionTable::update(
['=USER_ID' => false], // NULL
['USER_ID' => $fields['USER_ID']]
);
});
Why OneSignal Is Better Than a Self-Made Solution
Self‑made push solutions require supporting the Web Push Protocol (VAPID), handling subscriptions for different browsers, and scaling. OneSignal eliminates this: the SDK updates automatically, segmentation and analytics are built‑in, and message TTL is configurable. For a typical e‑commerce store, OneSignal is many times cheaper and faster. Infrastructure savings can reach 70%.
| Criteria | OneSignal | Self‑made solution |
|---|---|---|
| Setup time | 1–2 days | 1–2 weeks |
| Cross‑browser support | Built‑in | Requires manual configuration |
| Analytics and A/B tests | Ready dashboard | Development from scratch |
| Price per 10,000 subscribers | Free up to 10,000 | ~$50/month for infrastructure |
Step‑by‑Step Frontend SDK Setup
Install the script in the <head> of the template or via a tag manager:
<!-- OneSignal Web Push SDK -->
<script src="https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js" defer></script>
<script>
window.OneSignalDeferred = window.OneSignalDeferred || [];
OneSignalDeferred.push(async function(OneSignal) {
await OneSignal.init({
appId: "YOUR_ONESIGNAL_APP_ID",
safari_web_id: "web.onesignal.auto.YOUR_SAFARI_ID",
notifyButton: { enable: false },
allowLocalhostAsSecureOrigin: false,
});
const subscription = await OneSignal.User.PushSubscription;
if (subscription.optedIn) {
await registerSubscription(subscription.id);
}
OneSignal.User.PushSubscription.addEventListener('change', async (event) => {
if (event.current.optedIn) {
await registerSubscription(event.current.id);
}
});
});
async function registerSubscription(subscriptionId) {
await fetch('/local/api/onesignal/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Bitrix-Csrf-Token': BX.bitrix_sessid()
},
body: JSON.stringify({ subscription_id: subscriptionId })
});
}
</script>
Important: the Service Worker must be registered through the SDK. OneSignal automatically generates the OneSignalSDKWorker.js file. If you are using your own Service Worker, you need to dig into it.
Sending Notifications via REST API and Handling Errors
The server side in PHP sends notifications based on Bitrix events:
class OneSignalService
{
private string $appId;
private string $restApiKey;
private string $baseUrl = 'https://onesignal.com/api/v1';
public function sendToUser(int $userId, string $title, string $body, array $data = []): array
{
$subscriptions = LocalPushSubscriptionTable::getList([
'filter' => ['USER_ID' => $userId, 'ACTIVE' => 'Y'],
'select' => ['SUBSCRIPTION_ID'],
])->fetchAll();
if (empty($subscriptions)) {
return ['skipped' => 'no_subscriptions'];
}
$subscriptionIds = array_column($subscriptions, 'SUBSCRIPTION_ID');
return $this->send([
'app_id' => $this->appId,
'include_subscription_ids' => $subscriptionIds,
'headings' => ['en' => $title, 'ru' => $title],
'contents' => ['en' => $body, 'ru' => $body],
'data' => $data,
'web_url' => $data['url'] ?? '/',
'ttl' => 86400,
]);
}
public function sendToSegment(string $segment, string $title, string $body, array $extra = []): array
{
return $this->send(array_merge([
'app_id' => $this->appId,
'included_segments' => [$segment],
'headings' => ['en' => $title, 'ru' => $title],
'contents' => ['en' => $body, 'ru' => $body],
], $extra));
}
private function send(array $payload): array
{
$ch = curl_init("{$this->baseUrl}/notifications");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"Authorization: Key {$this->restApiKey}",
],
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true) ?? [];
}
}
Example response for InvalidSubscriptionId error
{
"id": "",
"recipients": 0,
"errors": ["All included subscription ids are invalid or have reached their rate limit"]
}
Upon receiving such a response, we set ACTIVE to N for the corresponding subscriptions.
Integration Points with Bitrix Events and Segmentation
Order status change:
AddEventHandler('sale', 'OnSaleStatusOrder', function(string $statusId, \Bitrix\Sale\Order $order) {
$messages = [
'P' => ['Order received', 'Your order #' . $order->getField('ACCOUNT_NUMBER') . ' has been accepted for processing'],
'D' => ['Order shipped', 'Your order has been handed over for delivery'],
'F' => ['Order completed', 'Thank you for your purchase! Leave a review for the product'],
];
if (!isset($messages[$statusId])) return;
[$title, $body] = $messages[$statusId];
$userId = (int)$order->getUserId();
(new OneSignalService())->sendToUser($userId, $title, $body, [
'url' => '/personal/order/detail/' . $order->getField('ACCOUNT_NUMBER') . '/',
'order_id' => $order->getId(),
]);
});
Abandoned cart—via an agent every 60 minutes. Personal promotions—mass sending via OneSignal segments or user filters.
OneSignal can segment users by behavior: abandoned cart, viewed categories, device type. We set up automatic segments via the REST API using data from Bitrix. For example, the segment “Buyers with high order total and no purchase for 30 days”—we send them a personal discount. The effectiveness of such segments is 2–3 times higher than mass mailings.
Subscription Management from the Personal Account
On the “Notifications” page, the user selects push types: order status, promotions, reminders. Settings are stored in user fields (UF_PUSH_ORDER_STATUS, UF_PUSH_PROMO). We check the flag before sending.
What Is Included in the Work and Timelines
- Deploying the SDK on the frontend (custom subscription button).
- Creating a subscription table with Bitrix ORM.
- Linking subscriptions to users (including authorization).
- Configuring sending by events: orders, abandoned carts, promotions.
- Subscription management interface in the personal account.
- Documentation and training for your developers.
- 14 days of support after delivery.
The integration cost starts from $1,500 and typically pays back within 2–3 months.
| Task | Duration |
|---|---|
| SDK integration, saving subscription_id | 2–3 days |
| Subscription table, linking to users | 1–2 days |
| Sending by order events | 2–3 days |
| Abandoned cart, subscription management from personal account | 3–5 days |
| Complete package | 2–3 weeks |
We will assess your project in one day. Contact us—we will select the optimal turnkey solution. We guarantee stability and transparency. Get a consultation right now—it's free. Order the integration and start earning additional profit in just a week.







