Setting up web push notifications on the 1C-Bitrix website

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
    1212
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815
  • 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
    565
  • 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
    980

Web Push Notification Setup on 1C-Bitrix Website

Web push notifications work through the browser Push API and Service Worker — technologies independent of PHP and Bitrix. Bitrix's task: store user subscriptions and initiate notification sending through events (new order, promotion, cart reminder). The actual sending goes through Web Push Protocol to browser vendor servers (FCM for Chrome, Mozilla Push Service for Firefox).

Infrastructure: VAPID keys and Service Worker

For Web Push, you need VAPID keys (Voluntary Application Server Identification) — a pair of public/private keys for server authentication before the browser push service. Generated once:

composer require minishlink/web-push
use Minishlink\WebPush\VAPID;
$keys = VAPID::createVapidKeys();
// ['publicKey' => '...', 'privateKey' => '...']

The public key is passed to the browser when registering a subscription, the private key is stored on the server in b_option:

COption::SetOptionString('local', 'vapid_public_key', $keys['publicKey']);
COption::SetOptionString('local', 'vapid_private_key', $keys['privateKey']);

Service Worker — a JS file registered by the browser for background operation. It processes incoming push messages when the user is not on your site. The service-worker.js file should be located at the site root (/service-worker.js), not in a subdirectory — this is a browser scope limitation:

self.addEventListener('push', function(event) {
    const data = event.data.json();
    event.waitUntil(
        self.registration.showNotification(data.title, {
            body: data.body,
            icon: data.icon || '/local/images/push-icon.png',
            data: { url: data.url }
        })
    );
});

self.addEventListener('notificationclick', function(event) {
    event.notification.close();
    event.waitUntil(clients.openWindow(event.notification.data.url));
});

User subscription

On the browser side, you request permission and create a subscription:

async function subscribeToPush() {
    const registration = await navigator.serviceWorker.register('/service-worker.js');
    const subscription = await registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_VAPID_PUBLIC_KEY')
    });

    // Send subscription to server
    await fetch('/local/ajax/push_subscribe.php', {
        method: 'POST',
        body: JSON.stringify(subscription),
        headers: { 'Content-Type': 'application/json' }
    });
}

On the server in /local/ajax/push_subscribe.php you save the subscription. Create a subscriptions table:

CREATE TABLE b_local_push_subscription (
    ID INT AUTO_INCREMENT PRIMARY KEY,
    USER_ID INT,              -- NULL for unauthenticated
    ENDPOINT VARCHAR(500) NOT NULL,
    P256DH TEXT NOT NULL,
    AUTH VARCHAR(100) NOT NULL,
    CREATED_AT DATETIME,
    LAST_ACTIVE DATETIME,
    UNIQUE KEY idx_endpoint (ENDPOINT(200))
);

Sending notifications from Bitrix

Service class for sending via minishlink/web-push library:

use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;

function sendPushNotification(array $subscription, string $title, string $body, string $url): void {
    $auth = [
        'VAPID' => [
            'subject' => 'https://example.com',
            'publicKey' => COption::GetOptionString('local', 'vapid_public_key'),
            'privateKey' => COption::GetOptionString('local', 'vapid_private_key'),
        ],
    ];

    $webPush = new WebPush($auth);
    $webPush->queueNotification(
        Subscription::create([
            'endpoint' => $subscription['ENDPOINT'],
            'keys' => ['p256dh' => $subscription['P256DH'], 'auth' => $subscription['AUTH']],
        ]),
        json_encode(['title' => $title, 'body' => $body, 'url' => $url])
    );

    foreach ($webPush->flush() as $report) {
        if ($report->isSubscriptionExpired()) {
            // Delete stale subscription from b_local_push_subscription
            deleteExpiredSubscription($report->getEndpoint());
        }
    }
}

Integration with Bitrix events

Cart abandonment reminder. An agent runs hourly, finds carts with items older than 2 hours from subscribed users and sends push. Table b_sale_basket, filter by DATE_UPDATE < NOW() - INTERVAL 2 HOUR and ORDER_ID IS NULL.

Order status change notification. Handler OnSaleOrderStatusUpdate:

AddEventHandler("sale", "OnSaleOrderStatusUpdate", function($orderId, $arFields) {
    if ($arFields['STATUS_ID'] === 'D') { // Delivered
        $userId = CSaleOrder::GetByID($orderId)['USER_ID'];
        sendPushToUser($userId, 'Order delivered', 'Your order #' . $orderId . ' is waiting for you');
    }
});

Mass mailings. Select all subscribers from b_local_push_subscription, send in batches of 100 via webPush->queueNotification() + flush(). Large mailings — via agent with pagination to avoid script timeout limits.

Delete stale subscriptions (endpoint returned 404 or 410) immediately — they accumulate quickly and slow down mailings.