⚙️ Custom site — full integration guide

Everything you need to connect the 3D configurator to a hand-built site (no Shopify/WooCommerce/PrestaShop plugin). Every attribute and field below matches the real widget code.

The 3 building blocks

  1. embed.js — a loader you drop on your page. It reads your data-* attributes and injects an iframe (you host nothing).
  2. The /e/{key} iframe — the configurator itself, in your brand (tenant logo & colors).
  3. config_id — the id of the customization, returned on add-to-cart. It is the thread that links the customer's design to the production order on the PrintWearAI side. Always keep it.

Two ways to open the configurator

A. Straight on a product

Pass data-product="slug" (or data-sku) → opens directly on that model.

B. On the product picker (choice screen)

Pass no product → the iframe shows your branded header, category tabs (per sport, shown when there are ≥2 categories) and a product grid. Clicking a card opens the configurator on that product. This is the “generic button” entry — a single Design yours button that opens the choice panels.

Step 1 — Get your key

Tenant workspace → Integration (/app/embed.html) → Public key (Embed Key) = fk_…. It is public — safe to expose in HTML.

Step 2 — Container + loader

<div id="pwai-configurator"></div>

<script src="https://printwearai.com/embed.js"
        data-key="fk_YOUR_KEY"
        data-autodetect="false"></script>

data-autodetect="false" + no data-product → the widget mounts the choice screen. (With auto-detect on, it would look for a SKU in your page — useless on a custom site.)

Step 3 — Wire your cart (required on custom sites)

On a custom site there is no automatic add-to-cart (that is built in only for Shopify/Woo/Presta). Without a handler the widget just logs a warning. Register your own:

PwaiEmbed.onAddToCart(function (m) {
  myCart.add({
    name:           m.product.name,
    quantity:       m.quantity,
    sizes:          m.sizes,                   // { "M": 3, "L": 2, ... }
    preview:        m.customization.thumbnail, // finished jersey image URL
    pwai_config_id: m.config_id                // ⚠️ keep it → link to production
  });
  // handle your own payment / redirect
});

Step 4 — A generic button that opens the choice screen

Most robust: a button that opens the /e/{key} iframe in a modal. With no ?product=, it opens on the catalog.

<button id="btn-create">🎨 Design yours</button>

<div id="pwai-modal" style="display:none;position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.6)">
  <div style="max-width:1100px;height:90vh;margin:5vh auto;background:#fff;border-radius:16px;overflow:hidden;position:relative">
    <button onclick="document.getElementById('pwai-modal').style.display='none'"
            style="position:absolute;top:10px;right:14px;z-index:2;border:0;background:#0002;border-radius:50%;width:32px;height:32px;cursor:pointer">✕</button>
    <iframe id="pwai-frame" style="width:100%;height:100%;border:0"></iframe>
  </div>
</div>

<script>
  document.getElementById('btn-create').addEventListener('click', function () {
    // No ?product= → the iframe opens the catalog (sport tabs + grid)
    document.getElementById('pwai-frame').src = 'https://printwearai.com/e/fk_YOUR_KEY';
    document.getElementById('pwai-modal').style.display = 'block';
  });
</script>
Widget-native variant: with embed.js loaded, PwaiEmbed.mount('#container') with no options opens the same choice screen; PwaiEmbed.mount('#container', { product_slug: 'x' }) opens a specific product. Advantage: your onAddToCart handler receives the event directly (with a hand-built iframe you'd have to listen to window.message yourself).

Targeted buttons alongside, if you want (direct product):

https://printwearai.com/e/fk_YOUR_KEY?product=home-jersey

Step 5 — What flows back to PrintWearAI

The customer designs → the config is saved → the order lands in your workspace → Orders, with the production-ready SVG pattern export. You only handled your own cart/checkout.

Server-side exports: order data & print proof (BAT)

The onAddToCart message is ephemeral — for your backend, use the tokenised exports:

NeedHow
Full order data (items, sizes, per-item texts/numbers/colors, buyer, totals)GET /api/orders/{id}/host-export?token={order_token} — the URL arrives in the order.created webhook as export_url. Add &include=patron for the production SVG inline.
Full saved design data (config, preview, summary)GET /api/embed/saved-design/{share_token} — from the design.saved webhook or the onDesignSaved callback.
Printable proof (bon à tirer) to approve before productionbat_url in both payloads → /bat.html?design={token} or /bat.html?order={id}&token={order_token} — print-ready page with preview, texts, colors and a signature block.
Raw pushed feed (no pull needed)Set your webhook to full payload mode (Integration → Webhooks): order.created and design.saved then carry the complete raw data — config JSON, production pattern SVG, previews. The add_to_cart message also ships m.bat.zones (named zones + Pantone) and the design's export_url/bat_url (auto guest-save).
Design saved without an order (lead)PwaiEmbed.onDesignSaved(fn) client-side + design.saved webhook server-side (includes guest_email when provided).

Reference — data-* attributes

AttributeDefaultRole
data-key—Required. Public key fk_…
data-product—Product slug → direct open. Absent → choice screen
data-sku—External SKU (resolved against the product's SKU / reference field)
data-autodetecttrue"false" to disable SKU auto-detection (recommended on custom sites)
data-target#pwai-configuratorCSS selector of the host container
data-height700Iframe height in px (auto-grows afterwards)
data-localeautofr / en / es / de / it
data-config—Reopen a saved configuration by its id
data-no-consentfalse"true" if you already handle cookie consent
data-back-url—Adds a Back button in the configurator top bar linking to this URL (overrides the back-office setting in App → Integration)
data-back-label← RetourLabel of the back/close button
data-customer-token—SSO for AI Design. Your backend signs a token identifying YOUR logged-in customer — the widget unlocks AI generation for them (paid credits) without a separate widget account. See below.

SSO — unlock AI Design for your logged-in customers

The AI Design feature requires an authenticated customer; every generation uses paid credits. If your site already has customer accounts, sign a token server-side with your secret API key (App → Integration → API) and pass it via data-customer-token:

// PHP (server-side only — NEVER expose your secret key in HTML)
$payload = base64_encode(json_encode([
  'external_id' => $customer->id,
  'email'       => $customer->email,
  'name'        => $customer->name,
  'exp'         => time() + 3600,          // valid 1 h
]));
$payload = rtrim(strtr($payload, '+/', '-_'), '=');   // base64url
$token   = $payload . '.' . hash_hmac('sha256', $payload, $API_SECRET);
// then: <script src=".../embed.js" data-key="fk_…" data-customer-token="<?= $token ?>">

Guests (not logged in on your site) are invited to create an account in the widget instead.

Without data-back-url (and no URL set in the back office), the button acts as Close: your page receives PwaiEmbed.onClose(fn) — hide the widget, close your modal, etc.

Reference — onAddToCart(m) payload

FieldContent
m.config_idSaved configuration id (keep it)
m.product.nameProduct name
m.product.flare_idInternal PrintWearAI id
m.quantityQuantity
m.sizesSize breakdown { "M": 3, "L": 2 }
m.customization.thumbnailPreview image URL
m.skuSKU if set

SKU resolution (if you use data-sku)

data-sku="JERSEY-001" triggers a call to /api/resolve, which looks up a product whose SKU / reference field equals JERSEY-001. The SKU must therefore be identical on both sides. Set it in Tenant workspace → Products → the product → SKU / reference, either manually or automatically via Integrations. Shortcut: use data-product="slug" instead — no SKU to sync.

REST API

For a server-side coupling (read a config back, create/track orders from your backend) use your secret key sk_… (≠ public key): endpoints /resolve, /configs, /orders. See Custom site (API).

Troubleshooting

← Embed widget Custom site (API) →