Development of a product subscription module for 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

Product Subscription Module Development for 1C-Bitrix

"Notify me when back in stock" is a basic requirement for online stores where products are not always available. Standard Bitrix has no such mechanism. The task is often solved with a feedback form handled manually, which does not scale. A subscription module addresses this systematically: automatic queue placement, notifications when stock arrives, statistics on item demand.

Data Model

Module vendor.waitlist:

  • b_vendor_waitlist_subscription — subscriptions: id, user_id, email (for non-registered users), product_id, sku_id (trade offer), created_at, notified_at, status (active/notified/cancelled)
  • b_vendor_waitlist_notify_log — notification log: id, subscription_id, sent_at, result (sent/failed), error

Subscribing to a Specific SKU

When a customer clicks "Notify me when available", they enter their email or subscribe via their account:

class WaitlistService
{
    public function subscribe(int $productId, ?int $skuId, int $userId, string $email): SubscribeResult
    {
        // Check for an existing active subscription from this user/email for this product
        $existing = SubscriptionTable::getList([
            'filter' => [
                'PRODUCT_ID' => $productId,
                'SKU_ID'     => $skuId,
                'STATUS'     => 'active',
                '=EMAIL'     => $email,
            ],
        ])->fetch();

        if ($existing) {
            return SubscribeResult::alreadySubscribed();
        }

        SubscriptionTable::add([
            'USER_ID'    => $userId ?: null,
            'EMAIL'      => $email,
            'PRODUCT_ID' => $productId,
            'SKU_ID'     => $skuId,
            'STATUS'     => 'active',
        ]);

        return SubscribeResult::success();
    }
}

Trigger on Stock Arrival

When stock changes via the API or 1C synchronization, the OnProductStockChanged event fires (a custom module event triggered from the OnAfterIBlockElementUpdate handler):

AddEventHandler('iblock', 'OnAfterIBlockElementUpdate', ['\Vendor\Waitlist\StockWatcher', 'onElementUpdate']);

public static function onElementUpdate(array &$fields): void
{
    // Check: is this a trade offer? Did QUANTITY change?
    if (!isset($fields['PROPERTY_VALUES']['QUANTITY'])) return;

    $newQty = (int)$fields['PROPERTY_VALUES']['QUANTITY'];
    if ($newQty <= 0) return;

    // Find active subscriptions for this SKU
    $subscriptions = SubscriptionTable::getList([
        'filter' => ['SKU_ID' => $fields['ID'], 'STATUS' => 'active'],
    ])->fetchAll();

    foreach ($subscriptions as $sub) {
        NotifyQueueTable::add(['SUBSCRIPTION_ID' => $sub['ID']]);
    }
}

Notifications are sent from the queue by an agent — without blocking the element save process.

Notification Agent

public static function run(): string
{
    $queue = NotifyQueueTable::getList(['limit' => 100, 'filter' => ['STATUS' => 'pending']])->fetchAll();

    foreach ($queue as $item) {
        $sub = SubscriptionTable::getById($item['SUBSCRIPTION_ID'])->fetch();

        // Re-check stock: it may have sold out while the agent was sleeping
        $qty = \CIBlockElement::GetProperty($sub['SKU_ID'], false, 'QUANTITY', true);
        if ($qty <= 0) {
            NotifyQueueTable::delete($item['ID']);
            continue;
        }

        $result = \Bitrix\Main\Mail\Event::send([
            'EVENT_NAME' => 'WAITLIST_PRODUCT_AVAILABLE',
            'LID'        => SITE_ID,
            'C_FIELDS'   => [
                'EMAIL'       => $sub['EMAIL'],
                'PRODUCT_ID'  => $sub['PRODUCT_ID'],
                'PRODUCT_URL' => \CIBlockElement::GetDetailPageUrl($sub['PRODUCT_ID']),
            ],
        ]);

        SubscriptionTable::update($sub['ID'], ['STATUS' => 'notified', 'NOTIFIED_AT' => new DateTime()]);
    }

    return '\Vendor\Waitlist\NotifyAgent::run();';
}

Widget on the Product Card

The vendor:waitlist.button component is connected to the product card. It determines:

  • Whether the current user has an active subscription for this SKU
  • Displays the "Notify me when available" or "You are subscribed" button
  • For non-logged-in users — an email input form without registration

Statistics and Analytics

In the admin interface:

  • Top products by subscription count — understanding real demand
  • Conversion: how many users purchased after receiving a notification
  • Average time from subscription to notification
  • List of "eternal waiters" — subscriptions older than N days without a notification

Demand statistics are directly useful for the purchasing department.

Development Timeline

Stage Duration
ORM tables, subscription service 1 day
Stock change trigger 1 day
Notification queue and agent 1 day
Product card widget 1 day
Notification email template 0.5 days
Statistics and analytics 1 day
Admin interface 1 day
Testing 0.5 days

Total: 7 working days. Support for push notifications as an additional channel — +1 day.