Skip to content

Take a payment in person

Send a cart to an enrolled phone or terminal with POST /ipp/v1/sessions, handle the ipp.* callbacks and store the order_id that ipp.session.finalized carries.

When the customer is ready to pay, your backend creates a session — the cart plus the device in front of them. Kustom pushes it to that device, the clerk collects the payment, and callbacks tell you how it went. The one that matters carries the order_id of the order Kustom created.

  • At least one enrolled device and its device_id — or, in Playground, a simulated one.
  • A webhook endpoint configured for your account in the Kustom Portal. It’s one endpoint for the whole account, not one per session — see Webhooks.

POST /ipp/v1/sessions with the cart and the device_id. Kustom pushes the session to the device by push notification — you don’t poll for delivery.

  • All amounts are in minor units. 19900 is 199.00 in the session’s currency.
  • There’s no location_id on the session. The location comes from the device, which was bound to one when it enrolled.

Store the session_id from the response. There’s no order_id until the session is finalized.

Kustom POSTs lifecycle events to your webhook endpoint as the session moves. Answer 200 — Kustom takes anything else as a failure. Every in-person event is prefixed ipp., so you can route them apart from other Kustom webhooks arriving at the same endpoint.

ipp.session.created is asynchronous and can reach you before you’ve finished handling the create response. Correlate on session_id, which both carry, and make the handler idempotent.

3. Store the order from ipp.session.finalized

Section titled “3. Store the order from ipp.session.finalized”

That’s the event that matters. It carries the order_id of the order in Order Management:

{
"id": "kevt_62EvxyMEaAaHSGeoDTza47",
"merchant_id": "DM11917000",
"timestamp": "2026-06-05T18:33:39.608920367Z",
"type": "ipp.session.finalized",
"data": {
"session_id": "dc01aa86-fb99-4d05-97dc-e79a22650f9a",
"order_id": "ccf4e76a-f56d-4d3a-abf9-f4fe941f0a1f"
}
}

Link it to your own order record. From here it’s an ordinary order, and the Order Management API takes it by that order_id:

  • Capture it once the goods are handed over, if your capture method is manual.
  • Refund all or part of the amount.
  • Cancel it to void the transaction, while nothing is captured.

A failed payment needs nothing from your backend. ipp.payment.failed is information, not the end of the sale: the clerk retries on the device and the session carries on. Session states and callbacks has every state and payload.

Cancel the session from your backend at any point before it reaches a final state. Cancellation is final — no payment can be collected on that session afterwards.

To read a session’s current state without waiting for a callback, get the session.

Test without hardware runs this whole flow in Playground with a simulated device and card tap. You’re done when:

  • ipp.session.finalized reaches your handler and its order_id is linked to your order.
  • A declined tap reaches you as ipp.payment.failed without ending the sale.
  • A session you cancel arrives as ipp.session.cancelled.

Callbacks never arrive. The webhook endpoint is set per account in the Portal, not in the session call. Check it’s set, and that it answers 200.

A callback arrived before the create response was handled. Expected — ipp.session.created is asynchronous. Correlate on session_id.

The /ipp/v1 endpoints return 404 or 403. In-person payments isn’t enabled on the account. Ask your Kustom representative.

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