Every iPhone that accepts payments is represented in Finix by a Device. The SDK requires the Device's ID at initialization.
Each Tap to Pay on iPhone Device represents one Merchant on one physical iPhone. Finix identifies the iPhone by Apple's reader identifier, which you pass as the Device's serial_number during creation.
Get the reader identifier
Apple assigns each iPhone a stable reader identifier that becomes the Device's serial_number.
Check for an existing Device
List existing Devices for a Merchant and match on serial_number. A Device whose serial_number matches the reader identifier and whose enabled is true can be reused; no new provisioning needed.
Create the Device
POST /merchants/{merchant_id}/devices
| Field | Value |
|---|---|
model | Always IOS_TAP_TO_PAY. |
serial_number | The Apple reader identifier. |
app_bundle_id | Your app's bundle identifier. Must match the app that takes payments. |
name, description | Free-form labels shown in the Finix Dashboard. |
The response returns the new Device with "enabled": false. The Device must be activated before it can process transfers.
{
"id": "DVxxxxxxxxxxxxx",
"enabled": false,
"model": "IOS_TAP_TO_PAY",
"merchant": "MUxxxxxxxxxxxxx",
"name": "Tap to Pay on iPhone - Front Counter",
"serial_number": "{reader_identifier}",
"created_at": "2026-08-04T17:32:11.000Z"
}Activate the Device
PUT /devices/{device_id}
| Field | Value |
|---|---|
action | ACTIVATE |
The response returns the Device with "enabled": true. Transactions on an un-activated Device fail at processing time, not at initialization.
Cache and reuse the Device ID
A Device ID is stable per (Merchant, reader identifier) pair. Cache the ID keyed by both; invalidate the cache when a transaction fails with a Device-not-activated or Device-not-enabled error.
import ProximityReader
func fetchReaderIdentifier() async throws -> String {
try await PaymentCardReader().readerIdentifier
}
func tapToPayDeviceId(merchantId: String, readerIdentifier: String) async throws -> String {
let cacheKey = "finix_ttp_device_\(merchantId)_\(readerIdentifier)"
if let cached = UserDefaults.standard.string(forKey: cacheKey) {
return cached
}
// Your backend looks up or creates + activates the Device for
// (merchantId, readerIdentifier) and returns its ID.
let deviceId = try await backend.provisionTapToPayDevice(
merchantId: merchantId,
readerIdentifier: readerIdentifier
)
UserDefaults.standard.set(deviceId, forKey: cacheKey)
return deviceId
}