Triggered Push Notification Setup in 1C-Bitrix
Triggered push notifications differ from segmented broadcasts in one thing: they are sent automatically in response to a specific user action, not on a schedule. Abandoned cart after 2 hours, price drop on a viewed item, restocked wishlist item — these are trigger scenarios. Properly configured, they deliver ROI of 500–1500%.
Event Architecture in Bitrix
Triggered push is built on Bitrix's event model. Every system event is a potential trigger. How it works:
- An event occurs (item added to cart, product viewed, order status changed)
- Event handler checks scenario conditions
- If conditions met — push send task is queued with a delay
- Bitrix agent processes queue and sends notifications
Task queue is stored in table:
CREATE TABLE custom_push_queue (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
scenario VARCHAR(50) NOT NULL,
payload JSON,
send_at DATETIME NOT NULL,
status ENUM('pending', 'sent', 'cancelled') DEFAULT 'pending',
created_at DATETIME,
INDEX idx_send_at (send_at, status),
INDEX idx_user_scenario (user_id, scenario)
);
Key Scenarios
Abandoned cart — most valuable scenario. Logic:
// OnSaleBasketItemSaved handler
AddEventHandler('sale', 'OnSaleBasketItemSaved', function($basketItem) {
$userId = (int)$basketItem->getField('USER_ID');
if (!$userId) return; // Only authorized
// Cancel previous task for this user
PushQueueTable::cancelByUserAndScenario($userId, 'abandoned_cart');
// Queue new one — in 2 hours
PushQueueTable::add([
'USER_ID' => $userId,
'SCENARIO' => 'abandoned_cart',
'PAYLOAD' => json_encode(['cart_url' => '/cart/']),
'SEND_AT' => new \Bitrix\Main\Type\DateTime(date('Y-m-d H:i:s', time() + 7200)),
'STATUS' => 'pending',
'CREATED_AT' => new \Bitrix\Main\Type\DateTime(),
]);
});
When user completes order — task is cancelled. When not — push arrives in 2 hours.
Price drop on viewed item — requires view history and price monitoring:
// OnAfterIBlockElementUpdate handler
AddEventHandler('iblock', 'OnAfterIBlockElementUpdate', function(&$fields) {
$elementId = (int)$fields['ID'];
$newPrice = getElementPrice($elementId);
// Find users who viewed this item
$viewers = ProductViewHistoryTable::getUsersByProduct($elementId, 30); // last 30 days
foreach ($viewers as $viewer) {
$oldPrice = ProductPriceHistoryTable::getLastPrice($elementId, $viewer['USER_ID']);
if ($newPrice < $oldPrice * 0.9) { // 10%+ discount
PushQueueTable::add([
'USER_ID' => $viewer['USER_ID'],
'SCENARIO' => 'price_drop',
'PAYLOAD' => json_encode(['product_id' => $elementId, 'new_price' => $newPrice]),
'SEND_AT' => new \Bitrix\Main\Type\DateTime(), // Immediately
'STATUS' => 'pending',
]);
}
}
});
Back in stock — via quantity change handler in b_catalog_store_product or 1C sync.
Order status — OnSaleStatusOrderChange event handler. Push sent immediately on each status change.
Queue Processing Agent
Bitrix agent runs every minute:
function ProcessPushQueue(): string {
$now = new \Bitrix\Main\Type\DateTime();
$records = PushQueueTable::getList([
'filter' => ['=STATUS' => 'pending', '<=SEND_AT' => $now],
'limit' => 100,
]);
while ($record = $records->fetch()) {
$tokens = PushTokenTable::getByUserId($record['USER_ID']);
if (!empty($tokens)) {
$message = PushScenario::buildMessage($record['SCENARIO'], json_decode($record['PAYLOAD'], true));
PushSender::send($tokens, $message);
}
PushQueueTable::update($record['ID'], ['STATUS' => 'sent']);
}
return __FUNCTION__ . '();';
}
Deduplication and Smart Scheduling
Without deduplication, user gets three push in an hour. Rules:
- One scenario — no more than once per 24 hours per user
- Don't send push from 23:00 to 09:00 (user's timezone)
- If user already bought after event — cancel task
User timezone from profile or geolocation on registration.
Execution Timeline
| Scope | Timeline |
|---|---|
| Abandoned cart + status change | 2–3 days |
| Price drop + back in stock | +2 days |
| Deduplication + timezones + analytics | +1–2 days |
Each working triggered scenario is an automated salesman who doesn't take salary.

