Open navigation
Back to Articles

August 2026 · 8 min read

Creating Google Wallet Passes: JWT Flows and Backend Integration

A technical guide for backend developers on implementing Google Wallet passes, from issuer account setup to the final signed Save to Wallet JWT.

Editorial cover for google wallet passes api implementation

Setting up the Google Wallet Issuer Account

The foundation of any Google Wallet integration is the Issuer Account. You cannot define the pass blueprints or issue items to users without this account. You must navigate to the Google Pay and Wallet Console to establish your business identity and accept the mandatory terms of service. This account acts as the primary container for your developer identity, allowing the Google ecosystem to map your API requests to your specific business entity. Every pass class and object you subsequently generate carries the unique issuer ID associated with this initial setup, so getting this correct at the start is vital for long term maintenance.[2][6]

Initial configuration places your account in demo mode. This restriction is important to understand, as it limits the visibility and functionality of your passes. You can only issue passes to test accounts explicitly authorized within the console. I found that developers often stall here, mistakenly assuming their code is broken when they cannot save a pass using a standard personal email. You must invite your test email addresses through the console before any issuance logic will work as expected. Do not overlook the test account configuration step, as it prevents your development work from being blocked by security policies intended for production users.[3][4]

Once your issuer account exists, you need a Google Cloud service account to interact with the API programmatically. You must grant this service account the specific permissions required to manage your wallet objects. Keep the private key associated with this service account strictly isolated from your codebase. It is the core secret that allows your backend to sign valid requests to the Google Wallet infrastructure. Without this secure credential, you lack the ability to generate the signed tokens necessary to invoke the pass-saving flow. I recommend using a secrets management tool to inject this key into your runtime environment.[4][6]

Separating the Pass Class blueprint from user Pass Objects

The mental model for Google Wallet relies on a clear split between the Pass Class and the Pass Object. Treating these as the same entity is a frequent failure pattern. The Class is your static blueprint. It contains the visual identity, your company logo, and the layout fields that remain constant across every single card you issue. Think of this as the master style sheet for your digital asset. By defining this once, you ensure that all passes of a specific type look identical, which preserves your brand integrity across thousands of disparate user devices. You should define this blueprint in the console or via a REST API call before attempting to generate any instances.[1][5]

The Object is the unique, per user instance. It references the Class ID but holds the dynamic data specific to that one user, such as a loyalty point balance, an expiration date, or a unique serial number. By decoupling these, you gain the power to update global branding for every user by simply patching the Class once. This architecture separates the structural definition from the variable content. When you update the Class, the change propagates automatically to every user who has saved that card, eliminating the need to re-issue objects manually. This separation ensures that your data management remains clean and scalable as your user base grows.[5]

In my experience, building this model correctly at the start saves massive amounts of technical debt. It prevents the need to reissue thousands of objects just because you realized a logo needed to be updated or a layout field was improperly scoped. During my work at Kareti, I learned that while the learning curve seems steep, this design pattern is the only way to ensure native lock screen visibility. By investing in a robust class hierarchy early, you avoid the administrative burden of handling individual pass mutations for layout changes. It makes your backend architecture significantly more resilient and easier to maintain over the long term.

Constructing and signing the JWT payload

You do not send raw JSON objects to Google to initiate a save. Your backend constructs a JSON Web Token instead. This token contains a claims object that includes the issuer email, the audience, and the pass data itself. Signing this payload with your service account private key provides the cryptographic proof Google requires to trust the request. This mechanism prevents malicious parties from injecting unauthorized passes into your users' wallets. The server-side generation of this token ensures that the sensitive credentials never touch the client browser. It keeps your authentication handshake secure while allowing the front end to trigger the wallet save flow through a simple signed URL.[1][5]

The JWT payload must explicitly list the generic objects intended for creation. This design allows you to bundle pass definitions directly into the link generation step. If the signature is invalid or the claims are improperly formatted, Google rejects the request and the save button fails to trigger the wallet app. Because the browser interprets the token on the user device, your backend does not need to maintain a stateful connection to the client. This stateless approach simplifies your infrastructure and allows for seamless scaling as traffic increases. You simply generate the token on demand when a user clicks the badge on your website.[3]

js
const claims = {
  iss: credentials.client_email, aud: 'google', origins: [], typ: 'savetowallet', payload: {
    genericObjects: [genericObject]
  }
};
const token = jwt.sign(claims, credentials.private_key, {
  algorithm: 'RS256'
});

Generating the Save to Google Wallet link

The final step involves converting your signed token into a user-facing link. By appending your signed JWT to the standard Google Wallet endpoint, you create a direct trigger that opens the Wallet app or prompts the save flow. This link remains stateless. Your server does not need to track the user progress through the save interaction, which reduces your database overhead. Because the entire process relies on standard web protocols, it integrates well with existing site architectures. You present the generated link as the official button asset, ensuring the user experience matches familiar industry patterns and reduces friction during the onboarding process.[1][5]

This link operates across multiple environments. On Android, it bridges directly to the native Wallet app. On desktop browsers, it displays a QR code that a mobile device can scan. This URL approach replaces the need for a native app and offloads the entire interface layer to the Google ecosystem. I did not need to ship or maintain a native app for this, saving countless hours on UI development. By utilizing the official Google-provided assets, you maintain user trust and avoid the complexity of rendering custom card interfaces that differ across devices. The ecosystem handles the display logic, allowing you to focus on managing your core business data.[5]

Handling rendering and pass updates

Once a user saves the pass, Google takes over rendering. You do not manage screen densities, operating system versions, or device-specific layouts. You provide the JSON metadata, and the Wallet app renders the card as a native component on the lock screen. This operational efficiency is one of the strongest arguments for using the API. It ensures a consistent look and feel for your users regardless of their hardware. You benefit from the native capabilities of the platform without needing to debug display issues on various Android versions. The pass integration feels like a built-in feature of the device from the moment the user interacts with it.[5]

Updating information is straightforward. If a loyalty point balance changes or a date shifts, you send a REST PATCH request to the Google API endpoint for that specific object. Your local database remains the source of truth, while the push to the device happens asynchronously as part of the Wallet synchronization process. This means your system stays performant even when handling thousands of concurrent updates. Because the API handles the synchronization logic, you only need to trigger a push when an event occurs in your system. This event-driven update model keeps your application logic simple and keeps the user information accurate without requiring manual intervention.[5]

For production, build a background worker to handle these mutations. Blocking web requests while waiting for the Google API to confirm an object update creates unnecessary latency and risks system instability during network fluctuations. Queue your update tasks to handle failures with retry logic. Treat these mutations as atomic updates within your system, ensuring that your local record of the pass state always matches the state of the API. This defensive posture is necessary for maintaining consistency in distributed environments where remote states can drift. A reliable background job ensures that your updates succeed even if the network is temporarily unreliable.[5]

Architectural differences: Google versus Apple

Transitioning between Google and Apple Wallet exposes a stark architectural divide. Google Wallet relies on stateless signed-JWT links that define objects and classes via API calls. You manage no physical files on the user device, keeping your infrastructure cloud-centric. This model favors systems that already manage data via RESTful services. In contrast, Apple Wallet requires a different approach that centers on file distribution. The disparity between these two systems is one of the most interesting challenges in digital wallet integration, as it requires you to support both a stateless link model and a stateful package distribution model within the same backend system.[5]

Apple Wallet operates on the PassKit paradigm. It requires you to generate signed pkpass bundles. This process involves creating a manifest containing SHA-1 hashes for every asset, signing the package with an Apple WWDR certificate, and zipping it into a single file for transmission. This requires your server to perform cryptographic packaging for every pass you issue. The operational overhead of managing these certificates and signing the manifests is significantly higher than generating a simple JWT. It requires a more complex security stack to protect the signing keys and ensure the integrity of the distributed bundles during the delivery phase.

Production launch checklist

Before you move to production, review your entire setup against these operational realities. This list ensures that you avoid blocking errors when real users attempt to save your cards. Verify your configuration files, confirm your access tiers, and test your failure modes thoroughly before going live.[2]

  1. Verify all private keys are retrieved from environment variables or a secure vault.
  2. Never commit these files to your repository.
  3. Ensure you have obtained production publishing access via the Google Pay & Wallet Console.
  4. Validate your JWT expiration times to provide enough cushion for user intent, but avoid indefinitely long tokens.
  5. Implement logging for all API interactions, capturing request payloads and error codes for every failure mode.
  6. Set up a reconciliation job to periodically verify that your internal record of objects matches the state stored in Google cloud. These steps build a robust foundation for your production wallet integration.[2][6]
google-walletbackendapijwtdigital-wallet

Sources

  1. https://developers.google.com/wallet/generic/use-cases/jwt
  2. Setting up a Google Wallet API Issuer account  |  Generic pass  |  Google for Developers
  3. Create passes on Web using the Google Wallet API  |  Google Codelabs
  4. Create passes on Android using the Google Wallet API  |  Google Codelabs
  5. Anatomy of a Google Wallet Pass: Every Field Explained — WalletWallet API
  6. Set up Google Wallet certificates and publishing access · Airship Docs

Related articles

Press ? for keyboard shortcuts