Transfer & Clear

This page is part of Protocol & internals. For the partner integration contract, use Managed BLE Operations.

GATT generations: BagID tags may use GATT v2.0 (EbtTagIdentity.BagId) or GATT v2.1 (EbtTagIdentity.BagIdBleV21). The SDK selects the stack from scan/connect data — partner apps use the same transferTag, clearTag, and managed BLE APIs on both. SEC handshake and transaction-token delivery on BLE apply to v2.1 only; v2.0 uses a simplified authorize path. The sequence diagrams below describe the logical flow; BLE security details in BLE security vary by generation.

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), optional recordLocator / custodyProofSurname to pre-fill proof, and optional per-call CustodyProofCollector (else BagIdConfig.custodyProofCollector). 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, transferTag suspends in CustodyProofCollector.collect until the host returns CustodyProofInput or cancels (null). Wrong submissions surface lastSubmissionError and repeat until maxCustodyProofAttempts.

  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 custody = CustodyProofCollector { hints, attempt, maxAttempts, lastErr ->
    // Show UI; suspend until user submits or dismisses
    CustodyProofInput(recordLocator = "...", surname = "...")
}

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,
    ),
    custodyProofCollector = custody,
)

result.onSuccess { transfer ->
    Log.d("BagID", "Written. pendingRetry=${transfer.pendingRetry}")
}.onFailure { error ->
    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 suspends inside CustodyProofCollector.collect until the user supplies CustodyProofInput or cancels (CustodyProofCancelledException).

  • CustodyHints carries masked PNR/surname and itinerary hints for your UI.

  • Collect PNR and passenger surname; the SDK uppercases surname for POST /v2/ebt-custody-proofs.

  • Full-screen managed path: Managed BLE Operations includes custody UI inside the SDK surface. Programmatic hosts implement CustodyProofCollector (or set a default on BagIdConfig).

BLE security

During transfer and clear, the tag firmware validates the session before accepting a write. Behavior depends on GATT generation:

Step GATT v2.0 (EbtTagIdentity.BagId) GATT v2.1 (EbtTagIdentity.BagIdBleV21)

Connect

Client certificate presented over BLE; device nonce obtained

Same, plus SEC establishment (certificate chain, session keys)

Authorize

Backend transactionToken from POST /v2/ebt-authorizations

SEC handshake completes; SDK writes transaction token to SEC characteristic before payload write

Write

Ticket/clear payload + token to ITS/GATT characteristics

Same, after SEC AUTHORIZED state

On all supported tags, the backend issues a short-lived transaction token tied to the device 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.

Partner apps do not implement SEC or token logic — it is internal to the SDK. For product testing, certify against the tag generations you ship (see UAT checklist).

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 inside CustodyProofCollector, or inside Managed BLE Operations.

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

Calls transferTag(request, custodyProofCollector = …)

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

Receives TransferResult or failure

Surfaces HTTP/BLE errors on Result / BagIdState