Skip to main content
This article covers the technical architecture of the Voyado Shopware integration. It is intended for developers implementing, extending, or debugging the integration.

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
Both are loaded with the 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 with data-* 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 typeKeyContentsCleared when
Local storagevoyado-cidVoyado contact IDNot automatically — persists across sessions
Local storageCart IDRandom identifier for the current cart journeyNot automatically
Local storageCart snapshotJSON of previous cart state for deduplicationOverwritten on each cart tracking call
Session storageSession IDUnique ID for the current browsing sessionBrowser tab closed; or when visitor navigates to login page
Cookievoyado-analyticsConsent state (1 = granted)Consent revoked
Nothing is written to any storage before the voyado-analytics cookie is set. The integration listens for two named browser events:
  • voyadoConsentGranted — sets voyado-analytics=1 and activates tracking
  • voyadoConsentRevoked — removes the cookie and stops all tracking
The Shopware built-in cookie banner fires these events automatically. Third-party CMPs must be configured to dispatch them. See Setting up for GTM and JavaScript examples.

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 calls POST /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 fileWhat it injects or enables
base.html.twigvoyado.js, CSS, and voyado-tracking-data element (shop ID, sales channel ID, language) on every page
product-tracking-script.twigServer-side preparation of product and stock data — no browser output
buy-widget-form.html.twigvoyado-product-tracking-buy-widget-form data element; back-in-stock form when product is out of stock
cart-widget.html.twigvoyado-cart-tracking element with serialised cart line items (promotion lines excluded)
action.html.twigProduct 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.twigPlaceholder element for the promotions widget in the mini-cart
login.html.twigLogin 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

TriggerBehaviour
Add to cart button click2-second delay, then reads cart contents from voyado-cart-tracking element
Cart page load (/checkout/cart)Fires immediately on page ready
Checkout confirm page loadFires immediately on page ready
Checkout finish page loadFires with empty product array — signals cart conversion

Product tracking payload fields

The voyado-product-tracking-buy-widget-form element carries the following data-* attributes, which are included in the product tracking request:
  • data-product-id — Shopware product ID
  • data-product-number — product SKU
  • data-page-url — URL of the product detail page
  • data-is-out-of-stock — boolean stock status
  • data-language — storefront language
  • data-sales-channel-id — Shopware sales channel ID
  • data-shop-id — Voyado shop identifier
The session ID and contact ID are added from browser storage at call time.

Web tracking data flow

Visitor accepts consent


voyado-analytics=1 cookie set


Contact identification
  ├── Login → Store contact ID in Local storage → voyado-cid stored
  └── Email link (encrypted parameter) → Decrypt parameter → Store contact ID in Local storage → voyado-cid stored


Product page visit
  └── POST api/v3/productviews (contact ID, product ID, SKU, URL, session ID)


Add to cart
  └── POST /api/v3/carts (contact ID, cart line items, cart ID)


Cart page / checkout confirm
  └── POST /api/v3/carts (updated cart snapshot)


Order placed (checkout finish)
  └── POST /api/v3/carts (empty product array — cart closed)

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 a product.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 availableStock from Shopware is > 0
Quantity changes that do not cross the zero boundary are ignored.

Webhook registration

All webhooks are registered automatically when the Voyado app is installed in Shopware. The inventory webhook is registered for the product.written event.

Sales channel and site matching

Each product visibility from Shopware includes a salesChannelId. The integration matches this against your configured site and channel mapping.
Back in stock must be enabled in the Accelerator configuration for each sales channel you want stock-level updates to flow through. Updates for channels where this is not enabled are silently dropped.

Step 2 — Scheduled push to Engage

A scheduled job polls for pending records and sends them to Engage using PUT /api/v3/inventory/stock-levels. Payload fields per record:
Quantity
integer
required
The current available stock quantity for the product.
Sku
string
required
The product number in Shopware.
ExternalId
string
required
The Sales Channel ID in Shopware.
Records remain in 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:
{
  "shop_id": "<shopware-shop-id>",
  "saleschannel_id": "<shopware-sales-channel-id>",
  "shopUrl": "<shopware-domain>",
  "email": "customer@example.com",
  "product_id": "<shopware-product-uuid>",
  "language_id": "<shopware-language-uuid>",
  "accept_email": "on"
}
On success, the API returns: "Thank you! We will notify you when the product is available."

Processing pipeline

1

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
2

Product and locale lookup

The API makes two calls to Shopware:
  • GET /api/product/{product_id} — the productNumber field becomes the Sku on the subscription
  • GET /api/language/{language_id}/locale — the locale code (for example, en-GB) becomes the Locale field
3

Contact resolution in Engage

The API resolves or creates a contact in Engage based on the submitted email address and marketing consent value:
ScenarioOutcome
Contact exists + acceptsEmail = trueExisting contact updated: acceptsEmail set to true
Contact does not exist + acceptsEmail = trueNew contact created with acceptsEmail = true
Contact does not exist + acceptsEmail = falseNew contact created with acceptsEmail = false
Contact exists + acceptsEmail = falseExisting contact unchanged — acceptsEmail preference is not overridden from true to false
4

Subscription creation in Engage

Once a valid contact ID is resolved and the locale lookup succeeds, the API posts the subscription to Engage:
POST /api/v3/inventory/backinstock/subscriptions
apikey: <your Engage API key>
Content-Type: application/json

{
  "ContactId": "<engage-contact-id>",
  "Sku": "<shopware-productNumber>",
  "Locale": "en-GB",
  "ExternalId": "<shopware-product-uuid>"
}
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.
5

Subscription creation in Engage

Once a valid contact ID is resolved and the locale lookup succeeds, the API posts the subscription to Engage:
POST /api/v3/inventory/backinstock/subscriptions
apikey: <your Engage API key>
Content-Type: application/json

{
  "contactId": "<engage-contact-id>",
  "sku": "<shopware-productNumber>",
  "locale": "en-GB",
  "externalId": "<shopware-product-uuid>"
}
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

ContactId
string
required
The Engage contact ID resolved from the customer’s email address.
Sku
string
required
The product number from Shopware (productNumber).
Locale
string
required
The locale code resolved from the Shopware language record, for example en-GB.
ExternalId
string
required
The Shopware product UUID.

Technical considerations

  • The voyado-cid in local storage persists across browser sessions with no automatic expiry. If a different contact logs in on the same browser, voyado-cid is 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.