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 the PaymentIntent. Every test account receives built-in simulated tmr_… IDs automatically; 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 built-in 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. If you are a Platform, send Ionic-Account: $CONNECTED_ACCOUNT_ID on every request in this guide. It selects the connected account that owns the PaymentIntent, Terminal, and resulting payment resources. A Merchant using its own Account key omits that header.

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

Every test account automatically owns two Ionic test Locations and three ready simulated Terminals. The fixture catalog is universal, but each Account gets its own loc_… and tmr_… IDs. This gives a Platform realistic Account and multi-location isolation without a separate provisioning integration. The profile 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

List the Account's test Terminals

Use the Platform test key and connected account header. Select a Terminal by test_profile, not by a hard-coded universal ID—the IDs are unique to each Account.
Store the selected tmr_… as TERMINAL_ID. List again for each connected Account; never reuse one Account’s Terminal ID for another Account.
2

Create a PaymentIntent

Create a fresh method-neutral PaymentIntent for the logical payment attempt, exactly as shown in the normal payment flow below.
3

Run the normal Terminal payment call

Call POST /terminals/{id}/process_payment_intent. No simulator-only request fields or helper call are needed, so this is the same integration you use with physical hardware.
4

Optionally override one PaymentIntent

Use the test helper only when a focused test needs a result other than the selected Terminal’s profile. This override wins for the exact Terminal and PaymentIntent once.
Supported override outcomes are succeeded, voided, declined, failed, canceled, unknown, and dispatch_not_started. Declined and failed outcomes require a compatible failure_code.
An override 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 override. Only one unconsumed override can be armed for an exact Terminal and PaymentIntent. With no matching override, the Terminal uses its built-in test_profile. 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. When refunds on the Terminal are on for your account, include terminal_id on a full refund before the batch settles to void the sale on the Terminal and print its receipt there; see Refunds on the Terminal. With automatic refund receipts on, a card-present refund that includes terminal_id prints its receipt on that Terminal; see Refund receipts.

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. The request has no Credit/Debit selector: the buyer presents an ordinary card and the eligible route is chosen automatically.
If PIN debit applies, the terminal prompts for PIN. The returned Terminal Payment reports payment_type as credit or debit once the tender type is confirmed.
3

Inspect the Terminal Payment

HTTP 202 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 terminal requests whose API reference lists Ionic-Account:
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. The header is route-specific, not a global account switch. Omit it for direct Merchant access and for any endpoint that does not document the header.

Test before going live

Use a test-mode key and the Account’s built-in simulated Terminals 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.

What’s next

Refund a completed payment

Send terminal_id with a refund to run it as a card-present refund on a terminal.

Read the Terminal object

Interpret a Terminal’s setup, connection, and capability states.

Run it for connected accounts

Process terminal payments for the connected accounts a platform serves.