Setting up data exchange via message queues (RabbitMQ) in 1C-Bitrix

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
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • 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
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Configuring Data Exchange via Message Queues (RabbitMQ) for 1C-Bitrix

Synchronous data transfer between 1C-Bitrix and external systems directly inside an event handler is unreliable. If the external system is unavailable, the user receives an error or the event is lost. RabbitMQ solves this: 1C-Bitrix publishes a message to the queue and immediately returns control; a separate worker picks up the message and delivers it to the target system.

When RabbitMQ is Needed

  • The external system is periodically unavailable (maintenance, unstable connection).
  • Event volume is irregular: thousands per minute at peak, dozens under normal load.
  • Delivery guarantee is required: every message must be processed exactly once.
  • Multiple consumers must each receive a copy of the same event (fan-out).

Publishing Messages from 1C-Bitrix

For RabbitMQ integration from PHP, use the php-amqplib/php-amqplib library. Install via Composer in /local/:

cd /local && composer require php-amqplib/php-amqplib

Publisher class:

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

class RabbitMQPublisher {
    private static ?AMQPStreamConnection $connection = null;

    private static function getConnection(): AMQPStreamConnection {
        if (!self::$connection || !self::$connection->isConnected()) {
            self::$connection = new AMQPStreamConnection(
                COption::GetOptionString('site', 'rmq_host', 'localhost'),
                COption::GetOptionInt('site', 'rmq_port', 5672),
                COption::GetOptionString('site', 'rmq_user', 'guest'),
                COption::GetOptionString('site', 'rmq_pass', 'guest'),
                COption::GetOptionString('site', 'rmq_vhost', '/')
            );
        }
        return self::$connection;
    }

    public static function publish(string $exchange, string $routingKey, array $payload): void {
        $channel = self::getConnection()->channel();
        $channel->exchange_declare($exchange, 'topic', false, true, false);

        $msg = new AMQPMessage(
            json_encode($payload),
            ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT, // message survives broker restart
             'content_type'  => 'application/json']
        );
        $channel->basic_publish($msg, $exchange, $routingKey);
        $channel->close();
    }
}

Publishing on 1C-Bitrix Events

// In init.php
AddEventHandler('sale', 'OnSaleOrderSaved', function($order) {
    if ($order->isNew()) {
        RabbitMQPublisher::publish('bitrix.events', 'order.created', [
            'order_id'   => $order->getId(),
            'user_id'    => $order->getUserId(),
            'total'      => $order->getPrice(),
            'timestamp'  => time(),
        ]);
    }
});

AddEventHandler('catalog', 'OnAfterIBlockElementAdd', function($fields) {
    RabbitMQPublisher::publish('bitrix.events', 'product.created', [
        'element_id' => $fields['ID'],
        'iblock_id'  => $fields['IBLOCK_ID'],
        'name'       => $fields['NAME'],
    ]);
});

Consumer Worker

The worker is a separate PHP process (or multiple processes) managed by Supervisor:

// worker.php
require '/local/vendor/autoload.php';
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

$connection = new AMQPStreamConnection(/* connection params */);
$channel    = $connection->channel();
$channel->queue_declare('order.processor', false, true, false, false);
$channel->queue_bind('order.processor', 'bitrix.events', 'order.created');

$channel->basic_qos(null, 5, null); // at most 5 unacknowledged messages per worker
$channel->basic_consume('order.processor', '', false, false, false, false,
    function($msg) {
        $data = json_decode($msg->getBody(), true);
        try {
            OrderSyncHandler::process($data);
            $msg->ack(); // acknowledge successful processing
        } catch (\Throwable $e) {
            $msg->nack(false, true); // return to queue for retry
        }
    }
);

while ($channel->is_consuming()) {
    $channel->wait();
}

Supervisor config (/etc/supervisor/conf.d/bitrix_worker.conf):

[program:bitrix_order_worker]
command=php /var/www/bitrix.loc/local/workers/order_worker.php
numprocs=3
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/bitrix_worker.err.log

Dead Letter Queue

Messages that fail to process after N attempts are moved to a DLQ (Dead Letter Queue) for manual review. Configured at queue declaration time:

$channel->queue_declare('order.processor', false, true, false, false, false, [
    'x-dead-letter-exchange'    => ['S', 'bitrix.dlx'],
    'x-dead-letter-routing-key' => ['S', 'order.failed'],
    'x-message-ttl'             => ['I', 3600000], // 1-hour TTL
]);

DLQ monitoring is done via the RabbitMQ Management UI (port 15672) or via alerts triggered when the queue depth grows.

Setting up RabbitMQ for a single event type with a worker takes 1–2 working days, including Supervisor configuration and monitoring.