Skip to main content
Terminal payments collect a card-present payment on a specific Terminal using the same PaymentIntent as online payments. The PaymentIntent carries the amount and currency; the terminal request selects the device and the tender. Use a simulated Terminal for repeatable automated testing, then run the same payment flow on a physical Terminal.
Terminal payments require an Ionic tmr_… ID owned by the same Merchant and mode as your secret key. You can create a simulated tmr_… with a test key; Ionic assigns physical Terminal IDs through the hardware provisioning flow. Send only the Ionic ID; never send a hardware serial number or device credential. See the Terminal object for its lifecycle, connection, and capability states.

Before you begin

You need:
  • a server-side Ionic secret key with payments:read and payments:write;
  • a Location in the same Merchant and mode, identified by loc_…;
  • a simulated test Terminal or an assigned physical Terminal that is ready for the requested tender;
  • the terminal’s Ionic ID, which starts with tmr_;
  • a unique Idempotency-Key for each mutating POST request; reuse a value only to retry the same request. The read-only batch refresh does not use one.
Keep keys on your backend. Your browser or POS client should call your server, and your server should call Ionic.

Request physical hardware

Create a provisioning request when a Merchant needs a physical Terminal. The request tracks hardware fulfillment and eventually points to the Ionic Terminal resource used by the payment API. The Terminal object guide explains how to interpret its independent setup, connection, and capability states.
1

Create or reuse a Location

Create the business Location where the Terminal will operate, or reuse an existing loc_… in the same Merchant and mode.
2

Request a Terminal

Use a secret key with terminals:write. Ionic selects and prepares the appropriate hardware; the request body contains only the Location, a merchant-facing label, and optional metadata.
Store the returned tprov_… as PROVISIONING_REQUEST_ID.
3

Track fulfillment

Retrieve the provisioning request until it becomes ready. If it becomes requires_action, show the additive required_actions values to the Merchant and continue retrieving the same request after they are complete.
The request can move through requested, processing, requires_action, ready, failed, or canceled. A ready request has a non-null terminal_id.
4

Check Terminal readiness

Retrieve the assigned tmr_… and begin payments only when its lifecycle status and the requested payment capability are both ready.
List provisioning requests with GET /terminal_provisioning_requests and Terminal inventory with GET /terminals. Both collections are cursor-paginated and scoped to the authenticated Merchant and test or live mode.

Test without physical hardware

A simulated Terminal is a durable Merchant resource for local development, CI, staging, and SDK integration tests. Create a test Location and Terminal once, then reuse their loc_… and tmr_… IDs across test runs. The Terminal is immediately ready for credit and PIN-debit requests and never creates a physical provisioning request. The test helper configures what the Terminal observes; it does not return a canned API response. Processing still creates and transitions normal Ionic PaymentIntent, Terminal Payment, Charge, PaymentMethod, Refund, batch, profile, item, idempotency, and webhook records. This makes status handling and resource relationships representative while keeping the card interaction deterministic.
1

Create or reuse a test Location

Use a test-mode secret key with locations:write. The key determines the Location’s mode; do not send livemode in the body. Test and live Locations are separate resources.
Store the returned loc_… as LOCATION_ID. A Location is Ionic’s record of your business location, not a hardware identifier. You can list, retrieve, and update it through the Location API; there is no delete endpoint.
2

Create a simulated Terminal

Use the same test-mode key with terminals:write and the Location from the previous step.
Store the returned tmr_… as TERMINAL_ID. You do not need to create a new Terminal for every test or payment.
3

Create a PaymentIntent

Create a fresh method-neutral PaymentIntent for the logical payment attempt, exactly as shown in the normal payment flow below. A scenario is scoped to one exact PaymentIntent and Terminal.
4

Arm the expected outcome

Arm one scenario before processing the PaymentIntent. This example arms a decline for insufficient funds.
Supported immediate outcomes are succeeded, voided, declined, failed, canceled, unknown, and dispatch_not_started. Declined and failed outcomes require a compatible failure_code.
5

Run the normal Terminal payment call

Call POST /terminals/{id}/process_payment_intent with the same Terminal and PaymentIntent IDs. The request has no simulator-only fields, so the same integration works with physical hardware.
A scenario persists until the next new command matching both IDs consumes it. Retrying that consumed command with the same idempotency key is safe; a different payment attempt cannot reuse the scenario. Only one unconsumed scenario can be armed for an exact Terminal and PaymentIntent. A command with no matching scenario returns a failed terminal_payment with failure_code: dispatch_not_started. To test reconciliation, set outcome to unknown and include a final resolution_outcome plus resolution_delay_seconds from 1 through 86400. Ionic first returns the attempt with an unknown result; retrieve that same terminal_payment until the configured delay passes. Ionic reconciles the result automatically, usually within about one second of the configured time. Never submit a second Sale to resolve an unknown result. Cancellation uses the normal POST /terminal_payments/{id}/cancel endpoint. An unresolved unknown scenario without a configured final result becomes canceled. If the scenario already has a later resolution_outcome, that result remains authoritative, which lets you test a late-approval race after cancellation was requested.

Simulate current batches

Configure a durable current-batch situation, then call the same batch refresh endpoint used with a physical Terminal. Keep a profile’s reference stable and submit that profile again to replace its items for the next observation. Profiles omitted from an update remain unchanged, so an open batch cannot disappear. Up to 10 profiles with 1,000 items per profile can be retained.
The refresh creates or updates normal terminal_batch, settlement-profile, and batch-item resources. With no configured batch scenario, a simulated Terminal reports an empty default profile instead of returning an unsupported operation. The simulator also supports the normal POST /refunds flow for a succeeded Terminal payment; refund resources and state transitions are handled by the same Ionic payment lifecycle.

Payment flow

1

Create a PaymentIntent

Create the same PaymentIntent used by other Ionic payment flows. Do not attach a payment_method, send a payment_token, or confirm it first. The intent must remain in requires_payment_method, use USD, and use automatic capture.
2

Process it on a terminal

Select the Ionic terminal ID in the route and pass the PaymentIntent ID in the request. payment_type selects the tender behavior on the terminal, credit or PIN-based debit. It is not a PaymentMethod type.
For PIN debit, send "payment_type": "debit". The selected terminal must have a ready PIN-debit capability.
3

Inspect the Terminal Payment

HTTP 200 means Ionic recorded the attempt and returned a terminal_payment; it does not by itself mean the payment was approved. Complete the order only when status is succeeded.

Handle every status

Declines and failures are returned as Terminal Payment resources with HTTP 200. Branch on status; treat failure_code as display detail, because new codes can appear without a new status value.

Failure codes

failure_code is nullable and appears only when Ionic has a known nonapproval. These are stable Ionic values; raw device responses are not returned. New failure_code values are additive. Use status as the workflow boundary and fall back to failure_message when a code is unfamiliar.

Handle API errors

An API error means Ionic could not return a normal Terminal Payment response. Most errors below happen before a Sale is sent, but a timeout or persistence failure can leave the financial outcome uncertain.
A client timeout or disconnected HTTP socket is not evidence that the Terminal payment failed. Reuse an idempotency key only for the identical route and body, inspect the PaymentIntent, and retrieve any known tpr_… before considering a new attempt. Include the response X-Request-ID when contacting support.

Retrieve an unresolved attempt

If the result is pending, in_progress, or unknown, retrieve the same tpr_… resource. Do not process the PaymentIntent again to resolve a timeout.
Retrieval reads Ionic’s latest durable state; the GET request does not itself send a command to the Terminal. Ionic can update an unknown attempt when the original payment reaches a confirmed result, and the resulting state is available through this GET and terminal-payment webhooks. Retrieval never submits a new Sale, so poll it as often as your integration requires.

Request cancellation

Cancel only an active attempt, and treat cancellation as a request rather than a financial outcome.
cancellation_requested: true confirms that Ionic recorded the request. Continue inspecting status: the payment can still be approved after you request cancellation. Refund a late approval separately if you no longer want the payment.

Platforms and connected Merchants

An authorized Platform acting for a connected Merchant adds the delegation header to every request:
The PaymentIntent, Terminal, Charge, and Terminal Payment must all belong to that account and use the same test or live mode. Identifiers that belong to another account or mode return 404.

Test before going live

Use a test-mode key and a reusable simulated Terminal for automated coverage. Cover an approval, decline, failed dispatch, delayed unknown result, retried request with the same Idempotency-Key, cancellation, refund, a batch refresh, and retrieval of the resulting resources. Then run acceptance testing on an Ionic-assigned physical test Terminal to validate the device, store network, PIN entry, and live account setup before enabling production hardware.
The simulator proves your integration and state handling; it does not certify a physical reader, PIN entry, receipt behavior, or store network. Keep physical-terminal acceptance testing as a separate release gate.