Skip to content

Ultralight

Ultralight enables Beamsync, a proximity-based data transmission mechanism that broadcasts passenger data to nearby airport touchpoints (for example, e-gates or self-service kiosks). This allows passengers to pass through airport processes without re-scanning their documents at each step.

In the current SDK architecture, Ultralight is integrated through the Enrolment SDK facade. You provide your own UltralightProvider during initialization, and Enrolment exposes two methods to control the sharing lifecycle: share() (sets passengers and starts broadcasting, asynchronously reporting its result through a completion callback) and stopSharing().

Prerequisites

Before integrating Ultralight, ensure you have:

  • Ultralight API key — Contact your Amadeus liaison to obtain one
  • Ultralight provider dependency — Added to your project (see import instructions below)
  • Minimum SDK level 26 (same as Enrolment SDK)
  • Required permissions (brought transitively by the Ultralight provider dependency):
    • BLUETOOTH_SCAN, BLUETOOTH_CONNECT (Bluetooth Low Energy)
    • ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION (required for BLE scanning)
  • Runtime requirements:
    • Bluetooth must be enabled on the device
    • Location services must be enabled on the device

Note

The SDK validates that Bluetooth and Location are enabled before starting Beamsync and returns a descriptive FeatureError if either is disabled.

Warning

Ultralight is not available when initializing the SDK via initializeOffline.

  • Minimum iOS Version: 15 (same as Enrolment SDK)
  • Required permissions on Info.plist (brought transitively by the Ultralight provider dependency):
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth is used to communicate with nearby UltraLight devices</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location is required to detect nearby Bluetooth devices.</string>

For Beamsync to continue operating while the app is backgrounded, add these background modes to the application target's Info.plist:

<key>UIBackgroundModes</key>
<array>
    <string>bluetooth-central</string>
    <string>bluetooth-peripheral</string>
    <string>fetch</string>
</array>

How to Import

Ultralight is exposed through the Enrolment SDK integration flow.

Add the Ultralight provider dependency to your app's build.gradle:

dependencies {
    implementation "com.amadeus.mdi.mob.sdk:ama-ultralight:2.0.9"
    // ... other dependencies
}
  • Target API level 26 (Oreo) or later;

Ultralight is distributed for iOS via Swift Package Manager (SPM).

Install using Xcode

  1. Open your project in Xcode
  2. Go to File ▸ Add Packages…
  3. Enter the package repository URL:
https://github.com/vbmobile/AMAShareUltralight
  1. Select the desired version (recommended: exact or up to next major)
  2. Add the AMAShareUltralight product to your app target

Install using Package.swift

If you are managing dependencies manually, add Ultralight to your Package.swift:

dependencies: [
   .package(
       url: "https://github.com/vbmobile/AMAShareUltralight",
       exact: "2.0.17"
   )
],

Replace 2.0.17 with the intended version.

Then include it in your target dependencies:

.target(
    name: "YourAppTarget",
    dependencies: [
        .product(name: "AMAShareUltralight", package: "AMAShareUltralight")
    ]
)

Image size limit

The images you provide to AMAShareUltralight as docPhotoBase64 and selfieBase64 must each be smaller than 256 KB. Larger images are rejected.

Replace YourAppTarget with the intended app target you wish to use.

Once added, Ultralight APIs are available to your application through the Enrolment SDK integration flow.

Configure Ultralight in Enrolment Initialization

Step 1: Create and Initialize UltralightProvider

Before initializing Enrolment, create and configure your UltralightProvider instance by calling initialiseBeamsync() with your API key.

On Android, you then call softStart() yourself and report success and errors through OnSoftStartCompletion. On iOS, the Enrolment SDK runs the soft start for you during initialization, so the provider only needs initialiseBeamSync(apiKey:).

private fun initializeUltralight(): UltralightProvider? {
    val ultralightApiKey = "<your-ultralight-api-key>"

    UltralightSdk.initialize(
        context = requireContext(),
        config = UltralightConfig(
            logLevel = UltralightLogLevel.ERROR 
        )
    )
    val provider = UltralightSdk.getInstance()
    provider.initialiseBeamsync(ultralightApiKey)
    provider.softStart(requireContext(), object : OnSoftStartCompletion {
        override fun onProgressChanged(progress: Progress) {
            // Not currently emitted by the provider; override required by the interface.
        }

        override fun onSoftStartSuccess() {
            Log.i("Ultralight", "softStart onSuccess")
        }

        override fun onSoftStartError(error: ProviderError) {
            Log.e("Ultralight", "softStart onError [${error.errorCode}] ${error.description}")
        }
    })
    return provider
}

If you don't want to enable Ultralight, simply return null:

private fun initializeUltralight(): UltralightProvider? {
    val enabled = false // Your configuration flag
    if (!enabled) return null

    // ... initialization code
}
func ultralightProvider() -> UltralightProtocol? {
    let ultralight = AMAShareUltralight.Ultralight()
    ultralight.initialize(config: .init(level: .debug))
    return ultralight
}

Call initialize(config:) when diagnostic logging is needed. Use the appropriate log level for the environment.

Step 2: Pass Provider to Enrolment Initialization

Pass the UltralightProvider when you initialize Enrolment:

val ultralightProvider = initializeUltralight()

Enrolment.initialize(
    context = requireContext().applicationContext,
    enrolmentConfig = enrolmentConfig,
    enrolmentCustomViews = enrolmentCustomViews,
    documentReaderProvider = documentReaderProvider,
    ultralightProvider = ultralightProvider, // Pass the provider
    rfidReaderProvider = null,
    enrolmentInitializerCallback = callback
)
func initializeEnrolment(provider: UltralightProtocol?) async -> EnrolmentProtocol {
    let ultralightProvider = provider ?? ultralightProvider()
    return await withCheckedContinuation { continuation in
        Enrolment.shared.initWith(enrolmentConfig: nil,
                                  documentScanProvider: nil,
                                  documentRFIDProvider: nil,
                                  ultralightProvider: ultralightProvider,
                                  viewRegister: nil,
                                  completionHandler: { result in
                                      switch result {
                                      case .success:
                                          continuation.resume(returning: Enrolment.shared)
                                      case let .failure(featureError):
                                          print(featureError.description)
                                          continuation.resume(returning: Enrolment.shared)
                                      }
                                  })
    }

Share Passenger Data

The share() method sets the passenger list and starts sharing in a single call. It is asynchronous — provide a completion callback to receive the result (OnShareCompletion on Android, a (Bool, FeatureError?) completion handler on iOS).

Passenger model:

Field Type Description
paxId String? Optional passenger ID (UUID v4). Defaults to a random UUID when omitted
language String Language code (e.g., "en", "fr")
mrz String MRZ string (\n separating lines)
boardingPasses List<String> Raw BCBP barcode strings
docPhotoBase64 String Base64-encoded document holder photo
selfieBase64 String Base64-encoded selfie
ePassport Boolean Whether the document is an e-Passport
eBagTagId String? Optional electronic bag tag ID
tag String? Optional custom tag

Selfie image size

Ensure selfieBase64 and docPhotoBase64 are smaller than 256 KB. Larger images are rejected.

val passengers = listOf(
    Passenger(
        paxId = null, // Defaults to UUID.randomUUID().toString()
        language = "en",
        mrz = "<mrz-line-1>\n<mrz-line-2>",
        boardingPasses = listOf("<bcbp-barcode-string>"),
        docPhotoBase64 = "<base64-encoded-document-photo>",
        selfieBase64 = "<base64-encoded-selfie>",
        ePassport = true,
        eBagTagId = null,
        tag = null
    )
)

Enrolment.getInstance().share(passengers, object : OnShareCompletion {
    override fun onShareSuccess() {
        // Passengers set and Beamsync started successfully
    }

    override fun onShareError(error: FeatureError) {
        // Check error.description for details
        Log.e("Ultralight", "Error: ${error.description}")
    }
})

Prefer UltralightBuilders.build(...) when creating passengers. It normalizes optional values such as the passenger ID and language and keeps passenger construction aligned with the Ultralight provider API.

func sampleShare(enrolment: EnrolmentProtocol?) {
    let passenger = AMAShareUltralight.UltralightBuilders.build(
        paxId: nil, // Defaults to UUID().uuidString
        idDocument: nil,
        faceCapture: nil,
        boardingPass: "<bcbp-barcode-string>",
        language: nil // Defaults to "en"
    )

    enrolment?.share(passengers: [passenger], completionHandler: { result, error in
        if result {
            // Passengers set and Beamsync started successfully
        } else {
            // Check error for details
            print(error ?? "")
        }
    })
}

Stop Beamsync

Stop Beamsync when the flow ends (for example, when leaving the screen or destroying the view):

Enrolment.getInstance().stopSharing()

It's recommended to call stopSharing() in your fragment/activity lifecycle:

override fun onDestroyView() {
    super.onDestroyView()
    Enrolment.getInstance().stopSharing()
}

It's recommended to call stopSharing() in your view lifecycle:

deinit {
    presenter?.shouldStopSharing() // MVP Design Pattern
}

or call it ad hoc

enrolment?.stopSharing()

Complete Example

Here's a complete example integrating Ultralight with the Enrolment SDK:

class UltralightFragment : Fragment() {

    // Initialize Ultralight provider
    private fun initializeUltralight(): UltralightProvider? {
        val ultralightApiKey = "<your-api-key>"

        UltralightSdk.initialize(
            context = requireContext(),
            config = UltralightConfig(
                logLevel = UltralightLogLevel.ERROR 
            )
        )
        val provider = UltralightSdk.getInstance()
        provider.initialiseBeamsync(ultralightApiKey)
        provider.softStart(requireContext(), object : OnSoftStartCompletion {
            override fun onProgressChanged(progress: Progress) {
                // Not currently emitted by the provider; override required by the interface.
            }

            override fun onSoftStartSuccess() {
                Log.i(TAG, "softStart onSuccess")
            }

            override fun onSoftStartError(error: ProviderError) {
                Log.e(TAG, "softStart onError [${error.errorCode}] ${error.description}")
            }
        })
        return provider
    }

    // Initialize Enrolment with Ultralight
    private fun initializeEnrolment() {
        val ultralightProvider = initializeUltralight()

        Enrolment.initialize(
            context = requireContext().applicationContext,
            enrolmentConfig = enrolmentConfig,
            enrolmentCustomViews = enrolmentCustomViews,
            documentReaderProvider = documentReaderProvider,
            ultralightProvider = ultralightProvider,
            rfidReaderProvider = null,
            enrolmentInitializerCallback = object : EnrolmentInitializerCallback {
                override fun onEnrolmentInitialized() {
                    Log.d(TAG, "Enrolment initialized successfully")
                }

                override fun onEnrolmentInitializationError(error: FeatureError) {
                    Log.e(TAG, "Initialization error: ${error.description}")
                }
            }
        )
    }

    // Prepare and share passenger data
    private fun prepareAndSharePassenger() {
        try {
            val idDocument = EnrolmentData.documentReaderReport?.idDocument ?: return
            val boardingPass = EnrolmentData.boardingPass
            val selfieBitmap = readPhotoFromInternalStorage(
                requireContext(),
                EnrolmentData.faceCapturePhotoFilename ?: ""
            )

            val passenger = Passenger(
                paxId = null, // Defaults to UUID.randomUUID().toString()
                language = "en",
                mrz = idDocument.mrz,
                boardingPasses = listOf(boardingPass.rawBoardingPass),
                docPhotoBase64 = documentPhoto,
                selfieBase64 = selfiePhoto,
                ePassport = idDocument.isElectronic
            )

            Enrolment.getInstance().share(listOf(passenger), object : OnShareCompletion {
                override fun onShareSuccess() {
                    Log.d(TAG, "Beamsync started")
                }

                override fun onShareError(error: FeatureError) {
                    val message = error.description.ifBlank {
                        error.publicMessage.ifBlank { "Unknown error" }
                    }
                    Log.e(TAG, "Share failed: $message")
                }
            })
        } catch (e: Exception) {
            Log.e(TAG, "Error preparing passenger", e)
        }
    }

    // Stop Beamsync
    private fun stopBeamsync() {
        Enrolment.getInstance().stopSharing()
        Log.d(TAG, "Beamsync stopped")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Enrolment.getInstance().stopSharing()
    }
}

Notes

  • The UltralightProvider must be initialized before passing it to Enrolment.initialize()
  • Ultralight is not available in offline mode (initializeOffline)
  • share() is asynchronous — results are delivered via OnShareCompletion; safe to call from the main thread
  • share() both sets the passenger data and starts Beamsync (there is no separate startSharing() step)
  • The SDK performs pre-flight checks for Bluetooth and Location before starting Beamsync
  • Always call stopSharing() when cleaning up (e.g., in onDestroyView())
import AMADocModeliOS
import AMAShareUltralight
import MobileIdSDKiOS

class UltralightProviderSample {
    func ultralightProvider() -> UltralightProtocol? {
        let ultralight = AMAShareUltralight.Ultralight()
        ultralight.initialize(config: .init(level: .debug))
        return ultralight
    }

    func initializeEnrolment(provider: UltralightProtocol?) async -> EnrolmentProtocol {
        let ultralightProvider = provider ?? ultralightProvider()
        return await withCheckedContinuation { continuation in
            Enrolment.shared.initWith(enrolmentConfig: nil,
                                      documentScanProvider: nil,
                                      documentRFIDProvider: nil,
                                      ultralightProvider: ultralightProvider,
                                      viewRegister: nil,
                                      completionHandler: { result in
                                          switch result {
                                          case .success:
                                              continuation.resume(returning: Enrolment.shared)
                                          case let .failure(featureError):
                                              print(featureError.description)
                                              continuation.resume(returning: Enrolment.shared)
                                          }
                                      })
        }
    }

    func sampleShare(enrolment: EnrolmentProtocol?) {
        let passenger1 = Passenger(language: "en",
                                  mrz: "<mrz-line-1>\n<mrz-line-2>",
                                  boardingPasses: ["<bcbp-barcode-string>"],
                                  docPhotoBase64: "<base64-encoded-document-photo>",
                                  selfieBase64: "<base64-encoded-selfie>",
                                  ePassport: true,
                                  tag: nil,
                                  ebagtagId: nil)

        let passenger2 = AMAShareUltralight.UltralightBuilders.build(
            paxId: nil, // Defaults to UUID().uuidString
            idDocument: nil,
            faceCapture: nil,
            boardingPass: "<bcbp-barcode-string>",
            language: nil // Defaults to "en"
        )

        enrolment?.share(passengers: [passenger1, passenger2], completionHandler: { result, error in
            if result {
                // Passengers set and Beamsync started successfully
            } else {
                // Check error for details
                print(error ?? "")
            }
        })
    }

    func stopSharing(enrolment: EnrolmentProtocol?) {
        enrolment?.stopSharing()
    }

    func prepareAndSharePassenger(enrolment: EnrolmentProtocol?) {
        let passenger = AMAShareUltralight.UltralightBuilders.build(
            paxId: nil, // Defaults to UUID().uuidString
            idDocument: EnrolmentData.idDocument,
            faceCapture: EnrolmentData.biometricFaceCaptureReport?.photo,
            boardingPass: EnrolmentData.boardingPass?.raw ?? "",
            language: nil // Defaults to "en"
        )
        enrolment?.share(passengers: [passenger], completionHandler: { result, error in
            if result {
                // Passengers set and Beamsync started successfully
            } else {
                // Check error for details
                print(error ?? "")
            }
        })
    }
}   

Notes

  • The UltralightProvider must be initialized before passing it to Enrolment.shared.initWith(...)
  • Ultralight is not available in offline mode (initOffline)
  • share() is asynchronous — the result is delivered through the completionHandler closure
  • share() both sets the passenger data and starts Beamsync (there is no separate startSharing() step)
  • Beamsync requires Bluetooth permission; share() fails with a bluetoothNotGranted error if it hasn't been granted
  • Always call stopSharing() when cleaning up (e.g., in deinit())