Transfer & Clear

Transferring a baggage tag to an EBT

Diagram

The host app calls transferTag with TransferTagRequest: BLE deviceId, backend uniqueDeviceId (tag uuid), baggageId, journeyId, optional ticket data (displayTicket or journey), and optional recordLocator / custodyProofSurname when custody proof is required. Everything below except custody UI is internal to the SDK:

  1. BLE connect to the selected device using the stored client certificate. The SDK receives a nonce from the EBT.

  2. Ownership verification check — the SDK calls the backend to determine whether additional verification is required.

  3. Custody proof — if required and PNR/surname are not supplied, the SDK returns CustodyProofRequiredException with hints; the host collects credentials and retries transferTag with recordLocator and custodyProofSurname. If supplied, the SDK calls POST /v2/ebt-custody-proofs.

  4. Authorization — the SDK requests a short-lived transaction token from the backend, tied to the device and nonce.

  5. BLE write — the SDK sends the baggage payload and transaction token to the EBT over Bluetooth.

  6. Device attachment — after the EBT confirms the write, the SDK finalizes the baggage-device link on the backend.

If any step fails, the SDK returns Result.failure with an exception or message. If device attachment fails after a successful BLE write, the result uses TransferResult.pendingRetry = true so the host can surface follow-up.

  • Kotlin

  • Swift

val connected = BagIdSdk.ebtBleDeviceState.value.connectedDevice!!
val result = BagIdSdk.transferTag(
    TransferTagRequest(
        deviceId = connected.deviceId,
        uniqueDeviceId = connected.uuid,
        baggageId = baggage.baggageId,
        journeyId = journey.journeyId,
        journey = journey,
    ),
)

result.onSuccess { transfer ->
    Log.d("BagID", "Written. pendingRetry=${transfer.pendingRetry}")
}.onFailure { error ->
    when (error) {
        is CustodyProofRequiredException -> { /* show host UI; retry with recordLocator + custodyProofSurname */ }
        else -> Log.e("BagID", "Transfer failed: ${error.message}")
    }
}
import BagIdSDK

do {
    let transfer = try await BagIdSdk.shared.transferTagOrThrow(
        request: TransferTagRequest(
            deviceId: connected.deviceId,
            uniqueDeviceId: connected.uuid,
            baggageId: baggage.baggageId,
            journeyId: journey.journeyId,
            journey: journey
        )
    )
    let batteryPercent: Int = {
        guard let level = transfer.device.batteryLevel else { return -1 }
        return Int(truncating: level)
    }()
    print("Written. Battery: \(batteryPercent)%")
} catch {
    // Map SDK errors to UI. Exact Swift error types depend on SKIE export; inspect in Xcode.
    print("Transfer failed: \(error.localizedDescription)")
}

Clearing an EBT

Diagram

The host app calls clearTag with BLE deviceId and backend uniqueDeviceId (tag uuid). The SDK handles connect, authorize, BLE clear, and unlock:

  1. BLE connect to the device using the stored certificate. Receives a nonce.

  2. Authorization — requests a transaction token for the clear operation.

  3. BLE clear — sends the clear-display command with the token.

  4. Unlock — after the EBT confirms the clear, the SDK removes the custody lock on the backend. The EBT is no longer bound to the previous journey.

  • Kotlin

  • Swift

val connected = BagIdSdk.ebtBleDeviceState.value.connectedDevice!!
val result = BagIdSdk.clearTag(
    ClearTagRequest(deviceId = connected.deviceId, uniqueDeviceId = connected.uuid),
)

result.onSuccess {
    Log.d("BagID", "Tag cleared and unlocked")
}.onFailure { error ->
    Log.e("BagID", "Clear failed: ${error.message}")
}
import BagIdSDK

do {
    _ = try await BagIdSdk.shared.clearTagOrThrow(
        request: ClearTagRequest(deviceId: connected.deviceId, uniqueDeviceId: connected.uuid)
    )
    print("Tag cleared and unlocked")
} catch {
    print("Clear failed: \(error.localizedDescription)")
}

Custody proof (host app)

When the lock lookup indicates custody proof is required, transferTag fails with CustodyProofRequiredException unless recordLocator and custodyProofSurname are already set on TransferTagRequest.

  • The exception includes hints (e.g. masked PNR/surname, airport) for your UI.

  • Collect PNR and passenger surname in the host app (uppercase surname as required by the API), then call transferTag again with both fields.

  • The SDK does not present a bundled verification dialog in this build—custody UX is host-owned.

BLE security

During both transfer and clear operations, the EBT firmware performs two cryptographic checks before accepting a write. On the first BLE message the EBT verifies the client certificate using its embedded Root CA public key, confirming the backend endorsed this app installation, then returns a random 32-byte nonce. On the second BLE message the SDK sends the payload and a transaction token from the backend. The EBT verifies the transaction token using the Root CA public key, confirming the backend authorized this write for this specific tag (GUID) and session (nonce).

Personally identifiable data is sent as write-only to the tag. The BLE GATT profile does not expose read characteristics for PII fields, limiting exfiltration risk from a compromised BLE client.

What the host app does vs what the SDK does

Host app SDK (internal)

Journey and baggage selection UI; build TransferTagRequest (journey / displayTicket, uniqueDeviceId).

Scan + device selection; notifyDiscoveredModel; connectEbt.

Custody proof UI when CustodyProofRequiredException is returned.

Lock lookup; POST /v2/ebt-custody-proofs when fields supplied.

Calls transferTag(request)

BLE connect + nonce, authorize, BLE write, device attachment

Receives TransferResult or failure

Surfaces HTTP/BLE errors on Result / BagIdState