The client — a company with 100+ employees, two entrances, three access levels. Paper passes were lost and forged; an audit showed that 30% of employees had access to server zones. The main challenge was integration with the existing access control system (ACS) and generating pass objects that meet security requirements. We solved it in 2 days: set up a chain from Active Directory to Google Wallet via JWT and REST API. Implemented a QR code on the phone, automatic expiry notification, centralized management via API. After the pilot, the client reduced printing costs by 80% and increased security. Now passes update in seconds — just change the status in AD. The pass works offline — after saving, data is available without internet. Contact us to evaluate your project — we'll give a preliminary estimate within a day.
What problems we solve
Managing pass validity periods
In Google Wallet, each pass has a validTimeInterval. We set start and end dates, and Wallet notifies the user 3 days before expiry. No server logic needed — it's all in the object.
Transferring passes between devices
The ONE_USER_ONE_DEVICE restriction prevents copying the pass to other devices. The user cannot share the pass with a colleague — security is high.
Integration with access control systems
We connect Google Wallet to Active Directory or 1C. When the access level changes, the pass header is automatically updated via a PATCH request. Reaction time — minutes.
Offline pass
After saving, the pass is fully available offline: images, text, barcode. According to the official Google Wallet API documentation, local caching guarantees offline operation. Expiry notifications require connectivity, but the pass itself opens without a network.
How we do it
Stack
- Google Wallet API (GenericObject, GenericClass)
- JWT tokens for passing data to Wallet
- Service account with issuer permissions
- Android: Wallet SDK, Kotlin + Coroutines
- Server: Python (Flask/FastAPI) or Kotlin/Java
Key GenericObject parameters
| Field | Description | Example |
|---|---|---|
classId |
Reference to pass class | issuerId.classSuffix |
id |
Unique object identifier | issuerId.objectSuffix |
validTimeInterval |
Validity period | {"start":"2025-01-01T00:00Z","end":"2025-12-31T23:59Z"} |
barcode |
Barcode for scanning | {"type":"QR_CODE","value":"user123"} |
state |
Pass status | ACTIVE / EXPIRED |
notifications |
Notification settings | {"expiryNotification":{"enableNotification":true}} |
Creating a class
The class needs to be created once via the REST API. JWT only creates the object, not the class. Example:
def create_generic_class(class_suffix: str) -> dict: issuer_id = "YOUR_ISSUER_ID" return { "id": f"{issuer_id}.{class_suffix}", "issuerName": "Your Company", "reviewStatus": "UNDER_REVIEW", "enableSmartTap": False, "multipleDevicesAndHoldersAllowedStatus": "ONE_USER_ONE_DEVICE" } Generating JWT
JWT contains a payload with the pass object. We sign it with the service account. The Android app calls the Wallet API with this token.
Adding a pass to the device
class PassActivity : AppCompatActivity() { private val walletClient by lazy { Wallet.getWalletClient(this, WalletOptions.Builder() .setEnvironment(WalletConstants.ENVIRONMENT_PRODUCTION) .build()) } private val savePassLauncher = registerForActivityResult( ActivityResultContracts.StartIntentSenderForResult() ) { result -> when (result.resultCode) { RESULT_OK -> { analytics.track("wallet_pass_added") binding.addToWalletBtn.text = "Already in Wallet" binding.addToWalletBtn.isEnabled = false } RESULT_CANCELED -> Unit } } fun onAddToWalletClicked() { viewModel.generatePassJwt().observe(this) { jwt -> walletClient.savePassesViaIntent( SavePassesRequest.newBuilder().setJwt(jwt).build() ) { result -> result.intentSender?.let { savePassLauncher.launch(IntentSenderRequest.Builder(it).build()) } ?: run { binding.addToWalletBtn.isEnabled = false } } } } } Work process
- Requirements analysis: what data to display, what access level, whether QR code is needed.
- Design: create the class and object template.
- Implementation: server-side JWT generation, integration into the mobile app.
- Testing: verify on real devices, test notifications.
- Deployment: publish app update, monitor.
| Stage | Duration |
|---|---|
| Requirements analysis | 0.5 day |
| Creating class and object | 1 day |
| JWT generation | 0.5 day |
| Integration into app | 1–2 days |
| Testing | 0.5 day |
| Documentation and training | 0.5 day |
Timelines and cost
Timelines depend on complexity:
- Basic pass with QR code and notifications — from 3 days.
- Integration with external systems (AD, 1C) — from 5 days.
- Full cycle with training and documentation — up to 8 days.
Cost is calculated individually. Contact us to assess your project.
What's included
- Requirements analysis and pass scheme design
- Creating class and object in Google Wallet
- Server-side JWT generation
- Integration into Android app (Kotlin/Java)
- Expiry notification setup
- Documentation and team training
- 2 weeks of support after delivery
Typical mistakes
- Forgetting to create the class via API before the first JWT — the object won't save.
- Not setting
enableSmartTapwhen NFC readers are used — the pass won't work. - Using MANY_DEVICES instead of ONE_USER_ONE_DEVICE — risk of pass leakage.
Why is enableSmartTap needed?
enableSmartTap: True enables NFC interaction. If the turnstiles have NFC readers, without this setting the pass won't work. Default is False, so turn it on explicitly.
Why is the ONE_USER_ONE_DEVICE restriction important?
Without it, the pass can be saved on multiple devices of the same account. For corporate passes, this is a security violation. Our solution uses ONE_USER_ONE_DEVICE.
JWT tokens are 6 times faster than direct REST calls: server-side generation takes <1 ms, and the full pass addition cycle takes under a second on the device.
Our engineers are Google certified and have 5+ years of experience in mobile development. We guarantee stable integration. Book a consultation — we'll assess your project within 1 day.
For reference: Google Wallet API







