> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wizzgift.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedded storefront quickstart

> Embed the WizzGift storefront in your app, let users pick gift cards, and pay for them from your balance.

The embedded storefront is a ready-made gift card shop you place in your site or app with an `<iframe>`. Your users browse and choose cards inside it; you decide whether they may have them (for example, by spending points in your app); you then pay WizzGift from your prepaid balance. WizzGift delivers the cards straight to the email address the user entered.

You never handle card codes, and you never need your own product catalog.

## How it works

1. The user fills a cart in the iframe and enters their email.
2. WizzGift prices the cart and the iframe posts a **signed quote** to your page (`order-requested`).
3. Your backend charges the user on your side, then **redeems** the quote with your API key. That creates the order and pays it from your balance.
4. WizzGift fulfils the order, emails the cards to the user, and sends a **signed callback** to your server with the outcome.

<Info>
  You need a business account with B2B enabled, an [API key](/authentication) with the `orders:create` and `orders:read` scopes, a funded balance (see the [B2B quickstart](/quickstart/b2b)), and an https endpoint for callbacks.
</Info>

<Steps>
  <Step title="Get your integration settings">
    ```bash theme={null}
    curl https://api.wizzgift.com/b2b/iframe-orders/config \
      -H "X-API-Key: wg_live_..."
    ```

    ```json Response (200) theme={null}
    {
      "iframeIntegrationId": "V1StGXR8_Z5jdHi6B-myT",
      "embedUrl": "https://iframe.wizzgift.com/?iframeIntegrationId=V1StGXR8_Z5jdHi6B-myT",
      "messageOrigin": "https://iframe.wizzgift.com",
      "callbackSignature": {
        "header": "X-Wizzgift-Signature",
        "scheme": "t=<unix ms>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">",
        "signingSecret": "9c1e...f03a"
      }
    }
    ```

    Store `signingSecret` server-side only. It verifies the callbacks in step 5.
  </Step>

  <Step title="Embed the storefront">
    ```html theme={null}
    <iframe
      id="wizzgift-store"
      src="https://iframe.wizzgift.com/?iframeIntegrationId=V1StGXR8_Z5jdHi6B-myT&theme=dark"
      style="width: 100%; height: 80vh; border: 0"
      title="Gift card store"
    ></iframe>
    ```

    | Query parameter | Description |
    | - | - |
    | `iframeIntegrationId` | Required. Your integration id from step 1. |
    | `theme` | Optional. `light` or `dark`. |
    | `email` | Optional. The user's email, URL-encoded (`user%40example.com`). Checkout then skips the email prompt; the user can still change where the cards go. |

    You can link straight to a section by adding a path before the query string: `/gift-cards/<category>`, `/gift-cards/<product-slug>`, `/refill` (mobile top-ups), or `/recharge/<product-slug>`.
  </Step>

  <Step title="Receive the quote">
    When the user checks out, the iframe posts a message to your page. Accept it only from the storefront origin **and** from the iframe you embedded:

    ```javascript theme={null}
    const store = document.getElementById("wizzgift-store");

    window.addEventListener("message", async (event) => {
      if (event.origin !== "https://iframe.wizzgift.com") return;
      if (event.source !== store.contentWindow) return;
      if (event.data?.type !== "order-requested") return;

      // Hand the signed token to YOUR backend; never redeem from the browser.
      await fetch("/api/wizzgift/cashout", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ token: event.data.orderInfo }),
      });
    });
    ```

    ```json Message theme={null}
    {
      "type": "order-requested",
      "orderInfo": "eyJhbGciOiJIUzI1NiIs...",
      "quote": {
        "quoteId": "iq_Xn3kP0aQ9vBm2LcT",
        "totalAmount": 25.75,
        "currency": "USD",
        "expiresAt": 1755734400000,
        "items": [
          { "productId": "prod_amazon_us", "name": "Amazon Gift Card (US)", "image": "https://cdn.wizzgift.com/product/amazon.png", "quantity": 1, "price": 25.75 }
        ]
      }
    }
    ```

    `orderInfo` is a JWT signed by WizzGift. Its payload repeats the quote (`totalAmount` in USD, `items`, the user's `email`, `iframeIntegrationId`, and `jti`, the quote id). You can decode it to show a confirmation, but you can't verify the signature: WizzGift checks it when you redeem.

    <Warning>
      Charge your user from the **token's** `totalAmount`, decoded on your server, never from a number sent by the browser. Check that its `iframeIntegrationId` is yours and `exp` is in the future, and accept each token (`jti`) for only one of your orders.
    </Warning>

    If you passed `email` and need the cards to go only to that address, compare the token's `email` claim with it before redeeming. The user controls the iframe URL and can change the address in the cart.
  </Step>

  <Step title="Redeem the quote">
    From your backend, after you have charged the user:

    ```bash theme={null}
    curl -X POST https://api.wizzgift.com/b2b/iframe-orders \
      -H "X-API-Key: wg_live_..." \
      -H "Content-Type: application/json" \
      -d '{
        "token": "eyJhbGciOiJIUzI1NiIs...",
        "partnerRef": "cashout_88213",
        "callbackUrl": "https://example.com/api/wizzgift/callback",
        "metaData": { "userId": "u_4410" }
      }'
    ```

    | Field | Description |
    | - | - |
    | `token` | Required. The `orderInfo` token. |
    | `partnerRef` | Recommended. Your own order id. Retrying with the same value is safe; reusing the token with a different value is refused. |
    | `callbackUrl` | Optional. https URL with a public hostname that receives the outcome. |
    | `metaData` | Optional. Object of up to 4 KB, echoed back in the callback as `meta_data`. |
    | `maxPriceIncreasePercent` | Optional, default `2`. The price is re-checked at redemption; a larger rise is refused. |

    The response is the order (`success: true` plus the fields of [`GET /b2b/orders/{checkoutId}`](/api-reference/introduction)). `201` means a new paid order; `200` means you retried a quote that was already redeemed for the same `partnerRef`.

    | Status | Code | What to do |
    | - | - | - |
    | `400` | `IFRAME_QUOTE_INVALID`, `IFRAME_QUOTE_EXPIRED` | Refund your user; they check out again. Quotes are valid for 30 days. |
    | `400` | `VALIDATION_ERROR` "Balance payment failed" | Top up your balance and retry with the same token. |
    | `403` | `FORBIDDEN` | The quote was issued for another integration id. |
    | `409` | `IFRAME_PRICE_CHANGED` | The price rose beyond your tolerance. Refund, or retry with a higher `maxPriceIncreasePercent`. |
    | `409` | `IFRAME_QUOTE_ALREADY_REDEEMED`, `IFRAME_QUOTE_CONSUMED` | The token already backs another order, or lapsed unpaid. Refund your user. |
    | `409` | `CONFLICT` | The same token is being redeemed concurrently. Retry in a few seconds. |
    | `422` | `B2B_LIMIT_EXCEEDED` | A tier spend limit was hit. See [limits and tiers](/guides/limits-and-tiers). |
  </Step>

  <Step title="Handle the callback">
    When the order reaches `completed`, `partial`, or `failed`, WizzGift sends a `POST` to your `callbackUrl`:

    ```json theme={null}
    {
      "checkoutId": "chk_7Qm2xLp9aR4t",
      "quoteId": "iq_Xn3kP0aQ9vBm2LcT",
      "partnerRef": "cashout_88213",
      "status": "partial",
      "meta_data": { "userId": "u_4410" },
      "link": "https://www.wizzgift.com/checkout/chk_7Qm2xLp9aR4t",
      "refund_details": { "status": "processing", "amount": 12.88, "currency": "USD", "paymentMethodId": "balance", "address": "" },
      "items": [
        {
          "id": "itm_1",
          "productId": "prod_amazon_us",
          "productName": "Amazon Gift Card (US)",
          "quantity": 2,
          "status": "partial",
          "fulfillments": [
            { "index": 0, "status": "completed", "fulfilledAt": 1753142400000 },
            { "index": 1, "status": "failed", "errorMessage": "Product temporarily unavailable" }
          ]
        }
      ],
      "timestamp": 1753142405000
    }
    ```

    * Verify `X-Wizzgift-Signature` with your `signingSecret`, using the same scheme and code as [webhook signatures](/webhooks/verify-signatures).
    * `X-Wizzgift-Event` is `order.completed`, `order.partial`, or `order.failed`.
    * Any non-2xx response is retried 3 more times (after about 3, 10, and 30 seconds). Return `2xx` once you have recorded the outcome, and make your handler idempotent.
    * WizzGift refunds failed units to your balance automatically. `refund_details.amount` is the value of every failed unit in USD: return that much to your user. For `failed`, return the full amount.

    <Tip>
      For the strongest guarantee, treat the callback as a signal and read the outcome from [`GET /b2b/orders/{checkoutId}`](/api-reference/introduction). Its `status` and `failedAmount` are authoritative, and its `externalRef` is `iframe:<quoteId>`, which ties the order to the token you redeemed.
    </Tip>
  </Step>
</Steps>

## Card codes and privacy

Cards from the embedded storefront belong to the end user. WizzGift emails them to the address entered in the iframe, and the user can also open them at the `link` above. Callbacks and the order API show you fulfillment status only; they never include card codes for these orders.

## Security checklist

* Accept `order-requested` only from `https://iframe.wizzgift.com` and from your own iframe's window.
* Derive the charge from the token on your server; keep one order per token (`jti`).
* Redeem only from your backend; keep your API key and `signingSecret` out of browser code.
* Use an https callback URL and verify every callback's signature before acting on it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.