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 ( |
Transferring a baggage tag to an EBT
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:
-
BLE connect to the selected device using the stored client certificate. The SDK receives a nonce from the EBT.
-
Ownership verification check — the SDK calls the backend to determine whether additional verification is required.
-
Custody proof — if required,
transferTagsuspends inCustodyProofCollector.collectuntil the host returnsCustodyProofInputor cancels (null). Wrong submissions surfacelastSubmissionErrorand repeat untilmaxCustodyProofAttempts. -
Authorization — the SDK requests a short-lived transaction token from the backend, tied to the device and nonce.
-
BLE write — the SDK sends the baggage payload and transaction token to the EBT over Bluetooth.
-
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
The host app calls clearTag with BLE deviceId and backend uniqueDeviceId (tag uuid). The SDK handles connect, authorize, BLE clear, and unlock:
-
BLE connect to the device using the stored certificate. Receives a nonce.
-
Authorization — requests a transaction token for the clear operation.
-
BLE clear — sends the clear-display command with the token.
-
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).
-
CustodyHintscarries 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 onBagIdConfig).
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 |
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 |
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 |
|
Scan + device selection; |
|
Custody proof UI inside |
Lock lookup; |
Calls |
BLE connect + nonce, authorize, BLE write, device attachment |
Receives |
Surfaces HTTP/BLE errors on |