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-tenantis 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):
- 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.
- Ga naar de instellingen van de plugin → Footer.
- Plak je snippet en sla op.
- 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:
- Uiterlijk → Thema-bestandseditor →
footer.php(gebruik een child-thema zodat een thema-update je wijziging niet overschrijft). - Plak het snippet vlak vóór
</body>. - Opslaan.
Twee WooCommerce-valkuilen (getest)
- "Coming soon"-modus staat standaard AAN op nieuwe WooCommerce-shops — je hele winkel (en dus de widget) zit dan achter een lanceerscherm. Zet hem uit via WooCommerce → Instellingen → Algemeen → Site-zichtbaarheid → "Live" (of klik Launch your store) voordat je test.
- Productafbeelding werkt zónder SEO-plugin. Vanilla WooCommerce zet
geen OpenGraph-tags, maar de widget pakt automatisch de volledige
productfoto uit de WooCommerce-gallery (geen Yoast/RankMath nodig). Heeft
je thema een afwijkende opbouw? Geef dan een
data-image-selectormee.
Shopify
- Online Store → Themes → … → Edit code.
- Open
layout/theme.liquid. - Plak je snippet vlak vóór
</body>. - 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):
- Content → Design → Configuration → bewerk je store view.
- HTML Head → Scripts and Style Sheets.
- Plak je snippet en sla op.
- 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>
- De pixel koppelt een aankoop anoniem aan een eerdere kamer-preview via een first-party token in de browser van de bezoeker — er worden geen bezoekers gevolgd en geen persoonsgegevens verstuurd.
- Heeft de bezoeker de widget nooit gebruikt? Dan is er geen token en wordt er niets verstuurd — alleen Veyra-aanrakingen tellen mee.
data-order-valueis optioneel — het orderbedrag in euro's (bv.129.95). Zonder bedrag telt de aankoop nog steeds mee, alleen zonder omzetcijfer. Per platform:- Shopify (checkout Additional Scripts / Order status-pagina, Liquid —
let op:
total_priceis in centen, deel door 100):data-order-value="{{ total_price | divided_by: 100.0 }}" - WooCommerce heeft géén template-variabelen op de bedankt-pagina; voeg
de pixel toe via een PHP-hook in je (child-)theme:
add_action('woocommerce_thankyou', function ($order_id) { $order = wc_get_order($order_id); printf('<script src="https://api.useveyra.ai/embed-purchase.js" data-tenant="jouw-shop" data-order-value="%s" defer></script>', esc_attr($order->get_total())); }); - Twijfel je? Laat
data-order-valuegewoon weg — de aankoop telt dan mee zonder bedrag, wat beter is dan een verkeerd bedrag.
- Shopify (checkout Additional Scripts / Order status-pagina, Liquid —
let op:
data-order-idis optioneel maar aan te raden: je ordernummer (bv.data-order-id="{{ order_number }}"). De pixel gebruikt het om dubbeltellen te voorkomen als de bedankt-pagina ververst wordt; zonder ordernummer valt hij terug op het pagina-adres.- Let op (Shopify en externe checkouts): draait je bedankt-pagina op een
ander domein dan je productpagina's (bv.
checkout.shopify.com), dan kan de pixel de eerdere kamer-preview niet terugvinden en telt er niets. Mail ons in dat geval — dan kijken we mee naar een passende oplossing. - Je dashboard toont vervolgens Aankopen, conversieratio en (indien meegegeven) omzet onder Conversie.
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).
- Maak een gratis account op https://openrouter.ai/keys
- Zet er wat tegoed op (± $5 is genoeg om te starten; een kamer-preview kost enkele centen)
- 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.