When to use:
- NFT drops and launches
- In-game item purchases
- Digital collectible storefronts
How it works
- Select and connect - The buyer chooses items in the Sale widget and connects their wallet.
- Quote - The widget requests pricing from Immutable’s API.
- Authorize and sign - At checkout, Immutable authorizes the order and signs the purchase transaction.
- Pay - The buyer pays. The signed transaction is submitted on-chain by their wallet for crypto, or by Transak for card payments.
- 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.
Prerequisites
- Hub project with an environment
- ERC-721 or ERC-1155 collection deployed
Setup
Configure in Hub
Go to Hub → Primary Sales to deploy a Primary Sales contract, configure payment currencies and payout addresses, and choose between Simplified or Advanced mode.
Widget integration
- npm
- yarn
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 atype 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
- Simplified
- Advanced
Create and manage products directly in Hub—no backend required.Create or update a productUpserts a single product by ID (pricing, stock, metadata, linked collection and token rules, etc.). Same URL for create and update.
Pricing (
Collection (
Limits (
Example (ERC-721)Use your real collection address from Hub in place of the placeholder AuthenticationListing products is public; create, update, and delete require authentication. Authentication requires either:
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:
- 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
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[])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)| 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)| 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. |
collection_address.Delete a productRemoves a product from the catalog for that environment.| 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 issued for your project. |
- ERC-1155: Pre-define metadata for the Token ID before sales begin
- ERC-721: Set up a webhook in Hub for
imtbl_zkevm_activity_mintevents, then generate metadata and call the Metadata Refresh API
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 theAuthorization 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.
- 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.
Quote
Quote
Advanced mode only. Called when a user requests a price quote for items.Method:
POST to your configured Quote URL.- Request/Response
- Implementation
Request: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
recipient_address may be null when the buyer’s wallet is not yet known.Response: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.Authorize
Authorize
Confirm
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.- Request/Response
- Implementation
Request: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:Expired
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.- Request/Response
- Implementation
Request: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:Webhook security
Immutable authenticates every webhook call with the API key you configure for that webhook in Hub, sent in theAuthorization header. Each webhook (quote, authorize, confirmation, expiration) has its own key. Reject any request whose Authorization header doesn’t match:
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, bridge, or fiat onramp options.Fiat
Card payments are handled through Transak, 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.Card payments currently process one NFT per order.
Limits
Next steps
Minting API
Mint NFTs from your backend
Hub Webhooks
Configure webhooks for mint events
ERC-721 Contracts
Deploy NFT collections
Orderbook
Enable secondary trading