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.







