Checkouts
The Checkout API lets you create payment sessions, check their status, and list past checkouts.
Base URL: https://api.inzilink.com/api/v1
All requests require an API key in the Authorization header. See Authentication.
Create checkout
Creates a new payment session and returns a hosted checkout page URL.
POST /v1/checkoutsRequest body
| Field | Type | Required | Description |
|---|---|---|---|
amount | string | Yes | Payment amount. String to avoid float precision issues. |
currency | string | Yes | ISO 4217 code. Currently supported: USD. |
description | string | No | Shown on checkout page. Max 256 chars. |
metadata | object | No | Arbitrary JSON (max 4KB). Returned in webhooks untouched. |
customer_email | string | No | For future receipt functionality. |
redirect_url | string | No | Where to redirect user after payment. |
webhook_url | string | No | Override merchant-level default webhook URL. |
expires_in | integer | No | Seconds until expiration. Default: 1800. Min: 300, Max: 86400. |
customer_ref | string | No | Your id for the buyer. Max 128 chars. See References. |
product_ref | string | No | Your id for what is being bought. Max 128 chars. |
order_ref | string | No | Your id for this order. Makes creation idempotent. |
References
Three optional strings that let us tell your purchases apart. They are opaque to us — never validated, displayed, or resolved against anything. We only ever compare them to each other.
They are worth sending, and here is precisely what each one buys.
order_ref makes this endpoint idempotent. Call it twice with the same
order_ref, same amount and same currency, and you get the same checkout back
rather than a second one. A different amount always creates a new checkout —
so if you re-price an order and retry, the buyer is never shown a figure that
differs from what they will be charged.
customer_ref and product_ref rescue a payment we would otherwise have to
leave unattributed. Outside Base and Polygon, a checkout is identified by its
exact amount: $7.38 becomes $7.381, and the fractional part is the invoice
number. Wallets and exchange withdrawal forms routinely round it away.
If a customer has one live checkout and pays the round $7.38, we credit it.
If they have several at the same price — usually because they pressed pay more
than once — the round amount fits all of them, and we cannot tell which. That
payment goes uncredited until someone intervenes.
With both references present we can: checkouts naming the same customer and the same product at the same price are interchangeable, so crediting any one of them delivers what that person paid for. We require both. Two products can cost the same and be entirely different things, so a customer alone is not enough — and getting this wrong means shipping someone the wrong goods.
Make product_ref distinguish anything a buyer would notice. If one code
covers several variants, put the variant in the string: PLAN-A:7d and
PLAN-A:30d rather than PLAN-A for both.
{
"amount": "7.38",
"currency": "USD",
"customer_ref": "user_6b1f2c",
"product_ref": "PLAN-A:7d",
"order_ref": "ord_9d41e0"
}If you cannot add top-level fields, we also read them from metadata — see
Reading references from metadata below.
cURL
curl -X POST https://api.inzilink.com/api/v1/checkouts \
-H "Authorization: Bearer sk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"amount": "25.00",
"currency": "USD",
"description": "Order #12345",
"metadata": {
"order_id": "uuid-xxx",
"user_id": "uuid-yyy"
},
"redirect_url": "https://yoursite.com/thank-you",
"webhook_url": "https://yoursite.com/webhooks/inzi",
"expires_in": 1800
}'Response 201 Created
{
"id": "chk_live_abc123def456",
"status": "pending",
"amount": "25.00",
"currency": "USD",
"description": "Order #12345",
"metadata": { "order_id": "uuid-xxx", "user_id": "uuid-yyy" },
"checkout_url": "https://pay.inzilink.com/c/abc123def456",
"available_chains": [
{
"chain": "solana",
"name": "Solana",
"tokens": ["USDC"],
"estimatedConfirmSec": 1,
"gasTier": "low",
"priority": 0,
"badge": "Fastest & cheapest"
},
{
"chain": "polygon",
"name": "Polygon",
"tokens": ["USDC", "USDT"],
"estimatedConfirmSec": 24,
"gasTier": "low",
"priority": 1,
"badge": null
}
],
"expires_at": "2026-04-04T12:30:00Z",
"created_at": "2026-04-04T12:00:00Z"
}Redirect the user to checkout_url — a full page navigation, not an iframe.
The checkout page cannot be embedded. It sets frame-ancestors to the
Telegram domains only, so an <iframe> or a modal built on one renders blank
and the browser reports it only in the console. The page shows the amount and
the payment address, which is what clickjacking targets — Telegram Mini Apps
are the single exception, because they have no other way to open a page.
They’ll see a hosted payment page with chain/token selector, QR code, and copy-paste address.
available_chains is sorted by priority (0 = recommended). Use badge, gasTier, and estimatedConfirmSec to build a custom network selector in your own checkout UI. You can also call GET /v1/chains (public, no auth) to get chain metadata independently.
Amount tolerance: Inzi accepts payments within max(1%, $0.05) of the requested amount to account for network rounding.
Checking what we recorded
Both the create response and GET /v1/checkouts/{id} echo back what we stored,
so you can confirm the protection is on without asking us:
"references": {
"customer_ref": "user_6b1f2c",
"product_ref": "PLAN-A:7d",
"order_ref": "ord_9d41e0",
"ambiguity_resolution_armed": true
}ambiguity_resolution_armed is true when both customer and product are
present — the condition under which several matching checkouts can be
collapsed and credited. order_ref is observable anyway, since idempotency
shows itself; the other two do nothing visible until a payment goes wrong,
which is why they are reported here.
Reading references from metadata
If adding top-level fields is awkward, put the same values in metadata and we
will find them. This exists for a real case: some integrations redirect the
buyer to our hosted checkout page, so the call that starts the payment is made
by us, not by you — creation is the only moment you are on the wire.
We look for these keys, in order, and accept either snake_case or
camelCase:
| Reference | Keys read from metadata |
|---|---|
customer_ref | customer_ref, user_id |
product_ref | product_ref, package_code |
order_ref | order_ref, reference_id, order_id |
Top-level fields win if both are present. Numbers are accepted and read as strings, since user ids are often integers.
{
"amount": "7.38",
"currency": "USD",
"metadata": {
"user_id": "user_6b1f2c",
"package_code": "PLAN-A:7d",
"reference_id": "ord_9d41e0"
}
}Unknown keys in metadata are ignored, not rejected. Adding these cannot
break an existing integration.
Get checkout
Retrieve the current state of a checkout. Use as fallback when a webhook doesn’t arrive.
GET /v1/checkouts/{checkout_id}Response 200 OK
{
"id": "chk_live_abc123def456",
"status": "completed",
"amount": "25.00",
"currency": "USD",
"description": "Order #12345",
"metadata": { "order_id": "uuid-xxx", "user_id": "uuid-yyy" },
"checkout_url": "https://pay.inzilink.com/c/abc123def456",
"payment": {
"amount_crypto": "25.000000",
"crypto_currency": "USDT",
"network": "ton",
"tx_hash": "abc123...",
"sender_address": "UQ...",
"confirmed_at": "2026-04-04T12:05:00Z"
},
"expires_at": "2026-04-04T12:30:00Z",
"created_at": "2026-04-04T12:00:00Z"
}The payment object is null when status is pending or expired.
Checkout statuses
| Status | Description |
|---|---|
pending | Awaiting payment |
processing | Payment detected, awaiting confirmations |
completed | Payment confirmed on-chain |
expired | Not paid before expires_at |
underpaid | Received less than required amount (customer can send more to the same address) |
List checkouts
Paginated list of your checkouts. Useful for dashboards and reconciliation.
GET /v1/checkouts?status=completed&limit=20&offset=0Query parameters
| Param | Type | Description |
|---|---|---|
status | string | Filter by status. Optional. |
limit | integer | Max results. Default: 20, Max: 100. |
offset | integer | Pagination offset. |
created_after | datetime | Filter: created after this ISO 8601 timestamp. |
created_before | datetime | Filter: created before this ISO 8601 timestamp. |
Response 200 OK
{
"data": [
{ "id": "chk_live_abc123", "status": "completed", "amount": "25.00", "..." : "..." },
{ "id": "chk_live_def456", "status": "pending", "amount": "10.00", "..." : "..." }
],
"total": 142,
"limit": 20,
"offset": 0
}