# 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.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developers.unigox.com/integration-options/embed-widget.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
