Architecture overview
The integration is a Shopware plugin that extends the storefront using Shopware’s Twig template inheritance system. It injects a JavaScript module (voyado.js) and structured data attributes into specific storefront pages. The JavaScript reads these attributes at runtime and sends tracking events to the Voyado Accelerator API via POST requests with JSON bodies.
Stock-level sync runs as a server-side pipeline separate from browser tracking. A product.written webhook triggers the Accelerator API, which detects restock events and pushes stock levels to Engage on a scheduled interval.
No Shopware core files are modified. All template changes are non-destructive Twig block overrides.
Script loading
Two scripts are loaded on every storefront page via the base template override:voyado.js— the Voyado integration script, served from the plugin bundle- Redeal widget script — a companion script for the on-site messaging add-on
defer attribute and do not block page rendering.
Data passing
Tracking data is passed from the Shopware PHP/Twig layer to the browser using hidden HTML elements withdata-* attributes. The JavaScript reads these attributes at runtime to construct API call payloads. This keeps Twig templates clean and decouples data preparation from behavioural logic.
Browser storage
| Storage type | Key | Contents | Cleared when |
|---|---|---|---|
| Local storage | voyado-cid | Voyado contact ID | Not automatically — persists across sessions |
| Local storage | Cart ID | Random identifier for the current cart journey | Not automatically |
| Local storage | Cart snapshot | JSON of previous cart state for deduplication | Overwritten on each cart tracking call |
| Session storage | Session ID | Unique ID for the current browsing session | Browser tab closed; or when visitor navigates to login page |
| Cookie | voyado-analytics | Consent state (1 = granted) | Consent revoked |
voyado-analytics cookie is set.
Cookie consent flow
The integration listens for two named browser events:voyadoConsentGranted— setsvoyado-analytics=1and activates trackingvoyadoConsentRevoked— removes the cookie and stops all tracking
Contact identification
Login-based: When a logged-in customer visits their account page, the integration reads their Shopware customer ID and email from the page and callsPOST /api/v1/Shopware/store-contactid. The returned Voyado contact ID is stored as voyado-cid in local storage.
Soft identification: Personalised email links include an eclub URL parameter containing an encrypted contact reference. On page load, the integration calls POST /api/v2/shopware/getSoftDecryptionKey to decrypt the parameter, then resolves the contact ID and stores it as voyado-cid.
Template overrides
| Template file | What it injects or enables |
|---|---|
base.html.twig | voyado.js, CSS, and voyado-tracking-data element (shop ID, sales channel ID, language) on every page |
product-tracking-script.twig | Server-side preparation of product and stock data — no browser output |
buy-widget-form.html.twig | voyado-product-tracking-buy-widget-form data element; back-in-stock form when product is out of stock |
cart-widget.html.twig | voyado-cart-tracking element with serialised cart line items (promotion lines excluded) |
action.html.twig | Product ID, product number, and minimum purchase quantity on listing page product cards |
index.html.twig (checkout/finish) | Completed order line items as a JSON data attribute for cart conversion signal |
offcanvas-cart.html.twig | Placeholder element for the promotions widget in the mini-cart |
login.html.twig | Login page URL marker used to trigger session reset |
Cart tracking
Deduplication
Before each cart tracking call, the integration serialises the current cart line items and compares them to the snapshot stored in local storage. If the payload is identical, the call is suppressed. The snapshot is updated after each successful call.Triggers
| Trigger | Behaviour |
|---|---|
| Add to cart button click | 2-second delay, then reads cart contents from voyado-cart-tracking element |
Cart page load (/checkout/cart) | Fires immediately on page ready |
| Checkout confirm page load | Fires immediately on page ready |
| Checkout finish page load | Fires with empty product array — signals cart conversion |
Product tracking payload fields
Thevoyado-product-tracking-buy-widget-form element carries the following data-* attributes, which are included in the product tracking request:
data-product-id— Shopware product IDdata-product-number— product SKUdata-page-url— URL of the product detail pagedata-is-out-of-stock— boolean stock statusdata-language— storefront languagedata-sales-channel-id— Shopware sales channel IDdata-shop-id— Voyado shop identifier
Web tracking data flow
Back-in-stock: stock-level sync
Stock-level sync runs as a two-step server-side pipeline, independent of browser tracking.Pipeline overview
Step 1 — Webhook ingestion
When a product record is written in Shopware, Shopware fires aproduct.written event and sends a webhook to the Accelerator API. The API validates the request using the shopware-shop-signature header — requests that fail validation are rejected before any processing.
After validation, the API fetches live product data and sales channel visibilities directly from Shopware using the product ID from the payload.
A pending stock update record is only created when both conditions are met:
- The product’s previously stored quantity was ≤ 0
- The product’s current
availableStockfrom Shopware is > 0
Webhook registration
All webhooks are registered automatically when the Voyado app is installed in Shopware. The inventory webhook is registered for theproduct.written event.
Sales channel and site matching
Each product visibility from Shopware includes asalesChannelId. The integration matches this against your configured site and channel mapping.
Step 2 — Scheduled push to Engage
A scheduled job polls for pending records and sends them to Engage usingPUT /api/v3/inventory/stock-levels.
Payload fields per record:
The current available stock quantity for the product.
The product number in Shopware.
The Sales Channel ID in Shopware.
Pending state until Engage returns a successful response. They are picked up on subsequent scheduled runs until delivery succeeds.
Back-in-stock: subscription flow
The subscription flow is independent of the stock-level sync pipeline. It records a customer’s opt-in to be notified when an out-of-stock product restocks, and writes that subscription to Engage.Storefront payload
On form submission, the storefront posts to the Accelerator API:"Thank you! We will notify you when the product is available."
Processing pipeline
Configuration and gate checks
The API resolves the site configuration using
shop_id and saleschannel_id. The call returns early with no subscription created if either of the following is not enabled:- Shopware integration enabled
- Back in stock feature enabled
Product and locale lookup
The API makes two calls to Shopware:
GET /api/product/{product_id}— theproductNumberfield becomes theSkuon the subscriptionGET /api/language/{language_id}/locale— the locale code (for example,en-GB) becomes theLocalefield
Contact resolution in Engage
The API resolves or creates a contact in Engage based on the submitted email address and marketing consent value:
| Scenario | Outcome |
|---|---|
Contact exists + acceptsEmail = true | Existing contact updated: acceptsEmail set to true |
Contact does not exist + acceptsEmail = true | New contact created with acceptsEmail = true |
Contact does not exist + acceptsEmail = false | New contact created with acceptsEmail = false |
Contact exists + acceptsEmail = false | Existing contact unchanged — acceptsEmail preference is not overridden from true to false |
Subscription creation in Engage
Once a valid contact ID is resolved and the locale lookup succeeds, the API posts the subscription to Engage:Engage returns
201 Created for a new subscription or 409 Conflict if the contact already has an active subscription for that SKU. Both responses are treated as success — 409 is the expected response on duplicate submissions.Subscription creation in Engage
Once a valid contact ID is resolved and the locale lookup succeeds, the API posts the subscription to Engage:Engage returns
201 Created for a new subscription or 409 Conflict if the contact already has an active subscription for that SKU. Both responses are treated as success — 409 is the expected response on duplicate submissions.Subscription payload fields
The Engage contact ID resolved from the customer’s email address.
The product number from Shopware (
productNumber).The locale code resolved from the Shopware language record, for example
en-GB.The Shopware product UUID.
Technical considerations
- The
voyado-cidin local storage persists across browser sessions with no automatic expiry. If a different contact logs in on the same browser,voyado-cidis overwritten only after the new contact’s ID is successfully resolved. The session ID is reset when the visitor navigates to the login page. - The 2-second delay on add-to-cart tracking is hardcoded to allow Shopware’s cart update to complete before the cart contents are read. This delay is not configurable.
- Listing page add-to-cart events use the product data from
action.html.twig, which does not include the product detail page URL — only the product ID, product number, and minimum purchase quantity. - Stock-level sync only activates for restock events (quantity crossing from ≤ 0 to > 0). Quantity changes that do not cross the zero threshold are not forwarded to Engage.