> For the complete documentation index, see [llms.txt](https://developers.unigox.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.unigox.com/integration-options/embed-widget.md).

# Embed Widget

Embed the Unigox buy/sell flow into your site with a single `<script>` tag. The loader creates a cross-origin `<iframe>` pointing at `https://unigox.com/embed`, wires up the required browser permissions, and exposes callbacks for trade and auth events.

> **Live playground:** [widgets.unigox.app](https://widgets.unigox.app)
>
> Use it to try every option below in your browser and copy the generated snippet straight into your page.

***

{% embed url="<https://youtu.be/mdtG4-LW5yA>" %}
Demo video
{% endembed %}

### Quickstart

```html
<div id="unigox-widget"></div>
<script src="https://unigox.com/widget.js"></script>
<script>
  const widget = UnigoxWidget.init({
    container: "#unigox-widget",
    crypto: "USDT",
    fiat: "USD",
    amount: 100,
    onTradeCompleted: function (e) {
      console.log("trade", e.tradeId);
    },
  });
</script>
```

That's it. The loader injects the iframe with the right `sandbox` and `allow` attributes — you do **not** need to construct the iframe yourself.

No registration, partner agreement or API key is required to embed the widget. All options below are optional unless marked otherwise.

> **Need a starting point for your stack?** Copy one of the [`examples`](https://github.com/Unigox/widgets/tree/main/examples) folders — vanilla HTML, React, or WordPress — and tweak from there. Each example mirrors this guide and stays in sync with releases.

***

### Earn referrals from your traffic

If you have a Unigox account and want every signup that goes through the widget on your site to count as your referral, just pass your Unigox username as `ref`:

<pre class="language-html"><code class="lang-html">&#x3C;div id="unigox-widget">&#x3C;/div>
&#x3C;script src="https://unigox.com/widget.js">&#x3C;/script>
&#x3C;script>
  UnigoxWidget.init({
    container: "#unigox-widget",
<strong>    ref: "your-unigox-username",
</strong>  });
&#x3C;/script>
</code></pre>

That's the entire integration. Anyone who signs up while the widget is loaded on your page is attributed to your account — same mechanism as the `unigox.com/?ref=…` link, but built into the widget so you can earn off the buy flow directly on your site.

***

### Init options

Pass these to `UnigoxWidget.init(options)`.

<table><thead><tr><th width="159.9765625">Option</th><th width="134.76171875">Type</th><th width="115.2578125">Required</th><th width="99.51953125">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>container</code></td><td><code>string | HTMLElement</code></td><td>yes</td><td>—</td><td>CSS selector or DOM element. The iframe is appended to it.</td></tr><tr><td><code>partner</code></td><td><code>string</code></td><td>no</td><td>—</td><td>Free-form attribution identifier — any string you want to see in your reports. Not validated. Optional.</td></tr><tr><td><code>ref</code></td><td><code>string</code></td><td>no</td><td>—</td><td>Your Unigox username. New signups initiated inside the widget are credited to this user (referral). Use this for no-integration deployments — paste your username and earn referrals from anyone who signs up via the widget on your site.</td></tr><tr><td><code>type</code></td><td><code>"buy" | "sell" | "buy-sell" | "buy-with-sendout"</code></td><td>no</td><td><code>"buy-sell"</code></td><td>Which side opens first. <code>"buy-sell"</code> shows the full buy/sell toggle. <code>"buy-with-sendout"</code> adds an automatic sendout step — see below. <code>"both"</code> is still accepted as an alias for <code>"buy-sell"</code> for back-compat.</td></tr><tr><td><code>sendoutAddress</code></td><td><code>string</code></td><td>conditional</td><td>—</td><td><strong>Required when <code>type=buy-with-sendout</code>.</strong> Destination address the purchased crypto is sent to after the trade (EVM <code>0x…</code> or Solana base58).</td></tr><tr><td><code>sendoutNetwork</code></td><td><code>string</code></td><td>conditional</td><td>—</td><td><strong>Required when <code>type=buy-with-sendout</code>.</strong> Destination network as a blockchain ticker or name. Accepted values: <code>"ethereum"</code> / <code>"eth"</code>, <code>"optimism"</code> / <code>"op"</code>, <code>"polygon"</code> / <code>"pol"</code>, <code>"unichain"</code> / <code>"uni"</code>, <code>"base"</code>, <code>"arbitrum"</code> / <code>"arb"</code>, <code>"avalanche"</code> / <code>"avax"</code>, <code>"hyperevm"</code> / <code>"hype"</code>, <code>"solana"</code> / <code>"sol"</code>. Numeric chain ids are not accepted.</td></tr><tr><td><code>crypto</code></td><td><code>string</code></td><td>no</td><td>auto</td><td>Pre-selected crypto ticker (<code>"USDT"</code>, <code>"USDC"</code>  are available). Falls back to USDT or the user's balance.</td></tr><tr><td><code>fiat</code></td><td><code>string</code></td><td>no</td><td>auto</td><td>Pre-selected fiat code (e.g. <code>"USD"</code>, <code>"EUR"</code>, <code>"VND"</code>). Falls back to the user's country currency.</td></tr><tr><td><code>amount</code></td><td><code>number</code></td><td>no</td><td>—</td><td>Pre-filled amount. For <code>type=buy</code> this is the <strong>fiat</strong> side ("You spend"); for <code>type=sell</code> it is the <strong>crypto</strong> side.</td></tr><tr><td><code>email</code></td><td><code>string</code></td><td>no</td><td>—</td><td>Prefills the login email. Combined with <code>requireLogin</code> it drives the auto-login flow.</td></tr><tr><td><code>theme</code></td><td><code>"light" | "dark"</code></td><td>no</td><td><code>"light"</code></td><td>Visual theme.</td></tr><tr><td><code>language</code></td><td><code>"en" | "es"</code></td><td>no</td><td><code>"en"</code></td><td>UI language. Unsupported values fall back to <code>"en"</code>. More locales arrive as they are translated.</td></tr><tr><td><code>loginMethods</code></td><td><code>"email" | "web3" | "ton" | "all"</code> or multiple options</td><td>no</td><td><code>"all"</code></td><td>Which login options to show. Pass a single value, <code>"all"</code> for all three, or multiple options separated by commas: <code>"email,web3"</code>, <code>"email,ton"</code>, <code>"web3,ton"</code>.</td></tr><tr><td><code>requireLogin</code></td><td><code>boolean</code></td><td>no</td><td><code>false</code></td><td>If <code>true</code>, force the login screen before the widget opens even when anonymous trading would otherwise be possible.</td></tr><tr><td><code>applyAttribution</code></td><td><code>boolean</code></td><td>no</td><td><code>true</code></td><td>Toggles the "Powered by Unigox" footer inside the widget.</td></tr><tr><td><code>width</code></td><td><code>string</code></td><td>no</td><td><code>"100%"</code></td><td>Iframe width (any CSS length).</td></tr><tr><td><code>height</code></td><td><code>string</code></td><td>no</td><td><code>"700px"</code></td><td>Iframe height. When <strong>omitted</strong>, the widget auto-resizes to fit its content.</td></tr></tbody></table>

#### Callbacks

> **All callbacks are informational, not authoritative.** They are fired from the iframe via `window.postMessage` — anything running in the top-level page can spoof or replay them. Use callbacks for UX (loading state, redirect after success, fire your own analytics) and for non-financial logging. **Do not** use them as proof-of-state for business decisions like releasing a product, crediting an account, or paying out funds. For those, verify via the Unigox API (server-to-server) or, when applicable, via independent on-chain confirmation.
>
> Server-signed webhooks are on the roadmap and will be the authoritative channel; until they ship, treat every event below as a hint, not a fact.

<table><thead><tr><th width="187.76171875">Callback</th><th width="221.3515625">Payload</th><th width="215.328125">Fires when</th><th>Verifiable?</th></tr></thead><tbody><tr><td><code>onReady</code></td><td>—</td><td>The widget finished its initial render.</td><td>UX-only</td></tr><tr><td><code>onAuthChange</code></td><td><code>{ isAuthenticated }</code></td><td>The user signs in or signs out.</td><td>UX-only</td></tr><tr><td><code>onTradeStarted</code></td><td><code>{ tradeId, tradeType }</code></td><td>The user confirmed a trade.</td><td>Verify via API</td></tr><tr><td><code>onTradeCompleted</code></td><td><code>{ tradeId }</code></td><td>A trade reached a terminal success.</td><td>Verify via API</td></tr><tr><td><code>onSendoutStarted</code></td><td><code>{ tradeId, address, chainId }</code></td><td><em>(buy-with-sendout only)</em> The bridge to the partner address was submitted.</td><td>Verify via API</td></tr><tr><td><code>onSendoutCompleted</code></td><td><code>{ tradeId, address, chainId, txHash? }</code></td><td><em>(buy-with-sendout only)</em> The bridge confirmed on the destination chain.</td><td><code>txHash</code> independently verifiable on-chain</td></tr><tr><td><code>onSendoutFailed</code></td><td><code>{ tradeId, code, message }</code></td><td><em>(buy-with-sendout only)</em> The sendout step failed. <code>code</code> is one of the <code>SENDOUT_*</code> codes — see Error codes.</td><td>UX-only</td></tr><tr><td><code>onWidgetError</code></td><td><code>{ code, message }</code></td><td>A misconfig error fired before any trade existed (e.g. missing/invalid <code>sendoutAddress</code>). <code>code</code> is one of the <code>WIDGET_*</code> codes.</td><td>UX-only</td></tr></tbody></table>

#### Handle methods

`init` returns a handle for live control:

```js
widget.configure({ crypto: "USDC", type: "sell" }); // update options at runtime
widget.reset();                                     // send user back to the start view
widget.destroy();                                   // remove the iframe + listeners
```

`configure` accepts the same shape as the init URL params (`type`, `crypto`, `fiat`, `amount`, `vendor`).

***

### Host-page headers

> **Skip this section** if your site does not set a `Content-Security-Policy` or a `Permissions-Policy` header. The widget works out of the box on the default browser permissions — these rules only matter if you have already tightened them.

#### One-block paste (strict-CSP sites)

If your site ships **both** a strict CSP and a Permissions-Policy, drop these two response headers on every page that loads the widget:

```
Content-Security-Policy: script-src https://unigox.com; frame-src https://unigox.com
Permissions-Policy: storage-access=(self "https://unigox.com"), publickey-credentials-get=(self "https://unigox.com"), publickey-credentials-create=(self "https://unigox.com")
```

Merge them into your existing directives — do not replace what you already have. Each line below explains what it unlocks and what breaks without it.

#### Content-Security-Policy

| Directive                       | Why it matters                                                                                                                        |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `script-src https://unigox.com` | Allows `widget.js` to execute. Without it the loader silently fails with a CSP-violation error in the console — no iframe is created. |
| `frame-src https://unigox.com`  | Allows the iframe at `unigox.com/embed` to load. Without it the iframe stays blank. (`child-src` on the legacy directive.)            |

Anything else (`connect-src`, `style-src`, `img-src`) does not need a Unigox entry: all those requests originate **inside** the iframe, which has its own document and is not constrained by the host page's CSP.

#### Permissions-Policy

The widget asks for several powerful browser features via the iframe's `allow` attribute (set automatically by `widget.js`). If your top-level page also sets a `Permissions-Policy`, the host's policy wins — you must delegate those features to `unigox.com` for the `allow` attribute to take effect:

```
Permissions-Policy: storage-access=(self "https://unigox.com"),
                    publickey-credentials-get=(self "https://unigox.com"),
                    publickey-credentials-create=(self "https://unigox.com")
```

| Feature                            | What breaks without it                                                                                     |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `storage-access`                   | Ambient session reuse — users have to log in on every visit instead of reusing their `unigox.com` session. |
| `publickey-credentials-get/create` | Passkey (WebAuthn) sign-in stops working.                                                                  |

***

### Buy with sendout

`type="buy-with-sendout"` turns the widget into a **one-shot fiat → crypto → partner-address** flow. The user buys crypto normally, and the widget then automatically opens a sendout step that bridges the funds to a partner-configured external address over our existing bridge relay.

```html
<div id="unigox-widget"></div>
<script src="https://unigox.com/widget.js"></script>
<script>
  UnigoxWidget.init({
    container: "#unigox-widget",
    partner: "acme",
    type: "buy-with-sendout",
    sendoutAddress: "0xAbCdEf0123456789abcdef0123456789AbCdEf01",
    sendoutNetwork: "ethereum", // also "eth", "polygon"/"pol", "optimism"/"op", "unichain"/"uni", "base", "arbitrum"/"arb", "avalanche"/"avax", "hyperevm"/"hype", "solana"/"sol"
    crypto: "USDC",
    fiat: "USD",
    amount: 100,
    onSendoutCompleted: (e) => console.log("sendout ok", e.tradeId, e.txHash),
  });
</script>
```

#### Behaviour

* The buy/sell toggle is hidden — only BUY is available in this mode.
* A persistent banner shows the sendout destination on every pre-sendout screen (`Sendout to 0xAb…cd on Ethereum`).
* After the trade reaches terminal success and the purchased crypto lands in the user's internal Unigox wallet, the widget auto-navigates to the sendout view (\~1.5 s after completion).
* The sendout view shows a locked amount (matching the trade result), the destination, an expandable quote breakdown, and — if the account has it enabled — inline 2FA fields. The user confirms with a single click.
* Bridge progress, fees and final tx hash are shown inline. The host is notified via `onSendoutStarted / onSendoutCompleted / onSendoutFailed`.

#### Requirements & constraints

* `sendoutAddress` must match the chosen network's address format. EVM networks require a `0x…` address that passes EIP-55 checksum validation when mixed-case; Solana requires a base58 address. Malformed addresses render a "Widget misconfigured" screen before any trade is created, so the user cannot proceed.
* `sendoutNetwork` must be a ticker the widget recognises (`ethereum` / `eth`, `optimism` / `op`, `polygon` / `pol`, `unichain` / `uni`, `base`, `arbitrum` / `arb`, `avalanche` / `avax`, `hyperevm` / `hype`, `solana` / `sol`) **and** the resulting chain must be supported by the Unigox bridge for the chosen `crypto`. Unsupported combinations render a "Sendout misconfigured" screen inside the widget. Unknown tickers render a "Missing required `sendoutNetwork` parameter." configuration error.
* Today the bridge supports **USDC** and **USDT** as source assets. If you plan to use other tickers, contact support first.
* The crypto selector inside the widget is **not** filtered — the user can still pick any listed token; misconfigurations surface at the sendout step rather than at selection time.

***

### Error codes

Error events carry a stable `code` field — branch on `code` for business logic, treat `message` as a human-readable hint that may be reworded between releases. Codes never change shape or meaning within the **v1** loader.

#### `WIDGET_*` — `onWidgetError({ code, message })`

These fire **before any trade exists**. Recovery is always a configuration fix on the partner side; the user cannot continue. The widget also displays a "Widget misconfigured" screen so end-users see something coherent.

<table><thead><tr><th width="286.9296875">Code</th><th>When</th><th>Recovery</th></tr></thead><tbody><tr><td><code>WIDGET_MISSING_SENDOUT_ADDRESS</code></td><td><code>type=buy-with-sendout</code> was used without <code>sendoutAddress</code>.</td><td>Re-init with a non-empty <code>sendoutAddress</code>.</td></tr><tr><td><code>WIDGET_MISSING_SENDOUT_NETWORK</code></td><td><code>type=buy-with-sendout</code> was used without <code>sendoutNetwork</code>, or the ticker is unknown.</td><td>Re-init with a recognised ticker (see <code>sendoutNetwork</code>).</td></tr><tr><td><code>WIDGET_INVALID_SENDOUT_ADDRESS</code></td><td><code>sendoutAddress</code> does not match the chosen network's format (bad EIP-55 checksum on EVM, or non-base58 on Solana).</td><td>Re-init with an address valid for the chosen <code>sendoutNetwork</code>.</td></tr></tbody></table>

#### `SENDOUT_*` — `onSendoutFailed({ tradeId, code, message })`

These fire **after a trade exists**. The user can usually retry from inside the widget; the partner is informed so it can react in its own UI / analytics.

<table><thead><tr><th width="215.359375">Code</th><th>When</th><th>Funds moved?</th><th>Retryable</th></tr></thead><tbody><tr><td><code>SENDOUT_NOT_SUPPORTED</code></td><td>The combination of the user's chosen crypto and <code>sendoutNetwork</code> is not on the bridge. The widget shows a "Sendout misconfigured" screen.</td><td>No</td><td>No — partner-side fix only (different <code>sendoutNetwork</code> or restrict <code>crypto</code>).</td></tr><tr><td><code>SENDOUT_QUOTE_FAILED</code></td><td>The bridge quote API rejected the request (no liquidity, network down, etc.).</td><td>No</td><td>Yes — user retries from inside the widget.</td></tr><tr><td><code>SENDOUT_BRIDGE_FAILED</code></td><td>Source-chain transaction submitted but destination did not confirm, or the relay errored mid-flight.</td><td>Maybe — funds may be in flight.</td><td>Yes inside the widget; if it keeps failing, recovery via <a href="https://unigox.com/wallet"><code>unigox.com/wallet</code></a> and contact support.</td></tr><tr><td><code>SENDOUT_UNKNOWN</code></td><td>Unclassified upstream failure. Treat as <code>SENDOUT_BRIDGE_FAILED</code> for retry purposes.</td><td>Maybe</td><td>Yes — same recovery path.</td></tr></tbody></table>

#### Branching example

```js
UnigoxWidget.init({
  container: "#unigox-widget",
  type: "buy-with-sendout",
  sendoutAddress: "0xAbCd…",
  sendoutNetwork: "base",
  onWidgetError: (e) => {
    // Always a partner-side bug. Surface a config-error UI and re-init.
    analytics.track("unigox_widget_error", e);
  },
  onSendoutFailed: ({ tradeId, code, message }) => {
    if (code === "SENDOUT_NOT_SUPPORTED") {
      analytics.track("unigox_sendout_unsupported", { tradeId });
      // The user is stuck — direct them to support or restrict crypto on your side.
    } else if (code === "SENDOUT_BRIDGE_FAILED") {
      // Funds may be on the way. Direct the user to unigox.com/wallet to recover.
      showRecoveryUI(tradeId);
    }
    // SENDOUT_QUOTE_FAILED / SENDOUT_UNKNOWN — let the user retry inside the widget.
  },
});
```

***

### Versioning & compatibility

The loader at `https://unigox.com/widget.js` auto-updates — every page load fetches the latest. We commit to the following stability rules within the **v1** loader:

* **Init options.** No existing option is ever removed or renamed. Accepted types and the required-vs-optional split do not change. New optional options may be added; partners should ignore options they do not recognise.
* **Callbacks.** Existing payload fields are not renamed or removed. New optional fields may be added; partners should ignore unknown fields and not rely on absence.
* **postMessage protocol.** Existing `type` values and existing payload fields are stable. New `type`s and new optional payload fields may appear.
* **Direct `/embed` URL params.** Same rules as init options.

The widget UI (visual design, copy, internal step ordering, error wording) is **not** covered by these rules — it changes continuously. Build your integration on the contract above, not on screen flow or DOM structure.

#### Breaking changes

When a breaking change is unavoidable we ship it as a new loader URL — `https://unigox.com/widget.v2.js` — and keep the previous URL serving the previous major for at least **90 days**. During that window both URLs work and partners migrate by changing the `<script src>` and reading the migration notes in [`CHANGELOG.md`](https://github.com/Unigox/widgets/blob/main/CHANGELOG.md).

We never publish breaking changes by silently flipping `widget.js`.

#### Tracking changes

All partner-visible changes are recorded in `CHANGELOG.md`. Watch the file on GitHub if you want a notification on every release.

***

### Examples

Stack-specific copy/paste examples live in  [`examples`](https://github.com/Unigox/widgets/tree/main/examples) . Each folder is self-contained — no build step beyond what your stack already needs.

| Stack           | What it shows                                                                              |
| --------------- | ------------------------------------------------------------------------------------------ |
| `vanilla-html/` | One HTML file, one `<div>`, one `<script>`. The minimum integration.                       |
| `react/`        | A reusable `UnigoxWidget` wrapper plus a sample page. SSR-safe.                            |
| `wordpress/`    | Snippet for the WordPress "Custom HTML" block, plus notes on caching plugins, AMP and CSP. |

The examples track the v1 loader, so they auto-pick up additive changes without edits. When the contract changes (`v2`), the examples folder is updated alongside.
