Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to use Stripe Terminal for in-person payments

Stripe Terminal lets you accept in-person card payments using physical card readers. Set up Terminal in the Dashboard, register a reader, create a connection token from your server, and use the Stripe Terminal SDK to collect payments. All transactions flow through your existing Stripe account alongside your online payments.

What you'll learn

  • How to set up Stripe Terminal and register a card reader
  • How to create connection tokens for reader authentication
  • How to collect an in-person payment using the Terminal SDK
  • How to handle payment confirmation and receipts
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate6 min read30 minutesStripe Terminal SDK (JavaScript, iOS, Android), BBPOS WisePOS E, Stripe Reader S700March 2026RapidDev Engineering Team
TL;DR

Stripe Terminal lets you accept in-person card payments using physical card readers. Set up Terminal in the Dashboard, register a reader, create a connection token from your server, and use the Stripe Terminal SDK to collect payments. All transactions flow through your existing Stripe account alongside your online payments.

Accepting In-Person Payments with Stripe Terminal

Stripe Terminal extends your Stripe integration to physical locations. You connect a card reader, create a PaymentIntent, and collect the payment in person. The reader handles chip, tap (NFC), and swipe payments. All transactions appear in the same Stripe Dashboard as your online payments, giving you a unified view of your revenue.

Prerequisites

  • A Stripe account with Terminal enabled
  • A compatible Stripe card reader (BBPOS WisePOS E or Stripe Reader S700)
  • A backend server running Node.js with Express
  • The Stripe Terminal JavaScript SDK or a mobile SDK for iOS/Android

Step-by-step guide

1

Order and register a card reader

Go to Stripe Dashboard → Terminal → Readers. Order a reader if you do not have one (Stripe ships directly). Once received, the reader appears in your Dashboard after powering on and connecting to Wi-Fi.

Expected result: Your card reader appears as 'Online' in the Terminal → Readers section of the Dashboard.

2

Create a connection token endpoint

The Terminal SDK needs a connection token to authenticate with Stripe. Create a server endpoint that generates one. This token is short-lived and must be fetched fresh each time.

typescript
1// server.js
2const express = require('express');
3const Stripe = require('stripe');
4const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
5const app = express();
6app.use(express.json());
7
8app.post('/api/connection-token', async (req, res) => {
9 try {
10 const token = await stripe.terminal.connectionTokens.create();
11 res.json({ secret: token.secret });
12 } catch (err) {
13 res.status(500).json({ error: err.message });
14 }
15});
16
17app.listen(3001);

Expected result: The endpoint returns a connection token secret that the Terminal SDK uses to connect to the reader.

3

Initialize the Terminal SDK

Load the Stripe Terminal JavaScript SDK and initialize it with a function that fetches the connection token from your server.

typescript
1const terminal = StripeTerminal.create({
2 onFetchConnectionToken: async () => {
3 const res = await fetch('/api/connection-token', { method: 'POST' });
4 const data = await res.json();
5 return data.secret;
6 },
7 onUnexpectedReaderDisconnect: () => {
8 console.log('Reader disconnected unexpectedly');
9 }
10});

Expected result: The Terminal SDK is initialized and ready to discover and connect to readers.

4

Discover and connect to a reader

Use the SDK to discover available readers on your network and connect to one. The Internet discovery method finds readers registered to your Stripe account.

typescript
1const discoverResult = await terminal.discoverReaders({
2 simulated: false // set to true for testing without a physical reader
3});
4
5if (discoverResult.error) {
6 console.error('Discovery failed:', discoverResult.error);
7} else {
8 const reader = discoverResult.discoveredReaders[0];
9 const connectResult = await terminal.connectReader(reader);
10 if (connectResult.error) {
11 console.error('Connection failed:', connectResult.error);
12 } else {
13 console.log('Connected to:', connectResult.reader.label);
14 }
15}

Expected result: The SDK connects to your card reader. The reader's status changes to connected.

5

Create a PaymentIntent and collect payment

Create a PaymentIntent on the server, then use the Terminal SDK to collect the payment on the reader. The customer taps or inserts their card on the reader.

typescript
1// Server: create PaymentIntent
2app.post('/api/create-terminal-intent', async (req, res) => {
3 const intent = await stripe.paymentIntents.create({
4 amount: req.body.amount, // in cents
5 currency: 'usd',
6 payment_method_types: ['card_present'],
7 capture_method: 'manual' // optional: authorize first, capture later
8 });
9 res.json({ client_secret: intent.client_secret });
10});
11
12// Client: collect payment
13const res = await fetch('/api/create-terminal-intent', {
14 method: 'POST',
15 headers: { 'Content-Type': 'application/json' },
16 body: JSON.stringify({ amount: 2000 })
17});
18const { client_secret } = await res.json();
19
20const collectResult = await terminal.collectPaymentMethod(client_secret);
21if (collectResult.error) {
22 console.error('Collection failed:', collectResult.error);
23} else {
24 const confirmResult = await terminal.confirmPaymentIntent(collectResult.paymentIntent);
25 if (confirmResult.error) {
26 console.error('Confirmation failed:', confirmResult.error);
27 } else {
28 console.log('Payment succeeded:', confirmResult.paymentIntent.id);
29 }
30}

Expected result: The reader prompts the customer to tap or insert their card. After confirmation, the PaymentIntent status is 'succeeded' (or 'requires_capture' if using manual capture).

Complete working example

terminal-server.js
1// terminal-server.js
2// Node.js Express server for Stripe Terminal in-person payments
3
4const express = require('express');
5const Stripe = require('stripe');
6const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
7const app = express();
8
9app.use(express.static('public'));
10app.use(express.json());
11
12// Connection token for Terminal SDK
13app.post('/api/connection-token', async (req, res) => {
14 try {
15 const token = await stripe.terminal.connectionTokens.create();
16 res.json({ secret: token.secret });
17 } catch (err) {
18 res.status(500).json({ error: err.message });
19 }
20});
21
22// Create PaymentIntent for in-person payment
23app.post('/api/create-terminal-intent', async (req, res) => {
24 try {
25 const intent = await stripe.paymentIntents.create({
26 amount: req.body.amount,
27 currency: 'usd',
28 payment_method_types: ['card_present'],
29 description: req.body.description || 'In-person payment'
30 });
31 res.json({ client_secret: intent.client_secret, id: intent.id });
32 } catch (err) {
33 res.status(400).json({ error: err.message });
34 }
35});
36
37// Capture a PaymentIntent (if using manual capture)
38app.post('/api/capture-intent/:id', async (req, res) => {
39 try {
40 const intent = await stripe.paymentIntents.capture(req.params.id);
41 res.json({ status: intent.status });
42 } catch (err) {
43 res.status(400).json({ error: err.message });
44 }
45});
46
47// Register a new reader location
48app.post('/api/create-location', async (req, res) => {
49 try {
50 const location = await stripe.terminal.locations.create({
51 display_name: req.body.name,
52 address: {
53 line1: req.body.line1,
54 city: req.body.city,
55 state: req.body.state,
56 postal_code: req.body.postal_code,
57 country: req.body.country
58 }
59 });
60 res.json({ location_id: location.id });
61 } catch (err) {
62 res.status(400).json({ error: err.message });
63 }
64});
65
66app.listen(3001, () => console.log('Terminal server on port 3001'));

Common mistakes when using Stripe Terminal for in-person payments

Why it's a problem: Using 'card' instead of 'card_present' as the payment method type

How to avoid: In-person Terminal payments use 'card_present', not 'card'. The 'card' type is for online payments only.

Why it's a problem: Not creating a connection token endpoint

How to avoid: The Terminal SDK requires a fresh connection token for each session. Create a server endpoint that calls stripe.terminal.connectionTokens.create().

Why it's a problem: Forgetting to call confirmPaymentIntent after collectPaymentMethod

How to avoid: collectPaymentMethod only reads the card. You must call confirmPaymentIntent to actually process the payment.

Why it's a problem: Testing without a simulated reader when no physical reader is available

How to avoid: Set simulated: true in discoverReaders to use a simulated reader during development.

Best practices

  • Use simulated readers during development and only connect to physical readers in staging and production
  • Create a Terminal Location for each physical store to organize readers in the Dashboard
  • Handle the onUnexpectedReaderDisconnect callback to automatically attempt reconnection
  • Use manual capture_method if you need to authorize first and charge later (e.g., restaurants adding a tip)
  • Store the PaymentIntent ID for each transaction to enable refunds and lookups
  • Test with the physical reader in test mode — use test card 4242424242424242 on a real card would not work, but the simulated reader accepts any input

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I want to accept in-person card payments using Stripe Terminal with a BBPOS WisePOS E reader. Show me how to create a connection token endpoint in Node.js, initialize the Terminal SDK, discover and connect to a reader, and collect a payment by creating a PaymentIntent with card_present and confirming it through the reader.

Stripe Prompt

Build a Node.js Express server for Stripe Terminal with endpoints for connection tokens and PaymentIntents. Show the client-side code to initialize StripeTerminal, discover readers, connect to one, collect a payment method, and confirm the PaymentIntent.

Frequently asked questions

Which card readers work with Stripe Terminal?

Stripe Terminal supports the BBPOS WisePOS E (countertop, Wi-Fi), Stripe Reader S700 (touchscreen, Wi-Fi/Ethernet), and BBPOS Chipper 2X BT (mobile, Bluetooth). Availability varies by country.

Can I use Stripe Terminal without internet?

Stripe Terminal requires an internet connection for payment processing. There is no offline mode — the reader must be connected to process transactions.

How do I test Stripe Terminal without a physical reader?

Set simulated: true when calling discoverReaders. The simulated reader mimics the behavior of a real reader, including presenting cards and confirming payments.

Are Terminal payments shown in the same Stripe Dashboard as online payments?

Yes. All Terminal payments appear in the Payments section of your Stripe Dashboard alongside your online transactions. They are identified by the card_present payment method type.

What are the fees for Stripe Terminal?

Stripe Terminal charges 2.7% + 5 cents per successful in-person transaction in the US. International and currency conversion fees may apply. Check Stripe's pricing page for your country.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.