Docs Headless storefronts

Headless storefronts

Evident does not require a BigCommerce or Shopify theme. If your storefront is React, Next.js, Vue, Astro, or anything else that renders HTML, you can run galleries, FAQs and reviews on it with a script tag and an allow-listed origin.

10 min setup Any framework
01

Get your Store Environment ID

In Evident, go to Settings → Store. Copy the Store Environment ID. This is the only credential the storefront widgets need — there is no separate storefront token to request, and nothing secret to keep on a server. It identifies which store a request belongs to; what controls who may call the API is the origin allow-list in the next step.

02

Allow-list the origins you render from

Still under Settings → Store, add every origin your storefront runs on to Allowed Storefront Origins — production, staging, and http://localhost:3000 for local development. Changes take effect immediately; no redeploy on our side.

https://yourstore.com https://staging.yourstore.com http://localhost:3000

An origin is scheme + host + port only — no paths, no trailing slash. A bare domain is treated as https. Preview deployments are a common trip-up: every Vercel or Netlify preview URL is a different origin and will be blocked until you list it.

03

Load the SDK

Add the script once, anywhere in your app shell or root layout. It reads your Store Environment ID from the data attribute, pulls in its own stylesheet, and mounts every widget container it finds — including ones your framework renders later.

<script src="https://app.evidentugc.com/widgets/evident-sdk.min.js" data-store-env-id="YOUR_STORE_ENV_ID" ></script>
04

Place a widget container

Widgets mount into any element carrying a data-evident-widget attribute. A gallery needs only its slug, which you will find in Evident under Galleries:

<div data-evident-widget="gallery" data-gallery-slug="customer-photos" data-columns="3" ></div>

Galleries, FAQs and store-wide review widgets work immediately. Product-scoped widgets — star badges, per-product review lists, review forms — need your catalog in Evident first. See step 06.

05

React, Next.js, Vue and other SPAs

The SDK watches the DOM, so a container your framework renders after page load is mounted automatically — and if a re-render wipes it, it is re-mounted. If you would rather mount explicitly than rely on that, the SDK exposes an API. window.Evident may not exist yet on first paint, which is why the optional chaining below matters — the container still gets picked up when the script finishes loading.

'use client'; import { useEffect, useRef } from 'react'; export function EvidentGallery({ slug }) { const ref = useRef(null); useEffect(() => { const el = ref.current; window.Evident?.mount(el); return () => window.Evident?.unmount(el); }, [slug]); return ( <div ref={ref} data-evident-widget="gallery" data-gallery-slug={slug} /> ); }

Full API: refresh() re-scans and mounts anything blank, mount(el) mounts one container, unmount(el) tears one down, and init() is safe to call more than once — React StrictMode double-invokes effects in development.

06

Push your catalog (for product-scoped widgets)

Platform stores sync their catalog automatically. A headless store pushes it instead. Create an API key under Settings → API Keys, then upsert products — up to 250 per call, keyed on your own product ID, so re-sending the same payload is safe.

curl -X PUT https://api.evidentugc.com/api/v1/products \ -H "Authorization: Bearer evnt_YOUR_API_KEY" \ -H "x-store-env-id: YOUR_STORE_ENV_ID" \ -H "Content-Type: application/json" \ -d '{ "products": [ { "platformProductId": "sku-1024", "name": "Merino Wool Runner", "imageUrl": "https://cdn.example.com/mwr.jpg", "productUrl": "https://example.com/p/merino-wool-runner", "price": 98.00 } ] }'

This is a replace, not a merge: a field you omit is cleared, so send the whole product each time. Items are independent — one rejected row does not fail the batch, so check the failed count in the response rather than the status code alone. The platformProductId you send here is the value your widgets pass as data-product-id.

07

Push your orders (for review requests and verified purchase)

Orders are what connect a reviewer to a purchase. Push them and you get the verified-purchase badge plus automated review request emails. Push your catalog first — line items link to products by platformProductId, and verified-purchase matches on that link, so an order whose products are missing is stored but inert. The response tells you how many line items are still unlinked.

curl -X PUT https://api.evidentugc.com/api/v1/orders \ -H "Authorization: Bearer evnt_YOUR_API_KEY" \ -H "x-store-env-id: YOUR_STORE_ENV_ID" \ -H "Content-Type: application/json" \ -d '{ "scheduleReviewRequests": false, "orders": [ { "platformOrderId": "order-5581", "customerEmail": "[email protected]", "customerFirstName": "Dana", "status": "Shipped", "orderDate": "2026-08-01T14:22:00.000Z", "lineItems": [ { "platformProductId": "sku-1024", "quantity": 2, "price": 98.00 } ] } ] }'

scheduleReviewRequests defaults to false, which makes backfilling your order history safe — nothing is emailed. Turn it on only once you are pushing orders as they happen. Orders older than 30 days are never scheduled even with it on. Review requests also trigger on the statuses configured under Settings ("Shipped" by default), so the status you send decides eligibility.

What to know before you build

Headless support is not yet at parity with the platform integrations. These are the gaps worth knowing about up front rather than discovering mid-build.

Review requests are opt-in, and only for recent orders

Pushing an order never emails anyone unless you set scheduleReviewRequests. Even then, orders older than 30 days are skipped: the send delay is counted from when you push, not from the order date, so a backfilled order would otherwise be treated as if it shipped today.

Content Security Policy

If you enforce a CSP, allow app.evidentugc.com for script-src and style-src, and cdn.evidentugc.com for img-src. A report-only policy will log violations without blocking — the widget will stop rendering the moment you enforce it.

Script tag, not a package

The SDK is distributed as a script tag rather than an npm package today. It is framework-agnostic and needs no build step, but there is no typed React component to import — wrap it yourself as shown in step 05.

Troubleshooting

The widget renders nothing and the console shows a CORS error

The origin you are rendering from is not on the allow-list. Copy it exactly as the browser reports it — including the port — into Settings → Store → Allowed Storefront Origins. Note that curl cannot reproduce this: it sends no Origin header, so the request is never checked and always appears to succeed. Reproduce it with -H "Origin: https://yourstore.com".

A product widget returns 404

The product does not exist in Evident yet. Product-scoped widgets resolve through the platformProductId you push in step 06, and 404 until that product has been upserted. Galleries and FAQs do not depend on your catalog.

The widget disappears after navigating between pages

Make sure you are on the current SDK build — the script is served with must-revalidate, so a hard reload is enough to pick it up. If you mount explicitly, confirm your cleanup calls unmount(el) with the same element you passed to mount(el).

Building something custom?

Tell us what your storefront needs and we'll tell you honestly whether Evident covers it today.