Veyra widget — installatiegids

De Veyra-widget is één <script>-regel op je productpagina. Bezoekers uploaden een foto van hun kamer en zien jouw product erin geplaatst.

Je installatie-snippet staat in je dashboard (/dashboard/{jouw-shop}). Hij ziet er zo uit:

<script src="https://api.useveyra.ai/embed.js"
        data-tenant="jouw-shop"
        defer></script>

data-tenant is verplicht en uniek voor jouw shop — neem hem letterlijk over uit je dashboard. De rest van deze gids legt uit waar je die regel per platform plakt.

Vragen tijdens de installatie? Mail support@useveyra.ai — we reageren binnen één werkdag.


Eerst: zet je domein in het dashboard

Voordat het script iets doet, moet in je dashboard staan op welk domein je widget mag draaien: Instellingen → Toegestane domeinen. Vul daar het adres in dat in de adresbalk van je shop staat, bijvoorbeeld www.jouwwinkel.nl, één domein per regel.

Staat er geen domein, dan weigert de widget elke bezoeker — je ziet dan wel de knop op je pagina, maar er gebeurt niets. Heb je een aparte staging-omgeving of een tweede domein? Zet ze er allebei in.


Waar komt het script?

De widget hoort op de productpagina, vlak vóór de afsluitende </body>-tag. De widget leest zelf het productbeeld en de titel van de pagina (via de standaard OpenGraph-tags die de meeste webshops al hebben), dus je hoeft niets anders te configureren.


WordPress / WooCommerce

Getest op een schone WordPress + WooCommerce + Storefront-installatie — deze stappen kloppen, inclusief de twee valkuilen onderaan.

Optie A — via een plugin (aanbevolen, geen code):

  1. Installeer en activeer een "header & footer scripts"-plugin — WPCode of Insert Headers and Footers (gratis, 1 klik vanuit Plugins → Nieuwe plugin). WordPress zelf heeft geen veld om een script te plakken; vandaar deze plugin.
  2. Ga naar de instellingen van de plugin → Footer.
  3. Plak je snippet en sla op.
  4. Open een productpagina — de knop "Bekijk in je eigen kamer" verschijnt onder In winkelwagen.

WooCommerce-tip — plaats de knop netjes bij In winkelwagen. Voeg data-mount=".single_add_to_cart_button" toe aan het script, anders belandt de knop onderaan de pagina:

<script src="https://api.useveyra.ai/embed.js"
        data-tenant="jouw-shop"
        data-mount=".single_add_to_cart_button"
        defer></script>

Optie B — via het thema:

  1. Uiterlijk → Thema-bestandseditor → footer.php (gebruik een child-thema zodat een thema-update je wijziging niet overschrijft).
  2. Plak het snippet vlak vóór </body>.
  3. Opslaan.

Twee WooCommerce-valkuilen (getest)


Shopify

  1. Online Store → Themes → … → Edit code.
  2. Open layout/theme.liquid.
  3. Plak je snippet vlak vóór </body>.
  4. Save.

De widget verschijnt op elke productpagina. Wil je hem alleen op specifieke producten? Mail ons — we helpen met een conditie.


Magento (Adobe Commerce)

Via de admin (geen deploy nodig):

  1. Content → Design → Configuration → bewerk je store view.
  2. HTML Head → Scripts and Style Sheets.
  3. Plak je snippet en sla op.
  4. Cache leegmaken (System → Cache Management → Flush).

Custom HTML / overig platform

Plak het snippet in de HTML van je productpagina, vlak vóór </body>:

    <!-- … je pagina-inhoud … -->

    <script src="https://api.useveyra.ai/embed.js"
            data-tenant="jouw-shop"
            defer></script>
  </body>
</html>

Werkt op elk platform dat een <script>-tag toelaat. Geen build-stap nodig.


Conversie meten (optioneel — tweede snippet)

Wil je zien hoeveel bezoekers daadwerkelijk een product kopen nadat ze het in hun eigen kamer zagen? Plak één extra regel op je bestelbevestigings- / bedankt-pagina (de pagina ná een geslaagde bestelling):

<script src="https://api.useveyra.ai/embed-purchase.js"
        data-tenant="jouw-shop"
        data-order-value="129.95"
        defer></script>

Het bedrag is een indicatief signaal voor je eigen dashboard — het is geen factuurregel en wordt niet voor facturatie gebruikt.


Je eigen AI-key aanleveren

Veyra draait op jouw eigen OpenRouter-key. Dat betekent dat OpenRouter het AI-verbruik rechtstreeks aan jou factureert — transparant, en je houdt volledige controle over je spend-limieten. Wij zien je key nooit in leesbare vorm (versleuteld opgeslagen).

  1. Maak een gratis account op https://openrouter.ai/keys
  2. Zet er wat tegoed op (± $5 is genoeg om te starten; een kamer-preview kost enkele centen)
  3. Klik "Create key" en plak hem zelf in je dashboard onder Instellingen → AI-key — klik daarna "Test mijn key" om direct te controleren dat hij werkt (kost geen tegoed).

Liever dat je financiële persoon dit regelt? Prima — nodig ze uit als extra gebruiker via Instellingen → Gebruikers; de key kan los van de installatie worden ingesteld. De widget werkt zodra beide er zijn.


Aanpassen (optioneel)

Extra data-*-attributen op het script-tag:

Attribuut Wat het doet
data-button-label="Pas toe in mijn kamer" Andere tekst op de knop
data-mount=".product-gallery" CSS-selector waar de knop ná geplaatst wordt
data-image-selector=".product__image img" CSS-selector voor het PRIMAIRE productbeeld als je pagina geen OpenGraph-tags heeft
data-images-selector=".product-gallery img" CSS-selector die MEERDERE productbeelden matcht — extra referentiebeelden van hetzélfde product (staal + sfeerfoto + detail) worden als extra context meegestuurd (max 3 extra)
data-product-type="wall_paint" Type product — furniture (standaard), wall_paint, wall_wallpaper of window_covering (gordijnen/vitrage/jaloezieën — een tik kiest wélk raam). Bepaalt of de AI een object plaatst, een muurvlak herbekleedt of een raam aankleedt
data-material="RAL 7016 mat antraciet" Expliciete materiaal-/kleuromschrijving (voor wall_paint/wall_wallpaper/window_covering) — woordelijk doorgegeven aan het AI-model zodat de exacte kleur/het exacte patroon wordt gereproduceerd in plaats van geschat
data-allow-stacking Alleen window_covering: bezoekers kunnen open/dicht wisselen (bijv. gordijnen open of gesloten renderen)
data-product-ref="havana" De product-slug uit je Veyra-dashboard (Producten). Vereist voor de configurator: de server valideert de gekozen opties tegen het daar opgeslagen schema
data-options='[…]' Het configurator-schema als JSON (te kopiëren uit je dashboard bij het product). Toont een "stel samen"-stap in de widget vóór de foto-upload; keuzes sturen de AI-weergave. Altijd samen met data-product-ref, per productpagina getemplatet — net als data-material
data-pattern-repeat-cm="64" Alleen voor behang/wandtextiel: de patroonrapport (herhaalmaat) in cm. Verankert de schaal van het motief op de echte maat in plaats van die te gokken uit een referentiebeeld
data-bundle-products='[{"image":"…"},…]' "Shop the look" — los geprijsde uitbreiding, alleen na afspraak. Een JSON-array van 2+ losse producten die samen in één kamer worden gecomponeerd. Elk item: {image, images?, type?, material?, dimensions?} (image = primaire URL verplicht; dimensions = vrije-tekst echte maat als schaalanker, bv. "B 210 × D 95 × H 85 cm"). Max 4 producten; ongeldige JSON wordt genegeerd en de widget valt terug op normaal enkel-product-gedrag. Modulaire banken: draagt élk item een role (corner_left, corner_right, middle, arm_left, arm_right, longchair_left, longchair_right, hocker) plus een dimensions als "W 98 x D 223 cm", dan zijn het de elementen van ÉÉN bank: de AI bouwt ze eerst samen tot één product en plaatst dat daarna in de kamer — verplaatsen en opnieuw genereren werken dan gewoon, een aantal-keuze niet. Max 8 elementen; links/rechts zijn van voren gezien. Optioneel per element photo_frame: "front" (de foto toont het element zoals het in de bank staat) of "inside" (een hoekelement gefotografeerd vanaf zijn eigen voorkant, arm aan de zijkant van de foto); weggelaten = hoeken inside, de rest front
data-dimensions="W 320 x D 223 cm" De echte maat van het product als vrije tekst — het schaalanker voor een enkel meubel. Bij een modulaire bank de totaalmaat van de samenstelling, die onder het resultaat wordt getoond
data-register="u" Aanspreekvorm van alle widget-teksten: je (standaard) of u (formeel — "Upload een foto van uw kamer")
data-result-label="Vraag een offerte aan" Tekst van de primaire knop onder het resultaat, in plaats van "Verder winkelen" — voor shops die op offerte verkopen. Met data-result-url wordt het een link (nieuw tabblad); zonder sluit de knop het venster en krijgt je pagina de gekozen opties via het veyra:cta-event (zie hieronder)
data-result-url="https://jouwwebshop.nl/offerte" Het http(s)-adres achter data-result-label. Opties die de bezoeker in de widget koos gaan mee als parameters: …/offerte?kleur=wit&zijsteen=natuursteen (de sleutels uit je productopties)
data-actions="share" Welke extra knoppen onder het resultaat staan, kommagescheiden: share (delen via WhatsApp, mail…), download, other (andere kamerfoto proberen). Afwezig = alle drie. Kies bij voorkeur één van delen of downloaden: op de telefoon kan de bezoeker vanuit het deelmenu ook opslaan. Op een apparaat zonder deelmenu (sommige desktops) wordt share automatisch een downloadknop
data-tools="configure" Welke aanpasknoppen onder het resultaat staan, kommagescheiden: regen (opnieuw genereren), repos (het product verplaatsen), configure (de gekozen opties aanpassen). Afwezig = alle die van toepassing zijn. Bijvoorbeeld configure voor een gordijnenwinkel die alleen het configureren wil aanbieden
data-stacking-default="open" Alleen met data-allow-stacking: het eerste resultaat toont het gordijn open (helemaal naar de zijkanten) of closed (dicht). De keuze onder het resultaat is dan alleen Dicht / Open. Afwezig = de AI volgt wat de kamerfoto laat zien, met "Zoals nu" als extra keuze
data-scenes="fire,daynight,door" Sfeerknoppen onder het resultaat, kommagescheiden: fire (Kachel aan / uit), daynight (Dag / Avond) en door (Kacheldeur: Dicht / Open). Elke keuze maakt een nieuwe weergave met dezelfde kamerfoto, plaatsing en overige keuzes; de knoppen staan altijd in die volgorde. daynight werkt voor elk product: avond = dezelfde kamer vanuit hetzelfde standpunt, schemer buiten, verlicht door de eigen lampen in de foto. fire en door alleen voor kachels (data-product-kind="stove", of other): uit = koude kachel zonder vlammen of gloed, deur open = de eigen deur van de kachel open zoals het ontwerp toelaat. Tot de bezoeker kiest wordt er niets meegestuurd: de eerste weergave volgt de kamerfoto (meestal dag) en de productfoto (brandend vuur, deur dicht), en die knoppen staan dan aan. Afwezig = geen sfeerknoppen
data-launcher="floating" Hoe de knop op de pagina staat: floating (altijd zwevend in beeld, ook met data-mount — voor sfeerpagina's) of inline (nooit zwevend, op de plek van data-mount). Afwezig = zwevend alleen als er geen data-mount is
data-launcher-shape="vertical" Vorm van de knop: pill (standaard), vertical (een verticale tab tegen de rand van het scherm) of dot (een ronde icoonknop; de knoptekst wordt de toegankelijke naam en tooltip)
data-launcher-position="right" Alleen voor een zwevende knop: bottom-right (standaard), bottom-left of right (rechts, verticaal gecentreerd)
data-mark="off" Optioneel: de knop zonder Veyra-merkteken, alleen het label (handig als uw eigen knoppen geen logo dragen; combineer met data-byline="off", data-launcher-size="compact" en de data-theme-* waarden). Geldt niet voor de ronde icoonknop (data-launcher-shape="dot"), want daar ís het merkteken het icoon
data-remember="off" Optioneel: schakelt het sessiegeheugen uit. Standaard bewaart de widget de kamerfoto van de bezoeker en het laatste resultaat in het sessionStorage van dít tabblad (nooit in localStorage, nooit op de server), hooguit een uur en te wissen door de bezoeker zelf, zodat het wisselen van product of pagina niet opnieuw om een foto vraagt. Zet op off als uw privacybeleid geen foto in de browser van de bezoeker toestaat
data-product-kind="dining_seating" Optioneel, alleen voor meubels: wat het product ís. Waarden: dining_seating (eetkamerstoel), lounge_seating (fauteuil, loungestoel, bank), table, storage, lighting, rug, stove (kachel, haard), other. Stuurt waar een set terechtkomt (eetkamerstoelen rond de tafel, fauteuils als zitgroep) en zorgt dat de AI alleen een vergelijkbaar stuk vervangt (een eetkamerstoel wisselt nooit een fauteuil uit). Zonder attribuut leest de widget de soort uit de JSON-LD category of uit de producttitel; lukt dat niet, dan beoordeelt de AI het zelf
data-allow-quantity (zonder waarde) Alleen voor meubels die als set komen (eetkamerstoelen, fauteuils): toont ná het eerste resultaat een keuze "hoeveel wil je er zien?" (1×/2×/4×/6×). De AI rendert dan exact dat aantal identieke kopieën als één bij elkaar passende set: eetkamerstoelen rond de eettafel, fauteuils als zitgroep rond de bank (zie data-product-kind) — in plaats van willekeurig verspreid. Zet dit niet op een bank of ander enkelstuk. Afwezig = één exemplaar (standaard)

Voorbeeld:

<script src="https://api.useveyra.ai/embed.js"
        data-tenant="jouw-shop"
        data-button-label="Pas toe in mijn kamer"
        data-mount=".product-summary"
        defer></script>

Gekozen opties overnemen in je formulier of winkelwagen

Kiest de bezoeker in de widget opties (kleur, zijsteen, leer…), dan laat de widget dat je pagina weten, zodat je offerteformulier of winkelwagen dezelfde keuze krijgt in plaats van wat er achter het venster nog geselecteerd staat:

window.addEventListener('veyra:cta', function (e) {
  // e.detail.options    → { kleur: 'wit', zijsteen: 'natuursteen' }
  // e.detail.selections → [{ key: 'kleur', label: 'Kleur', value: 'wit', valueLabel: 'Wit' }, …]
  // e.detail.productRef → het product uit data-product-ref
});

veyra:cta vuurt bij een klik op de primaire knop; veyra:result (zelfde inhoud) zodra een resultaat in beeld staat.

Standaardkeuze, weergave en linkparameter per optie

Elk optie-veld in het configurator-schema (dashboard → Producten → product → "Weergave in de widget") kent drie optionele instellingen. Laat je ze leeg, dan werkt het veld precies zoals voorheen.

Sleutel Waarden Wat het doet
default een keuze-waarde van dat veld (alleen keuzeknoppen) Heeft de bezoeker voor dit veld niets gekozen en is het veld zichtbaar (de zichtbaar-als-regel klopt met de gekozen opties plus de andere standaardkeuzes), dan past de server deze keuze toe alsof de bezoeker hem koos: de AI-instructie, een eventuele referentie-afbeelding en de maatbeperkingen gaan mee. Opgeslagen wordt alleen wat de bezoeker zelf koos; de toegepaste standaardkeuzes volgen uit het schema en staan in de prompt van de weergave.
display primary, advanced of hidden Hoe de widget het veld toont: als hoofdkeuze op het eerste scherm, onder de geavanceerde instellingen (⋯), of helemaal niet. Een verborgen veld kan nog steeds een waarde krijgen (bijv. breedte en hoogte die de widget uit de foto invult).
param kleine letters, cijfers en _, begint met een letter, max. 41 tekens De naam waaronder de widget de keuze meegeeft in de link naar je eigen productpagina of prijsberekening (bijv. curtain_type). Leeg = de sleutel van het veld.

In de widget

Heeft precies één veld display: primary (keuzeknoppen), dan ziet de bezoeker na de upload alleen die vraag, als plaatjes; één tik en het beeld wordt gemaakt. De AI meet ondertussen het raam op: verborgen maatvelden (hidden met prefill) krijgen die maat en gaan mee in de link, bv. …/gordijnen/havana?pv_id=123&curtain_type=vouw&curtain_lining=zonder&curtain_width=240&curtain_height=260. Parameters die al in data-result-url staan blijven staan. Het veyra:cta-event krijgt dezelfde set als e.detail.params.

Meerdere productbeelden (JSON-LD)

Levert je pagina schema.org Product structured data (<script type="application/ld+json">), dan leest de widget die als eerste: het image-veld mag één URL of een array zijn. De eerste afbeelding wordt het primaire productbeeld; de rest gaat als extra referentiebeelden mee (max 3 extra, totaal 4). Zo krijgt het AI-model bij een muurstaal bijvoorbeeld óók een sfeerfoto voor de juiste schaal en materiaaluitstraling.

Heb je geen JSON-LD maar wél een fotogalerij in de DOM? Gebruik dan data-images-selector om die beelden aan te wijzen. Eén productbeeld (alleen og:image) blijft gewoon werken zoals voorheen — er verandert niets aan bestaande installaties.


Werkt het niet?

Symptoom Oorzaak / oplossing
Geen knop op de pagina Staat het script écht op de productpagina, vóór </body>? Klopt data-tenant exact met je dashboard?
Knop verschijnt, maar er gebeurt niets Meestal staat je domein niet in Instellingen → Toegestane domeinen — zonder domein bedient de widget niemand. Controleer of het adres daar exact klopt met de adresbalk van je shop (www. telt mee als je shop dat gebruikt).
"Geen productafbeelding gevonden" Je pagina heeft geen og:image. Geef een data-image-selector op (zie hierboven) of mail ons de pagina-URL.
Knop doet niets Open de browser-console (F12) en kijk of er een [veyra]-melding staat; stuur die naar support.
Foutmelding na upload Meestal is je AI-key ongeldig of het tegoed op. Klik "Test mijn key" in je dashboard onder Instellingen → AI-key — dat controleert de key direct bij OpenRouter (het status-lampje "AI-koppeling" checkt alleen óf er een key is, niet of hij werkt).
"Kan de Veyra-dienst niet bereiken" Je site heeft een strikte Content-Security-Policy die de widget blokkeert. Zie hieronder.

Content-Security-Policy (CSP)

Heeft je shop een Content-Security-Policy (vaak bij enterprise- of bank-omgevingen), dan moet je de Veyra-domeinen toestaan — anders blokkeert de browser de aanroep en toont de widget "Kan de Veyra-dienst niet bereiken". Voeg toe:

connect-src https://api.useveyra.ai https://app.useveyra.ai;  # compose-aanroep + beacon + download van het resultaat
img-src     https://*.useveyra.ai blob:;                      # het gegenereerde beeld + de voorvertoning van de kamerfoto

Let op beide details: het gegenereerde beeld wordt geserveerd vanaf app.useveyra.ai (vandaar de tweede connect-src-host, nodig voor de download-knop), en de widget toont de kamerfoto van de bezoeker lokaal via een blob:-URL (vandaar blob: in img-src).

Gebruik je een strikte style-src zónder 'unsafe-inline'? De widget rendert in een geïsoleerde Shadow-DOM met een eigen <style>-blok; sta in dat geval 'unsafe-inline' toe vóór de Veyra-stijlen, of mail ons — dan denken we mee over een oplossing voor jouw policy.

Single-page apps (React / Vue / Next / Hydrogen)

Wisselt je productpagina van product zonder volledige herlaad (client-side navigatie), roep dan na elke navigatie aan:

window.veyra.updateProduct();   // herleest titel/prijs/afbeelding van de nieuwe pagina

De widget leest het product opnieuw bij elke klik op de knop, dus meestal gaat dit vanzelf goed — updateProduct() is de zekere weg. Het herleest ook de per-product attributen op de script-tag (data-bundle-products, data-dimensions, data-product-type, data-material, data-options, data-register, data-result-label…), dus een configurator die de manifest herschrijft roept daarna gewoon updateProduct() aan. Let op: als je framework bij een routewissel het stuk pagina verwijdert waar de Veyra-knop in staat, verdwijnt de knop mee; plaats de knop dan via data-mount op een element dat blijft staan, of open het venster programmatisch met window.veyra.open() vanaf je eigen knop.

Kom je er niet uit? support@useveyra.ai — wij installeren hem desnoods samen via een screenshare.


Controleren wat de widget ziet

Open je eigen productpagina met #veyra-inspect achter de URL, bijvoorbeeld https://www.jouwwinkel.nl/product/fauteuil#veyra-inspect. Linksonder verschijnt een paneel met exact wat de widget van je pagina leest: de productafbeelding(en), titel en via welke route ze gevonden zijn (JSON-LD, og:image of selector). De eerste afbeelding is wat de AI in de kamer plaatst — klopt die niet, pas dan je og:image of JSON-LD aan en herlaad. Bezoekers zien dit paneel nooit; het verschijnt alleen met die URL-hash.