Skip to content

Charge a customer token

Charge a stored customer token through the Customer Token API, with no checkout and no customer present, and tell declines worth retrying from permanent ones.

Every charge after the first one goes to the Customer Token API. No checkout is rendered, the customer doesn’t have to be present and the result is a real order that lands in Order Management like any other.

You need a recurring_token from a completed order — see creating a customer token at checkout — and the merchant ID that created it. A token can’t be charged under a different MID.

POST /customer-token/v1/tokens/{customerToken}/order
Authorization: Basic <token>
Content-Type: application/json
{
"merchant_reference1": "45aa52f387871e3a210645d4",
"auto_capture": true,
"purchase_currency": "EUR",
"order_amount": 999,
"order_tax_amount": 0,
"order_lines": [
{
"type": "digital",
"reference": "19-402",
"name": "Streaming Service Monthly",
"quantity": 1,
"unit_price": 999,
"tax_rate": 0,
"total_amount": 999,
"total_discount_amount": 0,
"total_tax_amount": 0
}
]
}

auto_capture defaults to false, which leaves the charge authorized but not collected. For a subscription you almost always want true, which captures the full amount as soon as the order is created. Without it, each charge waits for you to capture it through Order Management, in a single capture.

Send a shipping_address whole or not at all. Left out, the order ships to the address stored with the token. Sent, it must carry every one of given_name, family_name, email, street_address, postal_code, city and country — an empty string counts as missing — or the charge is refused with 400 BAD_VALUE, naming the first field it found missing: Bad value: shipping_address.email. There’s no merging with the stored address, so don’t send only the field that changed.

Use the right token. Nothing in the API can tell you that you charged the wrong customer. Only your own bookkeeping can.

A success is small:

{
"order_id": "a89ec121-1276-419d-882a-c343d58fd1bc",
"fraud_status": "ACCEPTED",
"authorized_payment_method": {
"type": "card"
}
}

A 200 always carries fraud_status: ACCEPTED, so there’s nothing to branch on there. If you sent merchant_urls.confirmation, the response also echoes it back as redirect_url.

Store the order_id against your own record of the charge. It’s how you find the order again in Order Management, and it’s the only handle you get.

3. Decide what a failure means before you retry

Section titled “3. Decide what a failure means before you retry”

Only one failure carries an error code that describes the charge. Most of the rest come back as a bare status with no body, so branch on the status first:

  • 403 with PAYMENT_METHOD_FAILED is worth retrying. The method behind the token didn’t work this time. Cards recover from temporary declines — insufficient funds, an issuer blip — so back off and try again rather than cancelling the subscription on the first miss. Keep the correlation_id from the body for support.
  • An empty 400 is permanent. It means the token is no longer active — it was cancelled — or recurring isn’t enabled on your merchant account. Retrying will never succeed. Read the token to tell which: a cancelled token reads back with status: CANCELLED.
  • An empty 404 means there is no such token. Check the id you stored rather than retrying. A token that exists but belongs to another merchant ID answers 400 with BAD_VALUE instead.
  • An empty 500 is ours, not theirs. Don’t involve the customer, but don’t assume nothing happened either — the outcome is unknown, so check for the order in the Kustom Portal before you charge again.

When retries on a PAYMENT_METHOD_FAILED keep failing, the token isn’t going to start working on its own. The only fix is asking the customer to update their payment method — which means a new first purchase and a new token, not a repair of the old one. Design that path before you need it: a subscription that silently stops charging is worse than one that asks.

StatusBodyMeaningRetry?
403PAYMENT_METHOD_FAILEDThe charge failedYes, with backoff, then ask the customer to update
400emptyThe token is cancelled, or recurring isn’t enabled on your accountNo
400BAD_VALUEA field in the request is invalid (named in error_messages) — including any shipping_address field left empty — or the token belongs to another merchant IDNot until you fix the request
404emptyNo such tokenNo
500emptyKustom faultOnly after checking the order wasn’t created

The error catalogue carries the coded errors with the causes behind each one.

In Playground:

  1. POST a charge against a token you created and confirm you get an order_id and fraud_status: ACCEPTED.
  2. Confirm the resulting order appears in Order Management, captured if you sent auto_capture: true.
  3. Charge a token id you’ve deliberately mangled and confirm your code handles the empty 404 without retrying forever.
  4. Cancel a token, charge it and confirm the empty 400 stops your retries.

This page is also available as Markdown — append .md to its URL — and is indexed in llms.txt.