Getting started with the iOS SDK

    Set up the Stripe Terminal iOS SDK so that you can begin accepting in-person payments.

    Getting started with the iOS SDK requires five steps:

    1. Install the SDK in your app
    2. Configure your app
    3. Set up the connection token endpoint in your app and backend
    4. Initialize the SDK in your app
    5. Connect your app to the simulated reader

    Step 1: Install the SDK Client-side

    The iOS SDK is compatible with apps supporting iOS 9 and above. To install the SDK, follow these steps:

    1. If you haven't already done so, install a recent version of CocoaPods.
    2. If you don't have an existing Podfile, run the following command to create one:
      pod init
    3. Add this line to your Podfile:
      pod 'StripeTerminal', '1.0.2'
    4. Run the following command:
      pod install

    From now on, use the .xcworkspace file to open your project in Xcode, instead of the .xcodeproj file.

    Step 2: Configure your app Client-side

    To prepare your app to work with the Stripe Terminal SDK, make a few changes to your Info.plist file in Xcode.

    1. Enable location services with the following key-value pair.
      Privacy – Location When In Use Usage Description
      Key NSLocationWhenInUseUsageDescription
      Value Location access is required in order to accept payments.

      To reduce fraud risks associated with payments, and to minimize disputes, Stripe needs to know where payments occur. If the SDK can't determine the location of the iOS device, payments are disabled until location access is restored.

    2. Ensure that your app runs in the background and remains connected to the reader.
      Required background modes
      Key UIBackgroundModes
      Value bluetooth-central (Uses Bluetooth LE accessories)

      Setting the bluetooth-central background mode lets the reader remain in standby mode when your app is backgrounded, or when the iOS device is locked. Without this value, standby fails. When your app is running in the background, the reader can turn off automatically to conserve power.

    3. Pass app validation checks when submitting to the App Store.
      Privacy – Bluetooth Peripheral Usage Description
      Key NSBluetoothPeripheralUsageDescription
      Value Bluetooth access is required in order to connect to supported card readers.

      This is an example—you can rephrase the prompt for user permission in your app.


    4. iOS 13 SDK and later: Allow your app to display a Bluetooth permission dialog.
      Privacy - Bluetooth Always Usage Description
      Key NSBluetoothAlwaysUsageDescription
      Value This app uses Bluetooth to connect to supported card readers.

      iOS 13 introduces new, more granular permissions surrounding an app's use of Bluetooth peripherals. Any apps that use the device's Bluetooth APIs must include this key in their Info.plist file, or else they will crash on first launch.


    Save your app’s Info.plist. Now it’s configured correctly and ready for use with the Stripe Terminal SDK.

    Step 3: Set up the ConnectionToken endpoint Server-side Client-side

    Server-side

    To connect to a reader, your backend needs to give the SDK permission to use the reader with your Stripe account, by providing it with the secret from a ConnectionToken. Your backend should only create connection tokens for clients that it trusts.

    curl https://api.stripe.com/v1/terminal/connection_tokens \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -X POST
    
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    # In a new endpoint on your server, create a ConnectionToken and return the
    # `secret` to your app. The SDK needs the `secret` to connect to a reader.
    Stripe::Terminal::ConnectionToken.create
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    # In a new endpoint on your server, create a ConnectionToken and return the
    # `secret` to your app. The SDK needs the `secret` to connect to a reader.
    stripe.terminal.ConnectionToken.create()
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    // In a new endpoint on your server, create a ConnectionToken and return the
    // `secret` to your app. The SDK needs the `secret` to connect to a reader.
    stripe.terminal.connectionTokens.create();
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    // In a new endpoint on your server, create a ConnectionToken and return the
    // `secret` to your app. The SDK needs the `secret` to connect to a reader.
    \Stripe\Terminal\ConnectionToken::create();
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    // In a new endpoint on your server, create a ConnectionToken and return the
    // `secret` to your app. The SDK needs the `secret` to connect to a reader.
    Map<String, Object> params = new HashMap<String, Object>();
    ConnectionToken.create(params);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    // In a new endpoint on your server, create a ConnectionToken and return the
    // `secret` to your app. The SDK needs the `secret` to connect to a reader.
    params := &stripe.TerminalConnectionTokenParams{}
    connectiontoken, err := connectiontoken.New(params)
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    // In a new endpoint on your server, create a ConnectionToken and return the
    // `secret` to your app. The SDK needs the `secret` to connect to a reader.
    var options = new ConnectionTokenCreateOptions{};
    var service = new ConnectionTokenService();
    ConnectionToken connectionToken = service.Create(options);
    

    Client-side

    To give the SDK access to this endpoint, implement the ConnectionTokenProvider protocol in your app, which defines a single function that requests a ConnectionToken from your backend.

    import StripeTerminal
    
    // Example API client class for communicating with your backend
    class APIClient: ConnectionTokenProvider {
    
        // For simplicity, this example class is a singleton
        static let shared = APIClient()
    
        // Fetches a ConnectionToken from your backend
        func fetchConnectionToken(_ completion: @escaping ConnectionTokenCompletionBlock) {
            let config = URLSessionConfiguration.default
            let session = URLSession(configuration: config)
            guard let url = URL(string: "https://{YOUR BACKEND URL}/connection_token") else {
                fatalError("Invalid backend URL")
            }
            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            let task = session.dataTask(with: request) { (data, response, error) in
                if let data = data {
                    do {
                        // Warning: casting using `as? [String: String]` looks simpler, but isn't safe:
                        let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
                        if let secret = json?["secret"] as? String {
                            completion(secret, nil)
                        }
                        else {
                            let error = NSError(domain: "com.stripe-terminal-ios.example",
                                                code: 2000,
                                                userInfo: [NSLocalizedDescriptionKey: "Missing `secret` in ConnectionToken JSON response"])
                            completion(nil, error)
                        }
                    }
                    catch {
                        completion(nil, error)
                    }
                }
                else {
                    let error = NSError(domain: "com.stripe-terminal-ios.example",
                                        code: 1000,
                                        userInfo: [NSLocalizedDescriptionKey: "No data in response from ConnectionToken endpoint"])
                    completion(nil, error)
                }
            }
            task.resume()
        }
    }
    
    #import <StripeTerminal/StripeTerminal.h>
    
    // Example API client class for communicating with your backend
    @interface APPAPIClient : NSObject <SCPConnectionTokenProvider>
    
    // For simplicity, this example class is a singleton
    + (instancetype)shared;
    
    @end
    
    #import "APPAPIClient.h"
    
    @implementation APPAPIClient
    
    + (instancetype)shared {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _shared = [APPAPIClient new];
        });
        return _shared;
    }
    
    // Fetches a ConnectionToken from your backend
    - (void)fetchConnectionToken:(SCPConnectionTokenCompletionBlock)completion {
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
        NSURL *url = [NSURL URLWithString:@"https://{YOUR BACKEND URL}/connection-token"];
        if (!url) {
            NSAssert(NO, @"Invalid backend URL");
        }
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
        request.HTTPMethod = @"POST";
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            id jsonObject = nil;
            NSError *jsonSerializationError;
            if (data) {
                jsonObject = [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)kNilOptions error:&jsonSerializationError];
            }
            else {
                NSError *error = [NSError errorWithDomain:@"com.stripe-terminal-ios.example"
                                                     code:1000
                                                 userInfo:@{NSLocalizedDescriptionKey: @"No data in response from ConnectionToken endpoint"}];
                completion(nil, error);
            }
            if (!(jsonObject && [jsonObject isKindOfClass:[NSDictionary class]])) {
                completion(nil, jsonSerializationError);
                return;
            }
            NSDictionary *json = (NSDictionary *)jsonObject;
            id secret = json[@"secret"];
            if (!(secret && [secret isKindOfClass:[NSString class]])) {
                NSError *error = [NSError errorWithDomain:@"com.stripe-terminal-ios.example"
                                                     code:2000
                                                 userInfo:@{NSLocalizedDescriptionKey: @"Missing `secret` in ConnectionToken JSON response"}];
                completion(nil, error);
                return;
            }
            completion((NSString *)secret, nil);
        }];
        [task resume];
    }
    
    // ...
    
    @end
    

    This function is called whenever the SDK is initialized. It’s also called when a new ConnectionToken is needed to connect to a reader (for example, when your app disconnects from a reader). If the SDK is unable to retrieve a new ConnectionToken from your backend, connecting to a reader fails with the error from your server.

    Step 4: Initialize the SDK Client-side

    The Terminal class made available by the Stripe Terminal SDK exposes a generic interface for discovering readers, connecting to a reader, creating payments, and updating reader software.

    To get started, provide your ConnectionTokenProvider implemented in Step 3. You can only call setTokenProvider once in your app, and must call it before accessing Terminal.shared. We recommend calling setTokenProvider in your AppDelegate’s application:didFinishLaunchingWithOptions method. Alternatively, you can use dispatch_once in Objective-C, or a static constructor in Swift.

    import UIKit
    import StripeTerminal
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
            Terminal.setTokenProvider(APIClient.shared)
            // ...
            return true
        }
    
        // ...
    
    }
    
    #import "APPAppDelegate.h"
    #import <StripeTerminal/StripeTerminal.h>
    #import "APPAPIClient.h"
    
    @implementation APPAppDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        [SCPTerminal setTokenProvider:[APPAPIClient shared]];
        // ...
        return YES;
    }
    
    // ...
    
    @end
    

    Step 5: Connect to the simulated reader Client-side

    The Stripe Terminal SDK comes with a built-in simulated card reader, so you can develop and test your app without connecting to physical hardware. Whether your integration is complete or you’re just starting out, use the simulated reader to emulate all the Terminal flows in your app: connecting to a reader, updating reader software, and collecting payments.

    Note that the simulated reader does not provide a UI. After connecting to it in your app, you can see it working when calls to the Stripe SDK succeed.

    To use the simulated reader, call discoverReaders to search for readers, with the simulated option set to true. When discoverReaders returns a result, call connectReader to connect to the simulated reader.

    import UIKit
    import StripeTerminal
    
    class ReaderViewController: UIViewController, DiscoveryDelegate, TerminalDelegate {
    
        var discoverCancelable: Cancelable?
    
        // ...
    
        // Action for a "Connect Reader" button
        func connectReaderAction() {
            let config = DiscoveryConfiguration(deviceType: .chipper2X,
                                                discoveryMethod: .bluetoothProximity,
                                                simulated: true)
            self.discoverCancelable = Terminal.shared.discoverReaders(config, delegate: self, completion: { error in
                if let error = error {
                    print("discoverReaders failed: \(error)")
                }
                else {
                    print("discoverReaders succeeded")
                }
            })
        }
    
        // ...
    
        // MARK: DiscoveryDelegate
    
        func terminal(_ terminal: Terminal, didUpdateDiscoveredReaders readers: [Reader]) {
            // Just select the first reader in this example.
            guard let selectedReader = readers.first else { return }
            // Only connect if we aren't currently connected.
            guard terminal.connectionStatus == .notConnected else { return }
    
            // In your app, display the discovered reader(s) to the user.
            // Call `connectReader` with the selected reader.
            Terminal.shared.connectReader(selectedReader, completion: { reader, error in
                if let reader = reader {
                    print("Successfully connected to reader: \(reader)")
                }
                else if let error = error {
                    print("connectReader failed: \(error)")
                }
            })
        }
    }
    
    #import "APPReaderViewController.h"
    #import <StripeTerminal/StripeTerminal.h>
    
    @interface APPReaderViewController () <SCPDiscoveryDelegate, SCPTerminalDelegate>
    
    @property (nonatomic, nullable, strong) SCPCancelable *discoverCancelable;
    
    // ...
    
    @end
    
    @implementation APPReaderViewController
    
    // Action for a "Connect Reader" button
    - (void)connectReaderAction {
        SCPDiscoveryConfiguration *config = [[SCPDiscoveryConfiguration alloc] initWithDeviceType:SCPDeviceTypeChipper2X
                                                                                  discoveryMethod:SCPDiscoveryMethodBluetoothProximity
                                                                                        simulated:YES];
        self.discoverCancelable = [[SCPTerminal shared] discoverReaders:config delegate:self completion:^(NSError *error) {
            if (error != nil) {
                NSLog(@"discoverReaders failed: %@", error);
            }
            else {
                NSLog(@"discoverReaders succeeded");
            }
        }];
    }
    
    // ...
    
    #pragma mark - SCPDiscoveryDelegate
    
    - (void)terminal:(SCPTerminal *)terminal didUpdateDiscoveredReaders:(NSArray<SCPReader *> *)readers {
        // Just select the first reader in this example
        SCPReader *selectedReader = [readers firstObject];
        // Only connect if we aren't currently connected.
        if (terminal.connectionStatus != SCPConnectionStatusNotConnected) {
            return;
        }
    
        // In your app, display the discovered reader(s) to the user.
        // Call `connectReader` with the selected reader.
        [[SCPTerminal shared] connectReader:selectedReader completion:^(SCPReader *reader, NSError *error) {
            if (reader != nil) {
                NSLog(@"Successfully connected to reader: %@", reader);
            }
            else {
                NSLog(@"connectReader failed: %@", error);
            }
        }];
    }
    

    SDK updates

    Stripe periodically releases updates to the Stripe Terminal JavaScript SDK, the Stripe Terminal iOS SDK, and the Stripe Terminal Android SDK, which can include new functionality, bug fixes, and security updates. Update your integrated version of the Stripe Terminal JavaScript, iOS, or Android SDK as soon as a new version is available.

    Next steps

    Congratulations! You integrated the Stripe Terminal SDK into your app and connected to a simulated reader. Next, either continue your integration using the simulated reader to collect payments, or learn about physical reader types.

    Was this page helpful?

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page