Integrate Stripe Terminal into point-of-sale and property management systems
A sequential guide for POS and PMS partners covering integration setup, payment flows, offline mode, testing and best practice.
This guide covers each step of a Stripe Terminal integration for point-of-sale (POS) and property management system (PMS) partners. It includes integration setup, payment flow, offline mode, testing and best practice.
Stripe Terminal overview
A Stripe Terminal deployment consists of four components:
- Your POS or PMS application (web, mobile or desktop)
- Your backend
- A Stripe Terminal reader
- The Stripe Terminal SDK
The SDK facilitates communication between your POS application, the firmware running on the reader and the Stripe API so you can accept in-person payments the same way you accept online payments with Stripe.
Choose your integration
When choosing an integration shape, consider the following:
- Offline mode : If your users need to accept payments without an internet connection (store and forward), choose the Android SDK, iOS SDK, React Native SDK, Java SDK or .NET SDK. The JavaScript SDK and server-driven integration don’t support offline mode.
- Device connection type : If your users require a wired Ethernet connection, choose an SDK that supports the Stripe the related setting, the related setting WisePOS E or Stripe the related setting/the related setting readers.
- Reader selection : The iOS, Android and React Native SDKs support all readers, including Tap to Pay. The server-driven integration, Java SDK and .NET SDK support only smart readers (the related setting, the related setting, WisePOS E and Verifone devices).
| Capability | Mobile SDKs (iOS, Android, React Native) | JavaScript | Desktop SDKs (.NET, Java) | Server-driven |
|---|---|---|---|---|
| Offline payments | Yes | No | Yes | No |
| Stripe the related setting, the related setting, the related setting, the related setting WisePOS E, Verifone the relevant part of the product | Yes | Yes | Yes | Yes |
| the related setting WisePad 3, Stripe M2 | Yes (not USB) | No | .NET: No, Java: Yes | No |
| Tap to Pay | Yes | No | No | No |
For detailed guidance on each integration shape and reader, see set up a reader.
Server-driven integration: Your POS or PMS interacts with Stripe only through server-side Stripe SDK calls. The reader runs embedded Stripe software and connects to Stripe over the internet. Offline payments aren’t supported.
SDK integration: Your POS or PMS interacts with Stripe on both the client side (Terminal SDK) and server side (Stripe SDK). The reader connects to your POS over a local network and to Stripe over the internet. Offline payments are supported in this architecture.
Set up your Stripe account
Create a Stripe account
Create a Stripe account at dashboard.stripe.com/register. For development and testing purposes, you don’t need to activate your account for live payments.
Find your API keys
Retrieve your API keys from the Dashboard. Your application uses these keys to authenticate with Stripe.
Find your account ID
Your account ID appears on the Account details page. You need this for non-Connect configurations and when identifying your account to Stripe Support.
Set up Terminal readers
Order readers
Order hardware from the Stripe Dashboard. After receiving readers, register them before use. You only need to do this initial setup once.
For ordering and returns, see Order and return readers.
The WisePOS E, the related setting and the related setting are identical from a software perspective. The the related setting adds a cellular (4G/LTE) module for redundant connectivity, which reduces time spent in offline mode.
Register readers to locations
Stripe manages readers under Locations. You can create a location for each physical site, then register readers to those locations with the Stripe Dashboard or API.
- Dashboard: Go to the Terminal section, create a location, then click Register reader and enter the pairing code generated on your reader.
- API: First, create a Location object. Then use the returned location ID to register a Reader object with the pairing code from your device.
Accept card present payments
This section covers the key Stripe objects and SDK steps needed to collect card-present payments. Each subsection links to the relevant API documentation for implementation details.
Stripe objects
| Object | Description | Example use |
|---|---|---|
| PaymentIntent | Represents your intent to collect a payment. Tracks payment state through a multi-step state machine. | Processing a card payment during a transaction |
| SetupIntent | Guides you through saving a customer’s payment credentials for future use. | Saving a card without immediately charging it |
| PaymentMethod | Payment instrument used in a transaction. | Tokenising card details for one-time or saved use |
| Customer | Represents a shopper with one or more payment instruments. | Maintaining a single profile and fraud model per shopper |
Install the Terminal SDK
Follow the SDK installation instructions at Set up the Terminal SDK.
Authenticate with restricted API keys
Stripe Apps can use restricted API keys (RAKs) to authenticate with Stripe APIs. Each app gets a unique key with only the permissions it needs.
If you have an existing integration to migrate to a RAK app, see Migrate to a RAK app.
For Connect integrations, you can grant Connect permissions to your app so it can act on behalf of connected accounts. Use the Stripe Apps CLI plugin:
stripe apps grant connect-permission "PERMISSION_NAME" "EXPLANATION"
Then increment the app version and upload with stripe apps upload. For the full permissions reference, see Connect permissions.
Set application information
Set AppInfo when initialising both the Terminal SDK and the Stripe SDK. Stripe uses this for observability and troubleshooting. If you interact with both SDKs, set this value for each. See Identify your Plugin for instructions.
Use the following convention for the Application name:
POSNameMiddleware
Authenticate with connection Tokens
A connection Token grants your app access to a specific Stripe account and allows the Terminal SDK to connect to a Reader. Don’t cache or hardcode connection Tokens – the SDK calls your ConnectionTokenProvider whenever it needs to Authenticate or reconnect, so your provider must create and return a new Token for each Request.
- Your back end requests a connection Token from Stripe.
- Stripe returns a secret value.
- On the Client, implement a ConnectionTokenProvider that returns this secret.
See Connection Token for implementation details.
Initialise the SDK
Use the connection Token secret and a TerminalListener to initialise the Terminal SDK. Implement the TerminalListener interface to receive Status notifications, then pass it and the ConnectionTokenProvider to Terminal.InitTerminal.
Initialise the SDK once when your Application starts and maintain the connection throughout the day. Reconnect only if the app crashes, the device reboots or the Reader loses power.
See Initialise the SDK for implementation details.
Discover and connect to a Reader
After initialising the SDK, discover online Readers and connect to one.
See Connect to a Reader for implementation details.
Create and Collect a Payment
When a Customer checks out, create a PaymentIntent to start a new Payment session. You can create the PaymentIntent on the Client or Server side.
Caution
Server-side PaymentIntent creation isn’t supported in offline mode. If you plan to support offline payments, use Client-side creation.
Use test amounts to simulate different results during development.
- Client-side creation: See Create a PaymentIntent (Client-side).
- Server-side creation: See Create a PaymentIntent (Server-side). Retrieve the
client_secretfrom the PaymentIntent on your Server, pass it to the Client, then callretrievePaymentIntentfollowed bycollectPaymentMethod.
After creating the PaymentIntent, send it to the Reader to Collect Payment details. See Collect Payment details.
Once the Customer presents their Card, confirm the Payment. See Confirm a Payment. On success, the PaymentIntent transitions to succeeded in the Dashboard.
Note
Mobile wallet payments (Apple Pay, Google Pay, Samsung Pay) are handled as contactless EMV transactions. No additional parameters are required for Mobile wallet transactions. When saving a Mobile wallet for future transactions, review the Mobile wallet considerations.
Handle failed payments
When a Payment fails (for example, due to a Card Decline), the PaymentIntent transitions to requires_payment_method. Don’t attempt to confirm immediately – Collect the Payment method again before retrying.
See Handling failures for implementation details.
Retrieve and list PaymentIntents
After a Payment completes, you can retrieve the PaymentIntent from your back end. The object includes Card brand, funding type, last 4 digits, processing network, expiry and more.
- Retrieve a PaymentIntent
- List all PaymentIntents
Cancel a PaymentIntent or Reader action
You can cancel a PaymentIntent at any time before capture. See Cancel a Payment.
To cancel an in-progress Reader action, use the Cancel action API.
Process refunds
Linked refunds
Trigger refunds from your back end using the Refunds API or the Dashboard. Pass either the PaymentIntent ID or the latest Charge ID. This approach works for all Card networks except Interac.
Caution
Refunds are initially returned as succeeded and can later transition to failed asynchronously – this reflects how Card networks behave in production. Listen for refund.failed Webhook events to handle failures.
If webhooks aren’t available, poll on the following schedule: every 5 minutes for the first 2 hours, every 30 minutes for hours 2–24, every 4 hours for days 1–7, then daily through day 30. Stop polling once the Refund transitions to failed and check the failure_reason field for details. Use a maximum polling period of 30 days and implement exponential backoff.
Interac refunds
Interac refunds must be processed in-person – the Customer must present the original Card at the Reader. Initiate a Refund API call to send the PaymentIntent to the Reader, then Stripe processes the Refund when the Customer presents their Card.
See Refund an Interac Payment.
Unlinked refunds
Unlinked refunds let merchants Refund to a different Payment method than the original, or to Refund payments originally processed on another Payment service provider.
Note
Unlinked refunds are gated. Contact Stripe Support with your Stripe account ID to Request access.
The steps are:
- Create a Customer object .
- Collect a card-present Payment method using a SetupIntent .
- Retrieve the generated PaymentMethod ID from the SetupIntent response.
- Issue the Refund to the saved Payment method. See Unlinked refunds .
Collect offline payments
Offline mode activates automatically when the Reader loses internet connectivity. The SDK stores encrypted transactions locally on the Reader and forwards them to Stripe once connectivity is restored.
Prerequisites
- You must have previously connected to the same Reader using a valid connection Token within the last 24 hours, on the same local network used for that online connection.
- The Reader must remain on that network while offline – you can’t switch networks while offline.
- The reader’s software must have been updated within the last 30 days, or it requires an online connection before it can process offline payments.
Enable offline mode
Configure and enable offline mode on a Configuration object, either through the API or the Dashboard. Assign the configuration to a location, or update the account’s default configuration to apply it to all locations.
Handle offline events
Implement the IOfflineListener interface to receive events such as connectivity transitions and payment forwarding status. Pass this listener when initialising the SDK.
Create offline payments
When creating a PaymentIntent for offline use, set the offline_behavior parameter using a CreateConfiguration object:
| Behaviour | Description |
|---|---|
RequireOnline | The PaymentIntent must be processed online. Throws a TerminalException if the device is offline. |
PreferOnline | Creates the PaymentIntent offline if needed, but processes online if connectivity is available. |
ForceOffline | Forces an offline PaymentIntent regardless of connectivity. Use only when transaction speed is critical. |
See Create a PaymentIntent offline.
When offline, the PaymentIntent response returns id=null because no Stripe ID has been assigned yet.
Collecting the payment method doesn’t require extra parameters. Swiping is unavailable offline, and tapping is unavailable offline in SCA-regulated markets.
Confirming the PaymentIntent follows the same steps as the online flow. You must handle the Stripe-enforced US$10,000 offline transaction limit in your error handling.
Manage offline risk
To limit exposure from offline payments, track thresholds and enforce them in your POS logic using OfflinePaymentsCount and OfflinePaymentAmountsByCurrency. For smart readers, these are exposed on the reader status object; for mobile SDK and Tap to Pay integrations, they’re exposed on the SDK status object instead.
- Deny transactions above a per-transaction amount threshold.
- Deny transactions if the reader’s total stored amount exceeds a configured limit.
- Deny transactions if the number of stored transactions exceeds a configured count.
See Managing risk.
Monitor payment forwarding
Once connectivity is restored, the reader forwards stored PaymentIntents automatically. To monitor this:
- Check Terminal. OfflineStatus. Reader. NetworkStatus for the reader’s connection state.
- Check Terminal. OfflineStatus. Reader. OfflinePaymentsCount for the number of pending payments.
- Implement IOfflineListener to log forwarded and failed-to-forward payments.
See Monitor forwarding.
When offline, retrieve receipt details from paymentIntent.OfflineDetails.OfflineCardPresentDetails to print proof of purchase for customers. See Provide receipts.
Additional payment flows
Tap to Pay
Tap to Pay lets compatible iPhones and Android devices accept contactless payments directly, without a separate reader. Supported card types include American Express, Mastercard and Visa contactless cards, as well as NFC-based wallets (Apple Pay, Google Pay, Samsung Pay).
Note
Only the iOS, Android and React Native SDKs support Tap to Pay.
See Set up Tap to Pay.
Alternative payment methods
Smart readers can display QR codes for alternative payment methods like WeChat Pay, Affirm and PayNow, letting customers complete checkout on their mobile device.
Note
PayNow requires gated access. Contact Stripe Support with your Stripe account number to request access.
See Alternative payment methods.
Mail and telephone order (the related setting) payments
For use cases where the customer isn’t present – such as phone orders – you can accept mail order/telephone order (the related setting) payments through the Terminal SDK. The payment flow is similar to card-present, with additional parameters required.
See the related setting overview and the related setting payments.
Payment Links
Payment Links doesn’t use the Terminal SDK or readers. It uses the Stripe Payment Links API.
To implement:
- Set up a product catalogue, then create a Payment Link .
- Share the link with your customer.
- Optionally, limit the number of payments per link.
- Track interactions and payment status using URL parameters.
- Deactivate the link when it’s no longer needed.
Test your integration
Use a sandbox
Use a sandbox with separate API keys and data for all development and validation – don’t process live payments during integration development.
Use test card numbers
Before you go live, test every card brand you plan to support end to end. See Test Terminal for the full list of virtual and physical test cards, including the simulated reader and magic cent amounts to force specific outcomes.
While you can’t test offline mode with a simulated reader, you can test it using physical hardware:
- Enable a hotspot on a device separate from your POS device.
- Connect the POS device and reader to the hotspot.
- Remove the SIM card from the hotspot device to simulate an internet outage.
- Confirm that the POS device remains connected to the hotspot.
Stripe doesn’t currently provide a dedicated way to test mobile wallet payments. Because wallet transactions are handled as contactless EMV transactions, a successful contactless EMV test confirms that wallet payments work correctly.
Test webhooks
Use the Stripe CLI to forward webhooks to localhost and manually trigger events during development.
Test identity and address verification
Use Stripe’s default test values for identity and address verification during development.
Best practices
Subscribe to SDK changelog notifications
Watch the relevant GitHub repositories to receive SDK release notifications:
- iOS
- Android
- React Native
- Java
Use idempotency keys
Use idempotency keys to safely retry requests without creating duplicate payments. Use V4 UUIDs or another high-entropy random string. Keys can be up to 255 characters.
Manage API keys securely
As a best practice, we recommend using a back end to store user secrets and field client requests. However, since many POS integrations operate on-premises without a back end, oftentimes the Stripe user’s secret key must be present on the POS device to accept payments through Stripe. In that case, we recommend the following controls be put in place:
- User secrets are encrypted at rest or user secrets are held in the app’s secure storage
- Access to user secrets should be restricted only to those persons or workloads with a valid need to access that secret.
- There should be an audit trail for all changes to a secret.
Use metadata
Attach a unique local transaction ID to each PaymentIntent using the metadata field. This is especially important for offline payments, which don’t receive a Stripe ID until they’re forwarded – metadata lets you reconcile them once they appear in your Dashboard.
Configure webhooks
Configure webhooks to receive real-time notifications about payment updates. Verify webhook signatures using the Stripe-Signature header to prevent replay attacks.
Handle AVS for card-not-present flows
If you process card-not-present payments (such as mail order/telephone order (the related setting) or Payment Links) with cards issued by banks that support AVS, include billing address and postcode in each payment request to reduce declines.
Connect integrations
Collect platform fees
Use the application_fee_amount parameter when creating a PaymentIntent to collect a platform fee. Stripe automatically splits the transaction between the merchant and your platform balance. This parameter is also applied when offline payments are forwarded, so Connect fees work the same way in offline mode.
Refunds in Connect
When using the Stripe-Account header (direct charges), the merchant owns the funds. Refunds are pulled from the merchant’s Stripe balance. If the balance is zero, the refund fails unless you’ve enabled account debits for that connected account.
