Skip to content

Capture an order

Capture a full or partial payment against an authorized order with POST /captures, and attach the order lines and shipping details each capture covers.

Capture is the call that moves money from the customer to your bank account, and you make it when you fulfill. This page covers capturing in full and capturing in parts as you ship. It assumes you have an authorized order and credentials that can act on it.

Checkout authorizes: it reserves the money without taking it. Capture takes it. They are separate because you should only charge for what you actually ship, and the moment you know that’s fulfillment, not purchase. It’s also why partial capture exists rather than being an edge case — an order shipped in two parcels is two captures against one authorization, and the customer is charged as each one leaves.

You need the order_id from a completed checkout, and an API credential for the same environment the order was created in. Paths are relative to your base URL: https://api.playground.kustom.co/ while testing, https://api.kustom.co/ in production.

Send a Kustom-Idempotency-Key on every write. Every POST and PATCH to Order Management moves money or changes the order. Send a Kustom-Idempotency-Key header with one UUID per operation, and reuse it when you retry, so a call repeated after a timeout runs once instead of twice. Idempotency and retries has the rules and the retry schedule.

Capture the whole order once it ships.

POST /ordermanagement/v1/orders/{order_id}/captures
Kustom-Idempotency-Key: 6a1f7c3e-9b2d-4a11-8f0e-2c5d7b9a4e13
Content-Type: application/json
{
"captured_amount": 6000,
"reference": "c9a94cf1-7126-4f55-92fa-4d34f416ed7f",
"description": "Shipped the full order",
"order_lines": [
{
"type": "physical",
"reference": "123050",
"name": "Tomatoes",
"quantity": 10,
"quantity_unit": "kg",
"unit_price": 600,
"tax_rate": 2500,
"total_amount": 6000,
"total_tax_amount": 1200
}
],
"shipping_info": [
{ "shipping_company": "Postnord", "tracking_number": "1234567890" }
]
}

Only captured_amount is required, and for a full capture it must match the order’s order_amount. The rest is optional but worth sending: order_lines is what the customer and the Portal display as the contents of this capture, and shipping_info is required in practice for physical goods.

A successful capture returns 201 Created with a capture_id and a Location header. Use that Location as given rather than assembling a URL from the id:

GET /ordermanagement/v1/orders/{order_id}/captures/{capture_id}

Then read the order itself and check the arithmetic — captured_amount should have gone up by what you captured, and remaining_authorized_amount down by the same. That’s the check that catches a double capture, which is the failure an idempotency key exists to prevent.

Partial capture is the same endpoint with a smaller captured_amount — send the lines you’re actually shipping, and their shipping_info. Do this once per shipment. The remaining authorization stays available for the rest, and the order’s status becomes PART_CAPTURED.

Keep each capture’s order-line reference values: a later refund of something from that shipment sends the same references, so the credit lands on the right invoice — see Partial refund.

A capture larger than remaining_authorized_amount is rejected rather than clamped. So is a capture against an order that has expired — authorizations don’t last indefinitely, and once one expires the money is gone from your side of the transaction. If fulfillment is going to run long, extend before it expires rather than after.

Both of those come back as CAPTURE_NOT_ALLOWED, which is a statement about where the order is rather than about your request — so retrying it unchanged will fail identically. Read the order first. A capture id you no longer recognize gives NO_SUCH_CAPTURE. List the order’s captures rather than reconstructing the id.

An order with a very large number of lines can exceed the accepted body size and return PAYLOAD_TOO_LARGE. Sustained traffic above your account’s limit returns TOO_MANY_REQUESTS, and a Kustom-side failure returns TEMPORARILY_UNAVAILABLE — the last two are worth retrying with backoff, the rest never are.

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