1C-Bitrix Integration with OneSignal (Push Notifications)
Browser and mobile push notifications are one of the few free direct communication channels with users. OneSignal provides an SDK for web push, iOS, and Android, along with analytics and segmentation. 1C-Bitrix has no built-in OneSignal support, so the integration is built at the following levels: browser subscription → OneSignal → PHP API → Bitrix event.
Integration Architecture
User's browser
→ OneSignal SDK (subscription, obtaining player_id)
→ POST /local/api/onesignal/register (storing player_id in b_user)
→ Bitrix event (order created, delivery, discount)
→ PHP → OneSignal REST API → push to user
player_id (or subscription_id in the new OneSignal SDK v2) is the key identifier for a device/browser. A single user may have multiple player_id values (different browsers, devices).
Frontend SDK Integration
In the <head> of the site 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 }, // using a custom button
allowLocalhostAsSecureOrigin: false,
});
// After permission is granted — get subscription id and bind to user
const subscription = await OneSignal.User.PushSubscription;
if (subscription.optedIn) {
await registerSubscription(subscription.id);
}
// Listen for subscription status changes
OneSignal.User.PushSubscription.addEventListener('change', async (event) => {
if (event.current.optedIn) {
await registerSubscription(event.current.id);
}
});
});
async function registerSubscription(subscriptionId) {
// Bind to the authenticated user
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>
Storing Subscriptions in Bitrix
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. When an anonymous user later logs in, their anonymous subscriptions must be linked to their account.
// On login (OnAfterUserAuthorize event)
AddEventHandler('main', 'OnAfterUserAuthorize', function(array $fields) {
if (empty($fields['USER_ID'])) return;
// Find subscription by session and bind to user
LocalPushSubscriptionTable::update(
['=USER_ID' => false], // NULL
['USER_ID' => $fields['USER_ID']]
);
// Simplified — in practice, subscription_id must be stored in the session
});
Sending Push Notifications via OneSignal REST API
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, // 1 day
]);
}
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) ?? [];
}
}
Notification Trigger Points in Bitrix
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! Please leave a product review'],
];
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 triggered 1 hour after the last item was added to the cart without order completion.
Personalized promotions — bulk sending via sendToSegment() or targeted by user attributes via the OneSignal Filters API.
Subscription Management from the User Account
A "Notifications" page in the user account provides toggles by type: order status, promotions, reminders. Preferences are stored in user fields UF_PUSH_ORDER_STATUS, UF_PUSH_PROMO, etc. Each notification checks the user's permission before sending.
Error Handling and Cleanup
OneSignal returns errors for invalid subscription_id values (user revoked the subscription). Upon receiving an InvalidSubscriptionId error, the record is marked as ACTIVE = N.
Timeline
| Task | Duration |
|---|---|
| SDK integration, saving subscription_id | 2–3 days |
| Subscription table, binding to users | 1–2 days |
| Sending on order events | 2–3 days |
| Abandoned cart, subscription management from account | 3–5 days |
| Full scope | 2–3 weeks |







