Integrating 1C-Bitrix with OneSignal (push notifications)

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1183
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    813
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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