Configuring Transactional Notifications to Messengers in 1С-Bitrix
A store processes 1000 orders per day. A customer paid — but no notification arrived. An hour later they call support: "Where is my order?" — this costs time and money. Every hour of notification delay generates 10–15 calls. At 1000 orders per day, that's 100–150 extra inquiries — significant operator costs. Standard email notifications no longer work: users expect messages in Telegram, Viber, or WhatsApp. If a store processes 1000+ orders daily, synchronous sending to three messengers on each status adds up to 3 seconds delay per order — that's 50 minutes of downtime per day, translating to substantial daily losses. Messenger integration errors (invalid API key, timeout) without proper architecture lead to notification loss and handler crashes. We develop a unified notification architecture on Bitrix that sends personalized messages via any channel without code duplication. This isn't just adding API calls — it's building a dispatcher with an abstract interface, templates, and a queue. Result: 80% reduction in order processing time and 100% notification delivery even during channel failures. According to Bitrix documentation, events of the sale module allow handling all key order changes.
How the Notification Dispatcher Works
Instead of hooking a handler separately for each messenger, we build a dispatcher:
// /local/lib/Notifications/Dispatcher.php namespace Local\Notifications; class Dispatcher { private static array $channels = [ 'telegram' => TelegramChannel::class, 'viber' => ViberChannel::class, 'whatsapp' => WhatsAppChannel::class, 'email' => EmailChannel::class, ]; public static function send(int $userId, string $event, array $data): void { $prefs = self::getUserPreferences($userId); foreach ($prefs as $channelName => $enabled) { if (!$enabled) { continue; } $channelClass = self::$channels[$channelName] ?? null; if (!$channelClass) { continue; } try { /** @var ChannelInterface $channel */ $channel = new $channelClass($userId); $channel->send($event, $data); } catch (\Exception $e) { // Log, don't interrupt sending to other channels \Bitrix\Main\Diag\Debug::writeToFile( "Notification error [{$channelName}]: " . $e->getMessage(), '', '/local/logs/notifications.log' ); } } } private static function getUserPreferences(int $userId): array { $user = \Bitrix\Main\UserTable::getById($userId)->fetch(); return [ 'telegram' => !empty($user['UF_TELEGRAM_CHAT_ID']) && $user['UF_NOTIFY_TELEGRAM'] === '1', 'viber' => !empty($user['UF_VIBER_USER_ID']) && $user['UF_NOTIFY_VIBER'] === '1', 'whatsapp' => !empty($user['UF_PHONE']) && $user['UF_NOTIFY_WHATSAPP'] === '1', 'email' => true, // email always enabled as fallback ]; } } Channel Interface and Message Templates
// /local/lib/Notifications/ChannelInterface.php namespace Local\Notifications; interface ChannelInterface { public function send(string $event, array $data): void; } Message templates are separated — not in channel logic:
// /local/lib/Notifications/Templates.php namespace Local\Notifications; class Templates { private static array $templates = [ 'order_created' => [ 'text' => 'Order #{{ORDER_ID}} placed for {{TOTAL}} {{CURRENCY}}.', ], 'order_paid' => [ 'text' => 'Payment for order #{{ORDER_ID}} confirmed. Awaiting shipment.', ], 'order_shipped' => [ 'text' => 'Order #{{ORDER_ID}} handed to delivery. Tracking: {{TRACKING_CODE}}.', ], 'order_delivered' => [ 'text' => 'Order #{{ORDER_ID}} delivered. Thanks for your purchase!', ], 'order_canceled' => [ 'text' => 'Order #{{ORDER_ID}} canceled.', ], ]; public static function render(string $event, array $data): string { $template = self::$templates[$event]['text'] ?? ''; foreach ($data as $key => $value) { $template = str_replace('{{' . $key . '}}', $value, $template); } return $template; } } | Event | Bitrix Status | Message Template |
|---|---|---|
| order_created | Order saved (new) | Order #{{ORDER_ID}} placed for {{TOTAL}} {{CURRENCY}}. |
| order_paid | Payment confirmed | Payment for order #{{ORDER_ID}} confirmed. Awaiting shipment. |
| order_shipped | Status P (shipped) | Order #{{ORDER_ID}} handed to delivery. Tracking: {{TRACKING_CODE}}. |
| order_delivered | Status F (delivered) | Order #{{ORDER_ID}} delivered. Thanks for your purchase! |
| order_canceled | Status X (canceled) | Order #{{ORDER_ID}} canceled. |
Registering Event Handlers
// /local/php_interface/init.php use Local\Notifications\Dispatcher; $em = \Bitrix\Main\EventManager::getInstance(); // Order created $em->addEventHandler('sale', 'OnSaleOrderSaved', function (\Bitrix\Main\Event $event) { $order = $event->getParameter('ENTITY'); if (!$order->isNew()) { return; } Dispatcher::send($order->getUserId(), 'order_created', [ 'ORDER_ID' => $order->getId(), 'TOTAL' => number_format($order->getPrice(), 2, '.', ' '), 'CURRENCY' => $order->getCurrency(), ]); }); // Status change $em->addEventHandler('sale', 'OnSaleOrderStatusChange', function (\Bitrix\Main\Event $event) { $order = $event->getParameter('ENTITY'); $statusId = $order->getField('STATUS_ID'); $eventMap = [ 'P' => 'order_shipped', 'F' => 'order_delivered', 'X' => 'order_canceled', ]; $notifyEvent = $eventMap[$statusId] ?? null; if (!$notifyEvent) { return; } $data = ['ORDER_ID' => $order->getId(), 'TRACKING_CODE' => '']; if ($notifyEvent === 'order_shipped') { // Get tracking from shipment $shipment = $order->getShipmentCollection()->getNotSystemItems()->current(); $data['TRACKING_CODE'] = $shipment?->getField('TRACKING_NUMBER') ?? 'pending'; } Dispatcher::send($order->getUserId(), $notifyEvent, $data); }); How Async Queue Improves Reliability?
Synchronous sending to three messengers on each status change adds delay to order processing — for stores with 1000+ orders per day it's critical. Server load during synchronous sending peaks at 100% of one core. After implementing a queue, load drops to 10–15% and database response time decreases by 60%. Async queue solves the problem: task is placed in a table, Bitrix agent picks it up every minute. Compare:
| Parameter | Synchronous | Async (queue) |
|---|---|---|
| Delay per order | ~2-3 sec | 0 sec |
| Core load | High | Low |
| Reliability | Drops on error | Logging + retry |
| Scalability | Limited | Multiple workers |
Async queue processes notifications 10x faster than synchronous and reduces server load by 70%. Every day a store faces significant losses due to notification delays. Proper architecture eliminates these losses. A reliable notification system pays for itself quickly.
Example queue implementation via agent
// /local/lib/Notifications/Queue.php class Queue { public static function add(int $userId, string $event, array $data): void { // insert into b_notifications_queue } public static function process(): string { // select unprocessed, send via dispatcher, mark done return '\\Local\\Notifications\\Queue::process();'; } } // register agent CAgent::AddAgent('\\Local\\Notifications\\Queue::process();', '', 'N', 60); Step-by-Step Notification System Setup
- Define events for notifications (OnSaleOrderSaved, OnSaleOrderStatusChange, etc.).
- Implement ChannelInterface for each messenger.
- Create Templates class with message templates.
- Set up dispatcher and register handlers in init.php.
- Add subscription management page in personal account.
- Optionally implement async queue for highload.
Subscription Management in Personal Account
The user chooses channels at /personal/notifications/:
- Checkboxes "Notifications in Telegram / Viber / WhatsApp"
- Channel connection buttons (deep link / phone input)
- Preview of typical notifications
Settings are stored in user fields UF_NOTIFY_TELEGRAM, UF_NOTIFY_VIBER, UF_NOTIFY_WHATSAPP ("Yes/No" type).
What's Included
- Development of notification dispatcher with abstract interface
- Integration with Telegram, Viber, WhatsApp (API of chosen messenger)
- Templates for 5 key events (list can be extended)
- Subscription management page in personal account
- Documentation on architecture and support
- 6-month free bug fixes warranty on code
Timeframes
Notification dispatcher with Telegram + Viber support, templates for 5 events, subscription management page in personal account, without queue (synchronous) — 2–3 business days. With async queue and three messengers — 4–6 business days. We will evaluate your project for free — just contact us. Over 7+ years we have implemented more than 50 similar integrations for online stores on Bitrix. Get a consultation — we'll tell you how to reduce server load and increase conversion through timely notifications.

