Oyster Widget Developer Docs
Integrate the Oyster (Oysterskin) widget by loading one UMD script and creating a widget instance. Use the snippets below as-is, then customize options and handle events in your callback.
What you ship
One script tag plus a small init call. The widget opens as an embedded overlay and can be opened, minimized, or closed.
Two display modes
Float shows a floating launcher automatically. Inline lets you control when to call open().
Events
Your callback receives checkout, minimized, and closed events.
Overview
The Oyster widget is embedded by loading a UMD bundle that exposes a global OysterskinWidget. You create a widget instance using one of the factory methods:
- OysterskinWidget.createScanWidget(options)
- OysterskinWidget.createChatWidget(options)
- OysterskinWidget.createUnifiedWidget(options)
- OysterskinWidget.createConsultationWidget(options)
Each factory returns an instance with open(), minimize(), and close().
Permissions: the widget is embedded using an iframe with allow="camera *; geolocation *" (this is not configurable by the integrator). If camera access is needed, your site must be served over HTTPS (browsers block camera on insecure origins).
Quickstart
Replace placeholders like YOUR_VENDOR_KEY and YOUR_END_USER_ID. Your callback is required and receives widget events.
Minimal standalone examples: float mode and inline mode.
Use the playground below to validate your key + event plumbing quickly (no code changes required).
Playground
This snippet reflects your current playground settings. Your vendor key is never inlined; replace YOUR_VENDOR_KEY.
Loading the bundle
The widget is distributed as a UMD script that exposes a global OysterskinWidget. There are two environment bundles (prod + sandbox). Load exactly one of them:
<script src="https://sandbox.widget-lib.oysterskin.com/v0.2.0/oysterskin-vendor-widget-web.umd.js"></script>
<script src="https://widget-lib.oysterskin.com/v0.2.0/oysterskin-vendor-widget-web.umd.js"></script>
Hosting recommendations
- Prefer loading from the official CDN for cache hit rate and updates.
- If you self-host, pin a specific version in the URL and validate it in staging before promoting to production.
- Load the script late (after your critical rendering path) unless you want the float launcher to appear immediately.
Modes (float vs inline)
Float mode
Create the widget with mode: "float". A floating launcher appears immediately; users open the widget by clicking it.
Inline mode
Create the widget with mode: "inline". No launcher is shown automatically. You decide when to call instance.open().
Both modes support minimize() which hides the widget and shows the floating launcher again.
Widget types
- Scan: createScanWidget (widgetMode = scan)
- Chat: createChatWidget (widgetMode = chat; supports conversation starters; routes to /chat)
- Unified: createUnifiedWidget (widgetMode = all; supports conversation starters; routes to /)
- Consultation: createConsultationWidget (widgetMode = consultation; routes to /consultation/booking)
All widget types share the same instance contract and most options.
Options reference
The widget factories accept an options object with the following fields:
- key (string, required): vendor key used to sign the widget config.
- user (object, required): { id, name?, email? }.
- callback (function, required): called with widget events.
- mode ("float" | "inline", optional): defaults to "float".
- primaryColor (string, optional): defaults vary by widget type; affects launcher/spinner and the widget UI theme.
- displayLogo (boolean, optional): defaults to true.
- introMessage (string, optional): launcher popup title.
- messageBody (string, optional): launcher popup body text.
- minBudget/maxBudget (number, optional): used by the widget; defaults to 100 and 100000.
- conversationStarters (string[], chat/unified only): pre-seeds suggestions; when provided, chat/unified auto-open.
Note: the type definition includes a couple fields not currently used by the runtime (primaryTextColor, skipVendorFetch). If you need them, confirm support in the bundle version you are shipping.
Events and callback
Your callback(message) receives objects of the shape: { event: string, data: any }.
- closed: widget requested close; the widget is removed from view.
- minimized: widget requested minimize; the widget is hidden and the floating launcher is shown.
- checkout: user completed a checkout flow; the widget minimizes and passes a payload in data.
function onWidgetMessage(message) {
switch (message.event) {
case "checkout":
// message.data: checkout payload
// Example: route to your checkout confirmation page
break;
case "minimized":
// Widget minimized to launcher
break;
case "closed":
// Widget closed
break;
default:
// Future-proof: ignore unknown events
break;
}
}
Framework patterns
React (client-side) pattern
Keep a single widget instance. Load the UMD script once, create on demand, and always clean up on unmount.
import { useEffect, useRef } from "react";
function loadScriptOnce(src) {
return new Promise((resolve, reject) => {
const existing = document.querySelector(`script[data-oyster-umd="1"]`);
if (existing) return resolve();
const s = document.createElement("script");
s.src = src;
s.async = true;
s.setAttribute("data-oyster-umd", "1");
s.onload = () => resolve();
s.onerror = () => reject(new Error("Failed to load Oyster widget script"));
document.head.appendChild(s);
});
}
export function OysterWidgetButton({ vendorKey, user }) {
const instanceRef = useRef(null);
useEffect(() => {
return () => {
// Ensure the widget is closed if component unmounts
try { instanceRef.current?.close?.(); } catch {}
instanceRef.current = null;
};
}, []);
async function open() {
await loadScriptOnce("https://sandbox.widget-lib.oysterskin.com/v0.2.0/oysterskin-vendor-widget-web.umd.js");
const api = window.OysterskinWidget;
if (!instanceRef.current) {
instanceRef.current = api.createScanWidget({
key: vendorKey,
user,
mode: "inline",
callback: (m) => console.log("Oyster widget message", m),
});
}
instanceRef.current.open();
}
return <button onClick={open}>Open skin analysis</button>;
}
Next.js note
- Call the widget only in a client component (add "use client").
- Do not create the widget during SSR.
GTM / Tag Manager (Custom HTML tag)
This is useful when you can’t deploy code changes but can add a tag. You still must provide your vendor key at runtime.
<script>
(function () {
var SCRIPT = "https://sandbox.widget-lib.oysterskin.com/v0.2.0/oysterskin-vendor-widget-web.umd.js";
var s = document.createElement("script");
s.src = SCRIPT;
s.async = true;
s.onload = function () {
// Replace these with variables or dataLayer values
var vendorKey = "{{OYSTER_VENDOR_KEY}}";
var userId = "{{USER_ID}}";
window.__oysterInstance = window.OysterskinWidget.createScanWidget({
key: vendorKey,
user: { id: userId },
mode: "float",
callback: function (m) { console.log("Oyster widget message", m); }
});
};
document.head.appendChild(s);
})();
</script>
Single-page apps (route changes)
- If your SPA fully replaces document.body content, keep the widget instance in an app-level singleton to avoid duplicates.
- On user logout/account switch, call instance.close() and recreate with the new user identity.
Advanced patterns
Singleton instance (recommended)
Keep one instance per page and reuse it.
let oysterInstance = null;
export async function getOysterInstance({ scriptUrl, vendorKey, user, callback }) {
if (!window.OysterskinWidget) {
await new Promise((resolve, reject) => {
const s = document.createElement("script");
s.src = scriptUrl;
s.async = true;
s.onload = resolve;
s.onerror = () => reject(new Error("Failed to load Oyster widget script"));
document.head.appendChild(s);
});
}
if (!oysterInstance) {
oysterInstance = window.OysterskinWidget.createScanWidget({
key: vendorKey,
user,
mode: "inline",
callback,
});
}
return oysterInstance;
}
CTA-driven open (inline mode)
Initialize only after user intent to reduce page work.
document.getElementById("open-skin-analysis").addEventListener("click", async () => {
const instance = await getOysterInstance({
scriptUrl: "https://sandbox.widget-lib.oysterskin.com/v0.2.0/oysterskin-vendor-widget-web.umd.js",
vendorKey: window.APP_CONFIG.oysterVendorKey,
user: { id: window.CURRENT_USER_ID },
callback: (m) => console.log("Oyster widget message", m),
});
instance.open();
});
Versioning
- Pin a version in your script URL (example: /v0.2.0/) to avoid surprise behavior changes.
- Promote through environments: sandbox/staging first, then production.
- Monitor: log callback events + errors so you can correlate failures to version changes.
// Example: centralize configuration (no hardcoded keys in source)
window.APP_CONFIG = {
oyster: {
scriptUrl: "https://sandbox.widget-lib.oysterskin.com/v0.2.0/oysterskin-vendor-widget-web.umd.js",
vendorKey: window.ENV?.OYSTER_VENDOR_KEY, // injected at deploy/runtime
}
};
CSP / security headers
If you use a Content Security Policy, you must allow the widget script origin and the widget iframe origin. The exact origins depend on the environment/version you use.
Start with a staging-only relaxed policy, validate, then tighten. Prefer nonces/hashes for your own inline scripts.
# Example (adjust domains for your environment)
Content-Security-Policy:
script-src 'self' https://widget-lib.oysterskin.com https://sandbox.widget-lib.oysterskin.com;
frame-src https://widget-lib.oysterskin.com https://sandbox.widget-lib.oysterskin.com;
connect-src 'self' https://widget-lib.oysterskin.com https://sandbox.widget-lib.oysterskin.com;
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
Performance
- Defer initialization in inline mode until the user clicks a CTA (best for page speed).
- Preload the script on pages where the widget is frequently used (best for responsiveness).
- Avoid multiple instances at once; keep a single instance and reuse it.
Troubleshooting
OysterskinWidget is undefined
- Verify your script URL is reachable (open it directly in a browser).
- Confirm the script tag is not blocked by CSP or an ad blocker.
- Load the script before you call OysterskinWidget.create*.
The widget opens but is blank
- Confirm your CSP allows frame-src for the widget origin.
- Check browser console for iframe errors.
- Confirm the key/user payload is valid for the environment.
Floating button is behind other UI
- The launcher uses a very high z-index. If your site uses extreme z-index values, reduce them or adjust your stacking contexts.
- Make sure no parent element creates a stacking context that clips fixed-position children unexpectedly.
Launch checklist
- Keys: no keys committed; loaded at runtime from your config/secret system.
- Callback: checkout/minimized/closed events are handled and logged in staging.
- CSP: script-src and frame-src updated for the widget domains.
- UX: inline mode opens from your CTA; float mode launcher placement doesn’t conflict with your chat/support widgets.
- Mobile: verify the widget and launcher are reachable and not blocked by safe-area insets.