iOS Integration
This guide covers the iOS-specific integration of the Tapkey Mobile SDK for local vehicle access via BLE.
Prerequisites
Section titled “Prerequisites”- Partner backend integration is available (assignments + webhooks)
- SDK Key available in the flinkey Portal (UAT)
- Mobile access context can be requested from your backend
- Real iOS test devices available (do not rely only on simulators for BLE)
Library dependencies
Section titled “Library dependencies”| Library | Purpose |
|---|---|
| Tapkey Mobile SDK | Mobile key handling, SDK login, BLE access flow |
| WITTE Mobile Library | Box ID conversion, Box Command data, box feedback parsing |
| AppAuth for iOS | OAuth / token exchange |
Podfile setup
Section titled “Podfile setup”source 'https://github.com/tapkey/TapkeyCocoaPods'source 'https://cdn.cocoapods.org/'
use_frameworks!
target 'PartnerApp' do pod 'TapkeyMobileLib', '~> VERSION' pod 'witte-mobile-library', '~> VERSION' pod 'AppAuth'endSample resources: witte-mobile-sample-ios | witte-mobile-library-for-objc
Info.plist permissions
Section titled “Info.plist permissions”<key>NSBluetoothAlwaysUsageDescription</key><string>This app uses Bluetooth to communicate with the vehicle access device.</string>Request Bluetooth access before starting the access flow. Handle denied permissions gracefully — missing Bluetooth descriptions may cause iOS to prevent BLE communication or terminate the app.
Authentication flow
Section titled “Authentication flow”sequenceDiagram
autonumber
actor customer as End customer
participant app as iOS app
participant backend as Partner backend
participant api as flinkey API
participant tapkey as Tapkey Trust Service
participant sdk as Tapkey Mobile SDK
customer->>app: Sign in
app->>backend: Request mobile access context
backend->>api: Get IdToken
api-->>backend: Return IdToken
backend-->>app: Return IdToken
app->>tapkey: Token exchange via AppAuth
tapkey-->>app: Tapkey access token
app->>sdk: SDK login
Token exchange
Section titled “Token exchange”import AppAuth
let request = OIDTokenRequest( configuration: configuration, grantType: "http://tapkey.net/oauth/token_exchange", authorizationCode: nil, redirectURL: nil, clientID: "wma-native-mobile-app", clientSecret: nil, scopes: ["register:mobiles", "read:user", "handle:keys"], refreshToken: nil, codeVerifier: nil, additionalParameters: [ "provider": "wma.oauth", "subject_token_type": "jwt", "subject_token": idToken, "audience": "tapkey_api", "requested_token_type": "access_token" ])
OIDAuthorizationService.perform(request) { response, error in guard let accessToken = response?.accessToken else { return } // Use accessToken for SDK login.}SDK initialization
Section titled “SDK initialization”@UIApplicationMainclass AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { TapkeyMobileSdk.initialize { builder in builder.setTokenRefreshHandler(PartnerTokenRefreshHandler()) } return true }}Token refresh handler
Section titled “Token refresh handler”class PartnerTokenRefreshHandler: TKMTokenRefreshHandler { func refreshAuthenticationAsync(userId: String, cancellationToken: TKMCancellationToken) -> TKMPromise<String> { let source = TKMPromiseSource<String>() requestFreshTapkeyAccessToken(userId: userId) { result in switch result { case .success(let token): source.setResult(token) case .failure: source.setError(TKMError(errorDescriptor: TKMErrorDescriptor(code: TKMAuthenticationHandlerErrorCodes.TokenRefreshFailed, message: "No new token can be obtained.", details: nil))) } } return source.promise }
func onRefreshFailed(userId: String) { // Handle refresh failure — may require user to sign in again. }}SDK login
Section titled “SDK login”TapkeyMobileSdk.serviceFactory.userManager .logInAsync(accessToken: accessToken, cancellationToken: TKMCancellationTokens.none) .continueOnUi { userId in // SDK login successful. } .catchOnUi { error in // SDK login failed. return nil }BLE scanning
Section titled “BLE scanning”let bleLockScanner = TapkeyMobileSdk.serviceFactory.bleLockScanner
let scanRegistration = bleLockScanner.startForegroundScan()let nearbyLocksRegistration = bleLockScanner.observable.addObserver { locks in // Match discovered locks against expected physical lock ID.}
// Stop scanning when donescanRegistration.close()nearbyLocksRegistration.close()Triggering vehicle access
Section titled “Triggering vehicle access”let boxId = "..." // from mobile access contextlet physicalLockId = WDBoxIdConverter().toPhysicalLockId(withBoxId: boxId)
let scanner = TapkeyMobileSdk.serviceFactory.bleLockScannerlet communicator = TapkeyMobileSdk.serviceFactory.bleLockCommunicatorlet commandFacade = TapkeyMobileSdk.serviceFactory.commandExecutionFacade
guard let lock = scanner.locks.first(where: { $0.physicalLockId == physicalLockId }) else { // Handle box not found. return}
let timeout = TKMCancellationTokens.fromTimeout(timeoutMs: 15000)
communicator.executeCommandAsync( peripheralId: lock.peripheralId, physicalLockId: physicalLockId, commandFunc: { tlcpConnection in commandFacade.triggerLockAsync(tlcpConnection, cancellationToken: timeout) }, cancellationToken: timeout).continueOnUi { result in switch result?.code { case .ok: return true // Access succeeded default: return false // Access failed }}.catchOnUi { error in return false }Error handling
Section titled “Error handling”| Error | User-facing | Technical |
|---|---|---|
| Missing access context | Ask to retry | Check backend assignment flow |
| Token exchange failed | Ask to retry | Log sanitized error |
| Bluetooth disabled | Ask to enable BT | Do not start scanning |
| Permission missing | Ask to grant | Show iOS permission guidance |
| Box not found | Move closer to vehicle | Stop scan after timeout |
| Command failed | Show access failed | Store sanitized diagnostics |
Test checklist
Section titled “Test checklist”- App authenticates customer against partner backend
- App requests and receives mobile access context
- AppAuth token exchange succeeds
- Tapkey SDK login succeeds
- Token refresh handler is implemented
- Info.plist Bluetooth usage descriptions configured
- Bluetooth permission handling implemented
- BLE scanning starts and stops correctly
- App discovers the flinkey BLE Box
- Access command succeeds
- Box feedback is handled
- Bluetooth disabled / box not found states handled
- No credentials, tokens or digital keys in logs
- Real-device testing completed
