Oyster Widget Developer Docs (v1)

v1 supports two modes: float (launcher button) and inline (embedded into a container you provide). Use the playground to generate copyable integration code for your exact settings.

Inline is embedded

Inline mode mounts the widget into your page via container. No floating toggle button is used in inline mode.

autoOpen

autoOpen controls whether the widget surfaces on init. In float mode the launcher is always shown — autoOpen: false just keeps the widget closed until the user clicks the launcher (or you call open()). In inline mode, false defers mounting entirely.

User is optional

If you don’t have user context (public page), omit user. The widget can prompt for an email when needed.

Overview

What it is, how it behaves

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)
  • OysterskinWidget.createVoiceAgentWidget(options)

Each factory returns an instance with open(), minimize(), and close(). The unified widget instance additionally exposes openScan(), openConsultation(), openChat(), and openVoiceAgent() for driving the widget to a specific view from host code.

In v1, minimize() only affects float mode (it returns the widget to the launcher). In inline mode, minimize() is a no-op.

Quickstart

Copy/paste baseline integration

Minimal standalone examples: float mode and inline mode.

Playground

Generate code from your settings
Important Your vendor key is never stored by this page (other non-secret fields may be remembered locally).
Prefer pinned URLs in production.
Chat and Unified can accept conversation starters.
Inline mounts into container. Float shows a launcher button.
If false, nothing renders until you call open().
This doc does not store it
If omitted, the widget can prompt for an email when needed.
Inline mode requires a mount container. This docs page includes a container with id inlineMount.
CSS selector or HTMLElement.
Number = px, or any CSS length.
Inline mount container
Custom logo image URL. When provided with displayLogo=true, replaces the default SVG logo.
Fallback when budgetRanges is not provided.
Fallback when budgetRanges is not provided.
Preferred currency code when using budgetRanges.
Supports either a single range set ({ low, mid, premium }) or currency map ({ NGN: { low, mid, premium } }).
Used by the float launcher hover popup text.
Optional suggestions shown in the widget.
Event log (callback payloads)

This snippet reflects your current playground settings. Your public key is never inlined; replace YOUR_PUBLIC_KEY.

Loading the bundle

UMD + global API
<script src="https://sandbox.widget-lib.oysterskin.com/v1/oysterskin-vendor-widget-web.umd.js"></script>

Modes (float vs inline)

Decide how the widget appears

Float mode

Create the widget with mode: "float". The floating launcher appears automatically on every embed. autoOpen: true (default) also opens the widget itself immediately; autoOpen: false leaves just the launcher, and the widget opens on click or when you call open().

Inline mode

Create the widget with mode: "inline" and provide a container. Inline mode mounts into your page and does not use a floating launcher.

Upgrade guide (v0.2.0 → v1)

Breaking changes + what to change

v1 is the recommended integration target. If you’re upgrading from v0.2.0, these are the changes you must make.

1) Inline mode is now truly embedded

  • v0.2.0: mode: "inline" injected a fullscreen, fixed-position widget when you called open().
  • v1: mode: "inline" mounts into a container you provide via container.
  • v1: inline mode does not use a floating launcher button (no toggle button).
// v0.2.0 (inline)
const instance = OysterskinWidget.createScanWidget({
  publicKey: "YOUR_PUBLIC_KEY",
  user: { id: "YOUR_END_USER_ID" },
  mode: "inline",
  callback: onWidgetMessage,
});
instance.open();

// v1 (inline) — add a mount container + pass container
// 
const instanceV1 = OysterskinWidget.createScanWidget({ publicKey: "YOUR_PUBLIC_KEY", // user is optional in v1 // user: { id: "YOUR_END_USER_ID" }, mode: "inline", container: "#widget-root", inlineHeight: 720, callback: onWidgetMessage, }); // autoOpen defaults to true in v1 (mounts immediately)

2) autoOpen controls whether the widget surfaces automatically

  • v1: autoOpen defaults to true.
  • v1 + float: the launcher is always shown. autoOpen only controls whether the widget itself opens on load — true: opens immediately; false: the widget stays closed until the user clicks the launcher (or you call open()).
  • v1 + inline: if autoOpen: true, the widget mounts immediately into container. If false, it mounts when you call open().
  • Changed from v1.9.x: previously, autoOpen: false in float mode rendered nothing — including the launcher. From v1.10.0 the launcher is always present in float mode; autoOpen only gates the widget itself.
// v1: delay rendering until a CTA click
const instance = OysterskinWidget.createUnifiedWidget({
  publicKey: "YOUR_PUBLIC_KEY",
  mode: "inline",
  container: "#widget-root",
  inlineHeight: 720,
  autoOpen: false,
  callback: onWidgetMessage,
});

// Later, on user intent:
instance.open();

3) User is optional in v1

  • If your widget loads on public pages (no user context), omit user.
  • If you do have a stable user id, pass it for better continuity.

Widget types

Which factory method to call
  • Scan: createScanWidget
  • Chat: createChatWidget
  • Unified: createUnifiedWidget
  • Consultation: createConsultationWidget
  • Voice Agent: createVoiceAgentWidget

Unified widget: programmatic navigation

On a unified widget instance you can jump directly to any tile from host code. Each method opens the widget (if minimized) and navigates it to that view.

const widget = OysterskinWidget.createUnifiedWidget({ /* ... */ });

widget.openScan();
widget.openConsultation();
widget.openChat();
widget.openVoiceAgent();

These methods are present on every widget instance, but on a restricted widget (e.g. createScanWidget) the widget silently ignores navigation to disallowed views.

Unified widget: restricting the gallery

Pass widgetTypes to show only specific tiles in the home gallery. The router also blocks deep-linked routes outside the listed types.

const widget = OysterskinWidget.createUnifiedWidget({
  publicKey: "YOUR_PUBLIC_KEY",
  widgetTypes: ["scan", "consultation"], // hide chat + voice-agent tiles
  callback: (message) => console.log(message),
});

Vendors can also configure this list centrally from the Oyster widget manager — no host code change required. When widgetTypes is passed from code it overrides the vendor-managed value.

Where config comes from

Widget Manager first, code only when you need to override

Most appearance and copy options can be set centrally from the Oyster Widget Manager in the vendor dashboard and are fetched at runtime. The recommended workflow:

  1. Set your branding, copy, and budget tiers once in the Widget Manager — no deploy required, every embed picks them up.
  2. From code, pass only what's page-specific or what you need to override for a particular widget instance. Anything you pass from code wins; anything you omit falls back to the manager-saved value.

Manager-controlled options

Set these in the Widget Manager; override per-instance from code only when needed.

  • primaryColor, primaryTextColor
  • displayLogo, logoUrl
  • introMessage, messageBody
  • budgetRanges, budgetCurrency, minBudget/maxBudget
  • conversationStarters (chat / unified)
  • widgetTypes (unified — restrict the home gallery)
  • Allowed domains

Code-only options

No manager equivalent — these are per-page or per-instance decisions.

  • publicKey, callback — required on every instance
  • mode, container, inlineWidth, inlineHeight — page-specific layout
  • autoOpen, displayHoverPopup, displayAutoPopup — per-instance UX behaviour
  • user — per-end-user context

In short: configure once in the Widget Manager; reach for code overrides only when an instance needs to differ.

Options reference

What you can configure
  • key (string, required): vendor key.
  • callback (function, required): called with widget events.
  • mode ("float" | "inline", optional): defaults to "float".
  • autoOpen (boolean, optional): defaults to true. Auto-opens the widget on init. In float mode the launcher is always shown — autoOpen only controls whether the widget also surfaces immediately. Set to false to keep only the launcher visible until the user clicks it (or you call open()).
  • user (object, optional): { id, name?, email? }. If omitted, the widget can prompt for email when needed.
  • container (HTMLElement | string, inline only): where the widget mounts.
  • inlineWidth/inlineHeight (number | string, inline only): number = px.
  • primaryColor (string, optional): widget theme color. Defaults vary by widget type.
  • displayLogo (boolean, optional): whether to show the Oysterskin logo. Defaults to true.
  • logoUrl (string, optional): custom logo image URL. When provided with displayLogo: true, replaces the default SVG logo with your custom image.
  • introMessage (string, optional): launcher tooltip title text.
  • messageBody (string, optional): launcher tooltip body text.
  • displayHoverPopup (boolean, optional, float only): defaults to true. Show the launcher tooltip when the user hovers the launcher.
  • displayAutoPopup (boolean, optional, float only): defaults to true. Auto-show the launcher tooltip (with a close button) once per browsing session per publicKey, after the host page finishes loading. Suppressed when autoOpen: true or once the user has opened the widget at least once.
  • minBudget/maxBudget (number, optional): fallback budget boundaries. Defaults to 100 and 100000.
  • budgetCurrency (string, optional): preferred currency code used for budget range rendering.
  • budgetRanges (object, optional): config-driven range presets. Supports { low, mid, premium } or { NGN: { low, mid, premium } }.
  • conversationStarters (string[], chat/unified only): optional suggestion prompts.
  • widgetTypes (("scan" | "chat" | "consultation" | "voice-agent")[], unified only): restrict which tiles appear in the home gallery. Omit (or pass empty) to show all. Vendor-managed value in the Oyster admin is used when this option is not provided.

Budget ranges example

const instance = OysterskinWidget.createScanWidget({
  publicKey: "YOUR_PUBLIC_KEY",
  budgetCurrency: "NGN",
  budgetRanges: {
    NGN: {
      low: { min: 30000, max: 60000, label: "Low Budget" },
      mid: { min: 60000, max: 100000, label: "Mid Budget" },
      premium: { min: 100000, max: 200000, label: "Premium" }
    }
  },
  callback: (message) => console.log(message),
});

Custom logo example

Use logoUrl to display your own logo instead of the default Oysterskin logo:

const instance = OysterskinWidget.createScanWidget({
  publicKey: "YOUR_PUBLIC_KEY",
  mode: "float",
  displayLogo: true,
  logoUrl: "https://your-domain.com/logo.png",
  callback: (message) => console.log(message),
});

Events and callback

How to listen and react

Your callback receives { event, data }. Events are emitted by the widget via postMessage, and forwarded by this library after origin/source checks.

Supported callback events

  • closed: user closed the widget UI. data: {}
  • minimized: user minimized the widget UI (float mode only). data: {}
  • checkout: user clicked checkout from within the widget. data: CheckoutPayload
  • cartItemAdded: an item was added or its quantity was increased. data: CartItemChangedPayload
  • cartItemRemoved: an item was removed or its quantity was decreased. data: CartItemChangedPayload
  • scanCompleted: scan completed and produced a batch id. data: { batch_id }
Note oysterSkinWidget.resize is an internal sizing message used to resize the inline widget. It is not forwarded to your callback.

Recommended handler pattern

function onWidgetMessage(message) {
  switch (message.event) {
    case 'checkout':
      // message.data: { total_quantity, total_amount, currency, checkout_items, user? }
      // `user` is { id?, name?, firstName?, lastName?, email? } when the widget
      // has a profile on hand (collected before scan or supplied via config.user),
      // otherwise omitted entirely.
      break;
    case 'cartItemAdded':
    case 'cartItemRemoved':
      // message.data: { total_quantity, changed_item, checkout_items }
      break;
    case 'scanCompleted':
      // message.data: { batch_id }
      break;
    case 'minimized':
    case 'closed':
      break;
    default:
      // Future-proofing: ignore unknown events
      break;
  }
}

Example payloads

checkout

{
  "event": "checkout",
  "data": {
    "total_quantity": 2,
    "total_amount": 12800,
    "currency": "NGN",
    "checkout_items": [
      {
        "product_id": 123,
        "product_name": "Name",
        "quantity": 2,
        "brand_name": "Brand",
        "sku": "SKU-123",
        "image_url": "https://example.com/image.jpg",
        "stock_level": 10
      }
    ],
    "user": {
      "id": "user-123",
      "firstName": "Jane",
      "lastName": "Doe",
      "email": "jane@example.com"
    }
  }
}

user is included when the widget has a profile on hand — either collected before the scan (contact step) or supplied via config.user. It is omitted entirely when nothing is known, so you can distinguish "no profile" from "anonymous". All fields inside user are optional.

cartItemAdded (payload shape is identical for cartItemRemoved)

Tip: checkout_items can be an empty array if the widget doesn’t yet have inventory-backed product IDs for the items in the cart.

{
  "event": "cartItemAdded",
  "data": {
    "total_quantity": 1,
    "changed_item": {
      "product_id": 123,
      "product_name": "Name",
      "quantity": 1,
      "brand_name": "Brand",
      "sku": null,
      "image_url": null,
      "stock_level": null
    },
    "checkout_items": []
  }
}

scanCompleted

{
  "event": "scanCompleted",
  "data": {
    "batch_id": "batch_abc123"
  }
}

Framework patterns

React/Next.js, SPAs, tag managers

Recommended: load the UMD script once, keep one widget instance, and reuse it. In inline mode, mount into a stable container.

Advanced patterns

Lazy load, singleton, CTA-driven open

For inline mode, use autoOpen: false + call open() from your CTA to defer the widget entirely and improve page performance. (In float mode the launcher is always shown, so autoOpen: false just keeps the widget itself closed — it doesn't avoid loading the launcher.)

Versioning

Pin versions and promote safely

In production, the recommended default is to pin to the latest major: /v1/. This lets you receive updates and patches without chasing every release.

// Production (major channel)
<script src="https://widget-lib.oysterskin.com/v1/oysterskin-vendor-widget-web.umd.js"></script>

// Sandbox (major channel)
<script src="https://sandbox.widget-lib.oysterskin.com/v1/oysterskin-vendor-widget-web.umd.js"></script>

If you need fully immutable builds (e.g. regulated environments), pin an exact version like /v1.0.0/ and upgrade intentionally.

CSP / security headers

Common integration blocker

Allow the widget script + widget origins for the environment you use.

Troubleshooting

Fast fixes

If OysterskinWidget is undefined, verify your script URL and CSP.

Launch checklist

Before you ship
  • Keys: no keys committed; loaded at runtime from your config/secret system.
  • Callback: events are handled and logged in staging.
  • CSP: script-src and frame-src updated for the widget domains.
  • Inline container: ensure your container exists before creating the widget.