Magento stores are large. Not WooCommerce "500 products with a few categories" large. Magento stores run 10,000+ SKUs, configurable products with dozens of variations, multiple store views for different languages, and Multi-Source Inventory across warehouses.
I built Emporiqa, a chat assistant for e-commerce stores. After shipping integrations for Drupal Commerce, WooCommerce, and Sylius, Magento was next. The catalog complexity made it the most interesting one to build.
Here's how the module works under the hood.
The Problem: Chat That Knows Nothing
Most chat solutions for Magento are JavaScript widgets that sit on the page and know nothing about your catalog. Customer asks "do you have running shoes under 80 euros in size 42?" The widget either sends them to the search page or gives a generic response.
To answer product questions, the chat assistant needs the actual catalog data: product names, descriptions, prices, stock levels, categories, images, and variation attributes. For Magento, it also needs to understand configurable product relationships and handle per-store-view translations.
Architecture: Observers → Queue → Webhooks
The module doesn't run language models inside Magento. That would tank admin performance. Instead, it sends product and page data to Emporiqa via webhooks, and the chat processing happens on separate infrastructure.
The flow:
-
Magento observer fires on
catalog_product_save_after,catalog_product_delete_before,cms_page_save_after, etc. - The observer formats the product into a consolidated payload (all store view translations in one event)
- The event is published to Magento's DB message queue (
emporiqa.webhook.consumer) - The queue consumer sends the webhook to Emporiqa with HMAC-SHA256 signature
The flow is non-blocking: the admin saves a product, the observer runs in milliseconds, and the actual HTTP request happens asynchronously via the queue consumer.
Configurable Products: Parent/Child Consolidation
This was the tricky part. Magento's configurable products have a parent that holds the name, description, and images, plus child simple products that hold the actual SKU, price, and stock.
The module syncs both:
Parent (configurable):
identification_number: "product-123"
sku: "JACKET-BLUE"
is_parent: true
variation_attributes: {"base": {"en-us": ["Color", "Size"], "de-de": ["Farbe", "Größe"]}}
names: {"base": {"en-us": "Trail Jacket", "de-de": "Wanderjacke"}}
Child (simple):
identification_number: "variation-124"
sku: "JACKET-BLUE-M"
is_parent: false
parent_sku: "JACKET-BLUE"
prices: {"base": [{"currency": "EUR", "current_price": 79.99}]}
stock_quantities: {"base": 42}
On the Emporiqa side, the assistant understands that "Trail Jacket" comes in multiple colors and sizes, with specific prices and stock per variation. A customer asking "do you have the trail jacket in medium?" gets an answer based on actual inventory.
Multi-Store-View Consolidation
Magento uses store views for languages. A product might have an English name in the default store view and a German name in the de_DE store view.
Rather than sending one webhook per store view (which would multiply requests by the number of languages), the module consolidates everything into one payload per product:
{
"names": {
"base": {
"en-us": "Summer Jacket",
"de-de": "Sommerjacke"
}
},
"descriptions": {
"base": {
"en-us": "Lightweight jacket for warm days",
"de-de": "Leichte Jacke für warme Tage"
}
}
}
The channel key "base" is the Magento website code. If you have multiple websites (e.g., base, b2b), products nest under each website's code as the channel key.
The ProductFormatter service handles this. It iterates over all enabled store views, loads the product in each store view's context, and merges the translations into one structure.
MSI: Salable Quantity vs Physical Stock
Magento's Multi-Source Inventory (MSI) tracks stock across multiple warehouses. Customers care about the salable quantity (what's actually available to sell after reservations), not the physical stock at any individual source.
The module uses MSI's GetProductSalableQtyInterface when available and falls back to StockRegistryInterface for stores not using MSI:
// Simplified: the module checks for MSI availability at runtime
try {
$salableQty = $this->getProductSalableQty->execute($sku, $stockId);
} catch (\Exception $e) {
// Fallback to legacy stock
$stockItem = $this->stockRegistry->getStockItemBySku($sku);
$salableQty = $stockItem->getQty();
}
This handles both Magento Open Source (which may not have MSI) and Adobe Commerce (which always does).
Message Queue: Why Not Direct HTTP
I could have sent webhooks directly from the observer. Drupal's integration does something similar with queue workers. But Magento's observer system runs inside the request lifecycle, and making HTTP calls during catalog_product_save_after would slow down admin operations.
Magento's DB message queue solves this. The observer publishes a message, the consumer processes it later. For large catalog imports (think: importing 5,000 products via CSV), the queue absorbs the load and the consumer works through them at its own pace.
The consumer is either started manually (bin/magento queue:consumers:start emporiqa.webhook.consumer) or runs via Magento's cron-based consumer processing.
Cart and Order Tracking
The module exposes REST endpoints at /rest/V1/emporiqa/cart/* for in-chat cart operations (add, remove, update, checkout URL). All use same-origin session cookies, no API tokens needed. The chat widget's EmporiqaCartHandler JavaScript calls these automatically.
For order tracking, a separate endpoint at /rest/V1/emporiqa/order/tracking accepts HMAC-signed requests from Emporiqa. The customer must verify their billing email before any order data is returned. Requests older than 5 minutes are rejected.
CLI Commands for Automation
Sync commands for deployment pipelines or scheduled jobs:
bin/magento emporiqa:sync:products # Full product sync
bin/magento emporiqa:sync:pages # Full CMS page sync
bin/magento emporiqa:sync:all # Both
bin/magento emporiqa:test-connection # Verify webhook connectivity
bin/magento emporiqa:sync:products --dry-run # Test without sending
bin/magento emporiqa:sync:products --batch-size=25
Each sync creates a session (sync.start → batched events → sync.complete). Items not seen during the session are marked as deleted on the Emporiqa side, so you don't need to manually delete stale products.
What Doesn't Work Well
Grouped and bundle products sync as standalone items without parent/child relationships. The module handles simple and configurable products with full parent/child consolidation. Grouped and bundle product types sync their component products individually, without the grouping structure.
The queue consumer needs babysitting. If emporiqa.webhook.consumer dies (OOM, timeout, server restart), webhook events pile up silently. Magento's cron-based consumer processing is the safer option for production, but it adds latency. There's no built-in alerting when the queue backs up.
CMS page sync is basic. Magento's CMS pages are flat content blocks. The module syncs title, content, and URL, but can't handle Page Builder layouts or widget directives. Stores using complex Page Builder pages get the raw content, which isn't always useful.
What I Learned
Three things stood out building this compared to the WooCommerce and Sylius integrations:
MSI detection is messy. You can't assume MSI is present. Magento Open Source installs may have it removed. The module needs graceful fallback at runtime, not compile time.
Configurable product observers fire for children too. When saving a configurable product, Magento also saves each child. Without deduplication, you'd send the same parent product multiple times. The queue handles this with a simple check before publishing.
Store view context switching is expensive. Loading a product in store view A, then store view B, then back to A involves Magento's store emulation. The formatter batches all store views together to minimize context switches.
Try It
The module is on the Adobe Commerce Marketplace and supports Magento 2.4.4+ (both Open Source and Adobe Commerce).
composer require emporiqa/module-chat-assistant
bin/magento module:enable Emporiqa_ChatAssistant
bin/magento setup:upgrade
bin/magento cache:flush
Start with a free sandbox store (100 products and 20 pages, no credit card). Sync some of your catalog, test with questions your customers ask, and see if the results make sense.
Full setup guide: emporiqa.com/docs/magento/. I also wrote about the full Magento integration on the Emporiqa blog.
Top comments (0)