External IDs
Every entity VoiceBot ingests is keyed by a stable external_id — the one identifier that ties a
synced catalog row to your live storefront. Your producer (Laravel / Node / Python) emits it, and the
bot passes it back to your host bridge when it acts (add_to_cart, select_variant, …). Producer
and bridge must agree on its shape, so it is a contract, not a free-form string.
Grammar
{producer}:{kind}:{id}[:{locale}]
producer— the source namespace. The Laravel package useslaravel; other producers use their own (wc, a custom prefix). It keeps ids from different producers from colliding inside one tenant.kind— the entity kind:product,category,variation,page, …id— the raw primary key from your store. The Laravel package wraps yourexternal_idcolumn automatically (laravel:product:84); with a customEntitySourceyou build it yourself.locale(optional suffix) — present only on a per-locale row in a multilingual catalog (laravel:product:84:ru).
laravel:product:84 # a product (base / canonical)
laravel:product:84:ru # that product's Russian locale row
laravel:category:12 # a category
laravel:variation:84-red-42 # a product variation
Base vs. translated rows
In a multilingual catalog one product becomes several ingested rows — a base (canonical) row plus one per extra locale. Each locale row carries:
- its own
external_id(often locale-suffixed, e.g.laravel:product:84:ru), and - a
translation_ofpointing at the base row'sexternal_id(laravel:product:84).
You set these with the lang and translation_of config keys — see
Mapping → Translations — or emit them from a custom
source.
Host-side canonicalization
Hosts key their cart and catalog by the canonical base id, never a per-locale one. So when the bot
acts from a non-default locale, the backend canonicalizes product_external_id /
variation_external_id back to the base (resolved through translation_of) before it dispatches
the action to your bridge.
That means your host bridge always receives the canonical base id — laravel:product:84, never
laravel:product:84:ru. Index your products and variants by the base id; you never strip a locale
suffix yourself. The step is a no-op for single-language stores, where translation_of is null.
Decode and match on the base form. If you mint a new producer prefix, use it consistently on both sides — the bot returns exactly what your producer emitted, after canonicalization.