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

# Primary Sales

Sell NFTs with payment collection, order verification, and minting—all in a single widget.

<img src="https://mintcdn.com/immutable-b818fae7/a-_df05Z16XVB-P7/images/checkout/primary-sales.webp?fit=max&auto=format&n=a-_df05Z16XVB-P7&q=85&s=b3276b5cb6294b14853b7c278bf7cb45" alt="Primary Sales Widget" noZoom style={{float: 'right', width: '200px', borderRadius: '8px', marginLeft: '24px', marginBottom: '16px'}} width="860" height="1300" data-path="images/checkout/primary-sales.webp" />

**When to use:**

* NFT drops and launches
* In-game item purchases
* Digital collectible storefronts

## How it works

1. **Select and connect** - The buyer chooses items in the Sale widget and connects their wallet.
2. **Quote** - The widget requests pricing from Immutable's API.
3. **Authorize and sign** - At checkout, Immutable authorizes the order and signs the purchase transaction.
4. **Pay** - The buyer pays. The signed transaction is submitted on-chain by their wallet for crypto, or by Transak for card payments.
5. **Mint and confirm** - The NFT is minted to the buyer's wallet as part of the on-chain transaction. Immutable then notifies your Confirmation webhook, if configured.

The widget handles wallet connection, insufficient-balance flows, and submitting the purchase transaction. Immutable's backend handles order validation, signing, and tracking confirmations. In Advanced mode, your backend supplies pricing and authorization through webhooks.

## Prerequisites

* [Hub project](/docs/products/hub/getting-started) with an environment
* [ERC-721](/docs/products/asset-contracts/erc721) or [ERC-1155](/docs/products/asset-contracts/erc1155) collection deployed

## Setup

<Steps>
  <Step title="Configure in Hub">
    Go to [Hub](https://hub.immutable.com) → **Primary Sales** to deploy a Primary Sales contract, configure payment currencies and payout addresses, and choose between Simplified or Advanced mode.
  </Step>

  <Step title="Integrate the widget">
    Mount the Sale widget in your frontend to handle the checkout flow.
  </Step>

  <Step title="Handle backend (Advanced mode only)">
    Implement webhook endpoints if using Advanced mode for dynamic pricing or custom logic.
  </Step>
</Steps>

## Widget integration

<Tabs>
  <Tab title="npm">
    ```bash theme={null}
    npm install @imtbl/sdk
    ```
  </Tab>

  <Tab title="yarn">
    ```bash theme={null}
    yarn add @imtbl/sdk
    ```
  </Tab>
</Tabs>

```typescript theme={null}
import { checkout } from '@imtbl/sdk';
import { Environment } from '@imtbl/sdk/config';

const { Checkout, WidgetType, WidgetTheme } = checkout;

const checkoutSDK = new Checkout({
  baseConfig: {
    environment: Environment.SANDBOX, // or Environment.PRODUCTION
    publishableKey: 'YOUR_PUBLISHABLE_KEY',
  },
});
```

```typescript theme={null}
const {
  CommerceFlowType,
  CommerceEventType,
  CommerceSuccessEventType,
  CommerceFailureEventType,
} = checkout;

export async function openSale(
  elementId: string,
  items: checkout.SaleItem[],
  environmentId: string
) {
  const widgets = await checkoutSDK.widgets({ config: { theme: WidgetTheme.DARK } });
  const widget = widgets.create(WidgetType.IMMUTABLE_COMMERCE);

  widget.mount(elementId, {
    flow: CommerceFlowType.SALE,
    items,
    environmentId,
  });

  widget.addListener(CommerceEventType.SUCCESS, (payload) => {
    if (payload.type === CommerceSuccessEventType.SALE_SUCCESS) {
      // payload.data: { paymentMethod, tokenIds, transactions, transactionId }
      console.log('Purchase complete:', payload.data);
      widget.unmount();
    }
  });

  widget.addListener(CommerceEventType.FAILURE, (payload) => {
    if (payload.type === CommerceFailureEventType.SALE_FAILED) {
      // payload.data: { reason, error, timestamp, paymentMethod, transactions, transactionId }
      console.error('Purchase failed:', payload.data);
      widget.unmount();
    }
  });

  widget.addListener(CommerceEventType.CLOSE, () => {
    widget.unmount();
  });

  return widget;
}

// Example items
const items: checkout.SaleItem[] = [
  {
    productId: 'starter-pack',
    name: 'Starter Pack',
    description: '10 cards + 500 gold',
    image: 'https://example.com/pack.png',
    qty: 1,
  },
];
```

### Parameters

| Parameter               | Type                      | Description                                                                                                                                |
| ----------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `flow`                  | `CommerceFlowType.SALE`   | Required. Selects the sale flow.                                                                                                           |
| `items`                 | `SaleItem[]`              | Items to purchase. Each item requires `productId`, `qty`, `name`, `image`, and `description`.                                              |
| `environmentId`         | `string`                  | Your Hub environment ID. Optional in the type, but required in practice—the widget warns and falls back to an empty value if it's missing. |
| `collectionName`        | `string`                  | Collection name shown in the widget UI. Optional in the type, but expected in practice.                                                    |
| `excludePaymentTypes`   | `SalePaymentTypes[]`      | Payment methods to hide: `crypto`, `debit`, `credit`.                                                                                      |
| `preferredCurrency`     | `string`                  | Overrides the backend's base settlement currency.                                                                                          |
| `excludeFiatCurrencies` | `string[]`                | Fiat currencies to exclude from the on-ramp.                                                                                               |
| `customOrderData`       | `Record<string, unknown>` | Custom key-value pairs attached to the order.                                                                                              |
| `walletProviderName`    | `WalletProviderName`      | Default wallet provider if none is supplied.                                                                                               |

### Events

Each event payload has a `type` discriminator and a `data` object. Check `payload.type` before reading `payload.data`, since `SUCCESS` and `FAILURE` also fire for other steps in the flow (for example `SALE_TRANSACTION_SUCCESS`).

| Event     | Description                                                                                                                                                              |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `SUCCESS` | A success step completed. For `CommerceSuccessEventType.SALE_SUCCESS`, `payload.data` is `{ paymentMethod, tokenIds, transactions: [{ method, hash }], transactionId }`. |
| `FAILURE` | A step failed. For `CommerceFailureEventType.SALE_FAILED`, `payload.data` is `{ reason, error, timestamp, paymentMethod, transactions, transactionId }`.                 |
| `CLOSE`   | User closed the widget. Payload is `{}`.                                                                                                                                 |

## Sale modes

<Tabs>
  <Tab title="Simplified">
    Create and manage products directly in Hub—no backend required.

    * Products created in Hub are automatically available for sale
    * Stock management handled by the widget
    * Token IDs auto-generated for ERC-721, specified for ERC-1155

    **Products API**

    **List products (read)**

    Storefronts and backends can fetch the catalog without authentication:

    ```
    # Testnet
    GET https://api.sandbox.immutable.com/v1/primary-sales/:environmentId/products

    # Mainnet
    GET https://api.immutable.com/v1/primary-sales/:environmentId/products
    ```

    **Create or update a product**

    Upserts a single product by ID (pricing, stock, metadata, linked collection and token rules, etc.). Same URL for create and update.

    ```
    # Testnet
    PUT https://api.sandbox.immutable.com/v1/primary-sales/:environmentId/product

    # Mainnet
    PUT https://api.immutable.com/v1/primary-sales/:environmentId/product
    ```

    `PUT` **replaces** the entire product record for that `product_id`. Send a JSON object with the shape below. A successful response returns the persisted product (same fields as **GET** `/products`).

    **Product**

    | Field         | Type    | Required | Description                                                                           |
    | ------------- | ------- | -------- | ------------------------------------------------------------------------------------- |
    | `product_id`  | string  | Yes      | Stable catalog identifier (for example `starter-pack`).                               |
    | `name`        | string  | Yes      | Display name.                                                                         |
    | `image`       | string  | Yes      | URL for product art or icon.                                                          |
    | `description` | string  | No       | Longer description shown in UIs that support it.                                      |
    | `status`      | string  | Yes      | `active` or `inactive`. Inactive products cannot be purchased in **Simplified** mode. |
    | `quantity`    | integer | Yes      | Stock available for sale (inventory).                                                 |
    | `pricing`     | array   | Yes      | One or more price rows (see **Pricing** below). At least one entry is required.       |
    | `collection`  | object  | Yes      | Which collection and token rules apply at mint (see **Collection** below).            |
    | `limits`      | object  | No       | Optional per-buyer caps (see **Limits** below).                                       |

    **Pricing** (`pricing[]`)

    Each element defines a price in one currency:

    | Field      | Type   | Required | Description                                                                               |
    | ---------- | ------ | -------- | ----------------------------------------------------------------------------------------- |
    | `currency` | string | Yes      | Currency code (for example `USDC`), aligned with currencies configured for Primary Sales. |
    | `amount`   | number | Yes      | Unit price in that currency for a single item.                                            |

    **Collection** (`collection`)

    | Field                | Type   | Required      | Description                                                                                                            |
    | -------------------- | ------ | ------------- | ---------------------------------------------------------------------------------------------------------------------- |
    | `collection_type`    | string | Yes           | `ERC721` or `ERC1155`. Must match how the linked collection was deployed.                                              |
    | `collection_address` | string | Yes           | NFT collection contract on chain (same collection linked to Primary Sales in Hub).                                     |
    | `token_id`           | string | ERC-1155 only | Fixed token ID for this product. **Required** for ERC-1155. Omit or leave unset for ERC-721 (mints use generated IDs). |

    **Limits** (`limits`)

    | Field                       | Type    | Required                | Description                                                                               |
    | --------------------------- | ------- | ----------------------- | ----------------------------------------------------------------------------------------- |
    | `enabled`                   | boolean | Yes if `limits` is sent | When `true`, per-recipient limits apply.                                                  |
    | `max_per_recipient_address` | integer | If enabled              | Maximum units one wallet may purchase; must be greater than zero when limits are enabled. |

    **Example (ERC-721)**

    ```json theme={null}
    {
      "product_id": "starter-pack",
      "name": "Starter Pack",
      "description": "10 cards + 500 gold",
      "image": "https://example.com/pack.png",
      "status": "active",
      "quantity": 500,
      "pricing": [{ "currency": "USDC", "amount": 9.99 }],
      "collection": {
        "collection_type": "ERC721",
        "collection_address": "0x0000000000000000000000000000000000000000"
      },
      "limits": { "enabled": false }
    }
    ```

    Use your real collection address from Hub in place of the placeholder `collection_address`.

    **Delete a product**

    Removes a product from the catalog for that environment.

    ```
    # Testnet
    DELETE https://api.sandbox.immutable.com/v1/primary-sales/:environmentId/product/:productId

    # Mainnet
    DELETE https://api.immutable.com/v1/primary-sales/:environmentId/product/:productId
    ```

    **Authentication**

    Listing products is public; **create, update, and delete require authentication**. Authentication requires either:

    | Method             | Header                                 | When to use                                                                                                                 |
    | ------------------ | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
    | **Bearer token**   | `Authorization: Bearer <access_token>` | Used when managing the product catalogue via Hub                                                                            |
    | **Secret API key** | `x-immutable-api-key: <secret_key>`    | Server-side scripts, automation, or backends using a [secret API key](/docs/products/hub/api-keys) issued for your project. |

    Navigate to the Settings page of the project environment to create a secret API key. Do not expose secret keys in client-side code. Only manage products from your backend or Hub.

    **Metadata for minted tokens:**

    * **ERC-1155**: Pre-define metadata for the Token ID before sales begin
    * **ERC-721**: Set up a [webhook in Hub](/docs/products/hub/webhooks) for `imtbl_zkevm_activity_mint` events, then generate metadata and call the [Metadata Refresh API](/docs/products/indexer/overview)
  </Tab>

  <Tab title="Advanced">
    Implement webhook endpoints for full control over pricing, stock, and order fulfillment.

    **When to use:**

    * Dynamic pricing based on user or market conditions
    * Complex inventory management
    * Custom eligibility rules
    * Integration with existing e-commerce systems

    In Hub you configure each webhook as its own URL (with an optional API key). Immutable's backend calls them server-to-server. A complete order in Advanced mode flows through these stages:

    1. **Quote** - The buyer selects items and the widget requests a quote. Immutable calls your Quote webhook for live pricing.
    2. **Review** - The buyer reviews the order and chooses a payment currency.
    3. **Authorize** - The buyer proceeds to pay. Immutable calls your Authorize webhook to reserve inventory and return token detail, then signs the purchase transaction.
    4. **Payment executes** - The signed transaction is submitted on-chain and the NFT is minted to the buyer's wallet.
    5. **Confirm** - Immutable calls your Confirmation webhook so you can fulfil or record the completed order.
    6. **Expired** - If the authorized order isn't paid before it expires, Immutable calls your Expiration webhook so you can release the reserved inventory.

    See [Webhooks](#webhooks) below for the full contract of each webhook.
  </Tab>
</Tabs>

## Webhooks

Primary Sales has two kinds of webhooks. You configure each one as an independent URL in Hub, each with its own optional API key, and Immutable's backend calls them server-to-server (sending the API key in the `Authorization` header). There is no shared base URL — the paths shown below are illustrative.

**Dynamic sale webhooks** — used only in **Advanced** (dynamic) sale mode:

* **Quote** — Immutable calls your backend for live pricing.
* **Authorize** — Immutable calls your backend to authorize and reserve the order.

In Simplified mode these are never called; pricing and authorization come from your Hub product catalog.

**Notification webhooks** — optional callbacks for order lifecycle events:

* **Confirmation** — sent in **both** Simplified and Advanced modes once the purchase transaction executes on-chain. Use it to fulfil, mint, or record the completed order.
* **Expiration** — sent when an authorized order expires without payment, in **Advanced mode only**. In Simplified mode, Immutable releases the reserved stock automatically and sends no callback.

<AccordionGroup>
  <Accordion title="Quote">
    Advanced mode only. Called when a user requests a price quote for items.

    **Method:** `POST` to your configured **Quote URL**.

    <Tabs>
      <Tab title="Request/Response">
        **Request:**

        ```json theme={null}
        {
          "recipient_address": "0x...",
          "products": [
            { "product_id": "starter-pack", "quantity": 1 }
          ]
        }
        ```

        `recipient_address` may be `null` when the buyer's wallet is not yet known.

        **Response:**

        ```json theme={null}
        {
          "products": [
            {
              "product_id": "starter-pack",
              "quantity": 1,
              "pricing": [
                {
                  "currency": "USDC",
                  "currency_type": "crypto",
                  "amount": 9.99
                },
                {
                  "currency": "IMX",
                  "currency_type": "crypto",
                  "amount": 5.5
                }
              ]
            }
          ],
          "totals": [
            {
              "currency": "USDC",
              "currency_type": "crypto",
              "amount": 9.99
            },
            {
              "currency": "IMX",
              "currency_type": "crypto",
              "amount": 5.5
            }
          ]
        }
        ```

        The buyer can pay in any settlement currency you've configured for the project in Hub, and the widget builds its currency options from that Hub configuration—**not** from this response. Your quote must therefore return both a per-product `pricing` row **and** a `totals` row for **every** configured currency. In the example above the project accepts both `USDC` and `IMX`, so each product is priced in both and `totals` carries a row for each.

        Each product's `pricing` entries cover that single product (unit price times its `quantity`). Each `totals` entry is the sum across all products for that currency.

        <Warning>
          `amount` **must be a JSON number** (for example `9.99`), not a string. Returning `"9.99"` causes the quote to be rejected with a `502`. `totals` is an **array** of per-currency rows, not an object. `currency_type` is `crypto` or `fiat`.

          Return a `pricing` and `totals` row for **every** currency configured in Hub. If a configured currency—especially the project's base currency—is missing from `totals`, the widget cannot price it and checkout fails when the buyer reaches the order review.
        </Warning>
      </Tab>

      <Tab title="Implementation">
        ```typescript theme={null}
        app.post('/quote', async (req, res) => {
          const { recipient_address, products } = req.body;

          // Validate products exist and are in stock
          const validatedProducts = await validateProducts(products);

          // Calculate pricing (can be dynamic based on user, time, etc.)
          // amount must be a number; include a row for every currency
          // configured in Hub, in both pricing and totals
          const pricing = await calculatePricing(validatedProducts);

          res.json({
            products: pricing.products, // [{ product_id, quantity, pricing: [{ currency, currency_type, amount }] }]
            totals: pricing.totals,     // [{ currency, currency_type, amount }] — one per configured currency
          });
        });
        ```
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="Authorize">
    Advanced mode only. Called when the user confirms the order and is ready to pay. Reserve inventory here.

    **Method:** `POST` to your configured **Authorize URL**.

    <Tabs>
      <Tab title="Request/Response">
        **Request:**

        ```json theme={null}
        {
          "spender_address": "0x...",
          "recipient_address": "0x...",
          "currency": "USDC",
          "products": [
            { "product_id": "starter-pack", "quantity": 2 }
          ]
        }
        ```

        `spender_address` and `custom_data` (an arbitrary object) are optional. The request does **not** include a reference, total, or token IDs — your backend generates those and returns them in the response. The `currency` is the one the buyer selected, and your response **must** echo it back.

        **Response:**

        ```json theme={null}
        {
          "reference": "order-123",
          "currency": "USDC",
          "products": [
            {
              "product_id": "starter-pack",
              "collection_address": "0x...",
              "contract_type": "ERC721",
              "detail": [
                { "token_id": "12345", "amount": 9.99 },
                { "token_id": "12346", "amount": 9.99 }
              ]
            }
          ]
        }
        ```

        The `detail` array is the heart of the response: **return one entry per token to be minted.** Immutable mints exactly one token for each `detail` entry, so the number of entries determines what the buyer receives. The example above authorizes `quantity: 2`, so it returns two `detail` entries.

        Each `detail[].amount` is the **per-token** price in the order currency. Immutable charges the buyer the **sum of every `detail[].amount`** across all products (here, `9.99 + 9.99 = 19.98`), so do not put a line total in a single entry. Set `token_id` per token: for `ERC721`, a unique ID for each token; for `ERC1155`, the collection's configured token ID.

        <Warning>
          Your backend **generates and returns** the `reference` here (max **32 characters**) — a longer reference is rejected. Each `detail[].amount` must be a JSON number, not a string (a string body is rejected as malformed with a `502`). `contract_type` is `ERC721` or `ERC1155`. There is no `authorized` boolean — a `2xx` response with a valid body means authorized; return a `4xx` with `{ "code": "...", "message": "..." }` to reject.
        </Warning>
      </Tab>

      <Tab title="Implementation">
        ```typescript theme={null}
        app.post('/authorize', async (req, res) => {
          const { spender_address, recipient_address, currency, products } = req.body;

          try {
            // Generate a reference (max 32 chars) and reserve inventory / token IDs.
            // Return one detail entry per token to mint (detail.length === quantity);
            // each detail.amount is the per-token price, summed into the order total.
            const reference = createReference(); // e.g. "order-123"
            const authorizedProducts = await reserveInventory(reference, products);

            // Store the order for later confirmation
            await createPendingOrder({
              reference,
              recipient_address,
              spender_address,
              currency,
              products: authorizedProducts,
              status: 'pending',
            });

            // Return the reserved token detail and pricing
            res.json({
              reference,
              currency, // must match the requested currency
              products: authorizedProducts, // [{ product_id, collection_address, contract_type, detail: [{ token_id, amount }] }]
            });
          } catch (error) {
            // Reject with a 4xx and an error body
            res.status(400).json({ code: 'INSUFFICIENT_STOCK', message: error.message });
          }
        });
        ```
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="Confirm">
    Sent in both Simplified and Advanced modes after the purchase transaction executes on-chain. Mint or fulfil the order here.

    **Method:** `POST` to your configured **Confirmation URL**.

    <Tabs>
      <Tab title="Request/Response">
        **Request:**

        ```json theme={null}
        {
          "reference": "order-123",
          "tx_hash": "0x...",
          "recipient_address": "0x...",
          "order": {
            "contract_address": "0x...",
            "total_amount": 9.99,
            "deadline": 1700000000,
            "created_at": 1700000000000,
            "currency": "USDC",
            "products": [
              {
                "product_id": "starter-pack",
                "quantity": 1,
                "collection_address": "0x...",
                "collection_type": "ERC721",
                "detail": [
                  { "token_id": "12345", "amount": 9.99 }
                ]
              }
            ]
          }
        }
        ```

        Order fields are nested under `order`. `tx_hash`, `recipient_address`, and `custom_data` are sent when available. `total_amount` and each `detail[].amount` are numbers; `deadline` and `created_at` are Unix timestamps (seconds and milliseconds respectively).

        **Response:**

        The response **body is ignored** — only the HTTP status code matters. Return `2xx` (or `204`) to confirm the order; a `404` marks it unconfirmed, and `5xx` triggers a retry. A simple acknowledgement is fine:

        ```json theme={null}
        {
          "reference": "order-123",
          "confirmed": true
        }
        ```
      </Tab>

      <Tab title="Implementation">
        ```typescript theme={null}
        app.post('/confirm', async (req, res) => {
          const { reference, recipient_address, tx_hash, order } = req.body;

          try {
            // Verify the transaction on-chain
            const tx = await provider.getTransaction(tx_hash);
            if (!tx || tx.to !== PRIMARY_SALES_CONTRACT) {
              throw new Error('Invalid transaction');
            }
            await tx.wait(1);

            // Mint NFTs via Minting API (token IDs come from order.products[].detail)
            for (const product of order.products) {
              for (const { token_id } of product.detail) {
                await mintNFT({
                  recipient: recipient_address,
                  contractAddress: product.collection_address,
                  tokenId: token_id,
                  metadata: await getMetadata(product.product_id),
                });
              }
            }

            // Update order status
            await updateOrderStatus(reference, 'completed');

            // Status code is what matters — 2xx confirms the order
            res.status(200).json({ reference, confirmed: true });
          } catch (error) {
            // Non-2xx (here 500) signals failure; 5xx is retried
            res.status(500).json({ reference, confirmed: false, reason: error.message });
          }
        });
        ```
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="Expired">
    Advanced mode only. Sent when an authorized order expires without payment. Release reserved inventory. In Simplified mode, Immutable restocks automatically and this webhook is not called.

    **Method:** `POST` to your configured **Expiration URL**.

    <Tabs>
      <Tab title="Request/Response">
        **Request:**

        ```json theme={null}
        {
          "reference": "order-123"
        }
        ```

        The expiry request contains **only** the `reference`. Look up the order by reference to find the products and inventory to release.

        **Response:**

        The response **body is ignored** — only the HTTP status code matters. Return `2xx` (or `204`) to acknowledge; `5xx` triggers a retry. A simple acknowledgement is fine:

        ```json theme={null}
        {
          "reference": "order-123",
          "expired": true
        }
        ```
      </Tab>

      <Tab title="Implementation">
        ```typescript theme={null}
        app.post('/expired', async (req, res) => {
          const { reference } = req.body;

          // Look up the order by reference, then release its reserved inventory
          const order = await getOrderByReference(reference);
          await releaseInventory(reference, order.products);

          // Update order status
          await updateOrderStatus(reference, 'expired');

          // Status code is what matters — 2xx acknowledges the expiry
          res.status(200).json({ reference, expired: true });
        });
        ```
      </Tab>
    </Tabs>
  </Accordion>
</AccordionGroup>

### Webhook security

Immutable authenticates every webhook call with the API key you configure for that webhook in Hub, sent in the `Authorization` header. Each webhook (quote, authorize, confirmation, expiration) has its own key. Reject any request whose `Authorization` header doesn't match:

```typescript theme={null}
// e.g. your quote webhook handler
app.post('/quote', (req, res) => {
  const authHeader = req.headers['authorization'];
  if (authHeader !== process.env.IMMUTABLE_QUOTE_API_KEY) {
    return res.status(401).send('Unauthorized');
  }
  // ... handle the webhook
});
```

## Payment options

### Crypto

Users can pay with tokens on Immutable Chain. The accepted tokens can be configured through Hub. Default currencies are USDC and IMX. Custom ERC-20 tokens must be whitelisted.

If users don't have enough tokens, the widget offers [swap](/docs/products/checkout/swap), [bridge](/docs/products/checkout/bridge), or [fiat onramp](/docs/products/checkout/onramp) options.

### Fiat

Card payments are handled through [Transak](https://transak.com), Immutable's fiat payment partner. When a buyer selects **Debit Card** or **Credit Card** in the widget, the purchase is completed in an embedded Transak checkout, which accepts cards as well as Apple Pay and Google Pay where the buyer's device and region support them.

Funds settle to your configured payout wallet in the sale's settlement currency (commonly USDC, depending on your Hub configuration). KYC may be required for larger amounts.

<Info>Card payments currently process one NFT per order.</Info>

## Limits

<Warning>
  To avoid issues with gas limits in a single transaction, it is recommended to split large orders into multiple transactions.
</Warning>

## Next steps

<CardGroup cols={2}>
  <Card title="Minting API" icon="wand-magic-sparkles" href="/docs/products/asset-contracts/minting-api">
    Mint NFTs from your backend
  </Card>

  <Card title="Hub Webhooks" icon="webhook" href="/docs/products/hub/webhooks">
    Configure webhooks for mint events
  </Card>

  <Card title="ERC-721 Contracts" icon="file-contract" href="/docs/products/asset-contracts/erc721">
    Deploy NFT collections
  </Card>

  <Card title="Orderbook" icon="arrow-right-arrow-left" href="/docs/products/orderbook/overview">
    Enable secondary trading
  </Card>
</CardGroup>
