- Checkout
- Get started
What to send
The order payload, block by block — why one line per cart item rather than a total, the four amount identities Kustom validates but never computes, which options to pass and what belongs in a reference.
On this page you'll
- Know which of the four blocks each field belongs to
- Send order lines that survive the payment method's risk check, the customer's payment app, your TMS and a partial refund
- Get the four amount identities right the first time
| reference | quantity | unit_price | tax_rate | total_amount | total_tax_amount |
|---|---|---|---|---|---|
| tshirt-001 | 2 | 25000 | 2500 | 50000 | 10000 |
| chinos-001 | 1 | 70000 | 2500 | 70000 | 14000 |
| discount-10pct | 1 | -12000 | 2500 | -12000 | -2400 |
| order | 108000 | 21600 | |||
Three lines, one of them a negative discount. The last row is order_amount and order_tax_amount — the two columns above it, summed. Kustom validates both and rejects the order when they disagree; it never computes them for you. tax_rate carries two implicit decimals, so 2500 is 25%.
The shape. One order object, four blocks, each answering a different question. What’s
being bought — order_lines plus the totals that have to reconcile with them. Where —
purchase_country, purchase_currency and locale. Only the country is fixed at creation;
the other two can be changed on a later update. Where Kustom calls you back — merchant_urls. What the order can do —
options, the capability switch covered further down this page. Everything else is optional.
Get these four right and the rest is detail.
Why one line per item
Section titled “Why one line per item”Nothing in the API requires order_lines to be granular — Kustom accepts a single line
summing an entire cart. Nothing downstream forgives it.
order_lines[]
- The payment method's risk check
- What it does with them: It reads the lines when deciding whether to accept
- Cost of one aggregated line: Thinner input, lower acceptance
- The customer's payment app
- What it does with them: Where they review the purchase afterwards
- Cost of one aggregated line: "1 item · 250.00" is all they see
- Your TMS, via the Shipping Assistant
- What it does with them: Carriers, pickup points, delivery windows
- Cost of one aggregated line: Cannot route a box it cannot describe
- Kustom tax validation
- What it does with them: Validates tax per line
- Cost of one aggregated line: One rate only — a mixed-rate cart is rejected
- Capture and refund
- What it does with them: Both calls carry order_lines
- Cost of one aggregated line: No vocabulary for a partial refund of one item
Two of the five consumers above sit outside Kustom’s own validation entirely, which is why
the argument starts with conversion rather than mechanics. The payment method — Klarna today,
and potentially others tomorrow — reads order_lines to assess risk and decide whether to
accept the purchase at all. There’s no Kustom risk engine standing between a merchant and the
payment method, only the lines sent to it. The same lines are also what the customer reviews
afterwards, inside their payment app rather than your confirmation page.
| What breaks on one aggregated line | Why |
|---|---|
| Lower acceptance from the payment method | The payment method’s risk assessment reads the array, and a flattened cart is thinner input |
| A receipt the customer doesn’t recognize | Their payment app shows the line you sent, not your product catalog |
| A box the TMS can’t route | The Shipping Service Callback API hands it order_lines, when the Kustom Shipping Assistant (KSA) is in use |
| A rejected order, not a warning | Kustom’s tax validation checks one tax_rate per line, and a mixed-rate cart has nowhere to put two |
| No partial refund | Capture and refund both carry order_lines, and an aggregated line has no vocabulary for “one of three” |
The recommendation follows directly: one order line per cart item, always. Shipping and fees
aren’t incidental to the cart — give each its own typed line rather than folding it into a
product line or into the order-level totals, for the same reason a product line stays
separate: something downstream needs to identify it as what it is. Line mechanics — required
fields, the sales_tax type, anonymizing a product name — are covered in
Order lines and tax.
Why amounts are strict
Section titled “Why amounts are strict”Kustom doesn’t compute these numbers for you — it validates the following and rejects the order if any one fails:
- Amounts are integers in minor units (250.00 SEK is
25000). order_amountequals the sum of everyorder_lines[].total_amount.order_tax_amountequals the sum of everyorder_lines[].total_tax_amount.- Per line,
total_amountequalsquantity×unit_price−total_discount_amount. - Per line,
total_tax_amountsits within ±1 oftotal_amount − total_amount × 10000 / (10000 + tax_rate). tax_ratecarries two implicit decimals, so 25% is2500.
The worked example at the top of this page is three lines that satisfy all six — including a
negative discount line, which is where the arithmetic is easiest to get wrong. A broken total
is the single largest source of a 400 on order creation, and the strictness follows directly
from the previous section: once a
line’s numbers leave the building — into the payment method’s risk assessment, a customer’s
payment app, a TMS, a partial refund — Kustom has no way to quietly fix them the way it might
if they stayed internal.
See What an order is for the object these amounts sit on, and
Troubleshooting Checkout for the exact error a broken identity
returns.
What belongs on the order, and what belongs on each line?
Section titled “What belongs on the order, and what belongs on each line?”A field belongs to the order once, or to every line — never both, and the two are easy to confuse on a first pass.
Per order:
- Who is buying
- Whether they are old enough
- Which addresses apply
- What they’ve consented to
- Where to call you back
Per line:
- What it is
- How many
- What it costs
- Its tax
- Its own reference
The mistake worth naming here: looking for a per-line age flag. There isn’t one — age
verification, like selling to businesses, lives in options, at the order level. Both are
covered under What options to pass below.
What to put in a reference
Section titled “What to put in a reference”Three places on an order hold data of yours, and they answer different questions.
| Slot | Lives on | Use it for |
|---|---|---|
order_lines[].reference | each line | Your SKU or catalog id for that item |
merchant_reference1, merchant_reference2 | the order | Your own order number, so a Kustom order can be found from your system and back |
merchant_data | the order, and each line | Anything of yours that only your own code reads |
The rule that matters more than the table: anything you’ll join on later has to be stable
and minted by you before the order exists, and must never be regenerated per cart. A
merchant_reference1 that’s a fresh value on every retry looks fine until a customer
double-submits, and you can no longer tell which of the two orders is the one your own system
already knows about.
The escape hatch, for an order created before your own order number exists yet:
merchant_reference1 can be set afterward, through Order Management, once you have something
to put there. See Your own data on an order for the
third slot — attachment — and the full detail on all three.
Where Kustom calls you back
Section titled “Where Kustom calls you back”merchant_urls carries four required URLs, and two of them cost money when they’re wrong.
confirmation is where the browser redirects the customer after purchase. push is the
server-to-server call that tells you an order exists even when the browser never gets there —
a closed tab, a crashed app, a customer who never sees your confirmation page at all. Both need
to be publicly reachable HTTPS with a real certificate. A self-signed one that worked in a
local test fails silently in Kustom’s hands, not yours.
The other two required URLs, terms and checkout, matter for a working checkout but not for
whether the purchase itself completes. Full detail, including the optional callback URLs, is
at Merchant URLs explained.
The example above sends a fifth. validation isn’t required, and it’s the only callback that
can still refuse a purchase: an order is created when the customer opens the checkout and bought
whenever they get around to pressing the button, and stock, a price or a fraud check can turn
between the two. Send it if anything about the order can stop being true in that window — and
note that skipping it isn’t the same as opting out, because an unanswered validation request
completes the purchase after ten seconds either way. Answer the
callbacks is where that decision, and the flag that
reverses the default, are covered. Server-side
callbacks has the payload and response shape for every URL in the list.
What options to pass
Section titled “What options to pass”| Pass it | When |
|---|---|
allowed_customer_types: ["person","organization"] | You sell to businesses. Availability is narrower than it looks |
verify_age: { minimum_age: 18 } | Age-restricted goods, NO/SE. Account must be enabled first. Prefer this over the two older identity-number options |
allow_separate_shipping_address | You ship somewhere other than you bill |
vat_removed | B2B summaries where VAT should not show |
| a custom checkbox | Legal requires explicit consent |
| a delivery note (≤255 chars) | You have something to say on the confirmation page |
Colors and corner radius are set in Kustom Portal. See the Checkout options reference for the rest of the field table, and Sell to businesses (B2B) and Age verification for the two rows above that need your account enabled first.
What not to send
Section titled “What not to send”- Addresses from your own guest-checkout form. Kustom drops them the moment the customer types their own into the checkout, so shipping to the form’s address rather than the customer’s is a bug waiting for the first mismatch.
- A
referenceregenerated on every cart. Mint it once and reuse it — see the mistake under references, above. - Personal data in
merchant_data. It’s returned to you exactly as sent and stored like any other field of yours, not a place built for anything a customer would expect Kustom, rather than you, to be careful with.
Prove it
Section titled “Prove it”curl -X POST 'https://api.playground.kustom.co/checkout/v3/orders' \
-u "$KUSTOM_MERCHANT_ID:$KUSTOM_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"purchase_country": "SE",
"purchase_currency": "SEK",
"locale": "en-SE",
"order_amount": 108000,
"order_tax_amount": 21600,
"order_lines": [
{
"type": "physical",
"reference": "tshirt-001",
"name": "Cotton T-Shirt",
"quantity": 2,
"quantity_unit": "pcs",
"unit_price": 25000,
"tax_rate": 2500,
"total_amount": 50000,
"total_discount_amount": 0,
"total_tax_amount": 10000
},
{
"type": "physical",
"reference": "chinos-001",
"name": "Slim Fit Chinos",
"quantity": 1,
"quantity_unit": "pcs",
"unit_price": 70000,
"tax_rate": 2500,
"total_amount": 70000,
"total_discount_amount": 0,
"total_tax_amount": 14000
},
{
"type": "discount",
"reference": "discount-10pct",
"name": "10% off",
"quantity": 1,
"quantity_unit": "pcs",
"unit_price": -12000,
"tax_rate": 2500,
"total_amount": -12000,
"total_discount_amount": 0,
"total_tax_amount": -2400
}
],
"options": {
"allow_separate_shipping_address": true
}
}'The docs proxy injects your session's credentials. In your own code, use your Playground API username and API password.
A 201 hands back an order_id and an html_snippet —
Create the order is where both get used.
Related
Section titled “Related”- Order lines and tax — line validation,
sales_taxlines, anonymizing product names - Checkout options — the full
optionsfield table - Merchant URLs explained — every URL, who calls it and when
- Your own data on an order — the three data slots in full
- Create the order — post this payload
This page is also available as Markdown — append .md to its URL — and is indexed in llms.txt.