June 2026 · 6 min read
Integrating with the Google Wallet Passes API: A Developer's Retrospective
Moving beyond static links to native Google Wallet passes. I break down the JWT signing, class architecture, and the mechanics of native loyalty integration.

Native Google Wallet integration provides the only reliable bridge to the user lock screen
Standard digital business cards are just websites. They disappear into browser tabs and browser history, forgotten as soon as the user closes the window. Real digital utility lives on the native phone wallet, where it is easily accessible at the exact moment of transaction. During my work on Kareti, I learned that while the Google Wallet API has a learning curve, it remains the only way to achieve that level of native visibility.
The API is a structured bridge between your backend and the user mobile device. It allows you to digitize loyalty programs, event tickets, or membership cards without forcing the user to install a standalone app. You define a blueprint that the Google ecosystem renders as a first class citizen on their lock screen. This is a significant shift from relying on email attachments or web redirects. Your users get a persistent link to their data.
I focus on the API because it handles the complexities of rendering and localization. You do not need to worry about screen densities or differing operating system versions. You provide the JSON data, and the Wallet app renders the card properly. The barrier to entry for users is low, requiring only a single tap to save the pass to their account.
Designing the pass class as a static blueprint
You must distinguish between a Pass Class and a Pass Object. I think of the Class as a static template or a blueprint. It defines the card visual identity, including the company logo, colors, and layout fields that stay constant for every user. You define this once in the Google Cloud Console or via a REST API call. It acts as the master reference for all generated instances.
The Pass Object represents the individual instance held by a specific user. It contains the dynamic, unique data like the loyalty points, the unique barcode, or a specific expiration date. You create this object dynamically as you serve a request to your backend. Separating these two ensures that a design change, like updating a store address, propagates automatically to every user who has saved that card without needing to reissue their individual objects.
Managing these classes requires foresight. If you ship a class with a typo in the logo URL or a missing field, fixing it later is tedious. I recommend versioning your class IDs during development. Once a class is active, try to treat it as immutable. When you launch, create a class named LoyaltyClass_v1. If you need to make breaking changes to the layout, create LoyaltyClass_v2. This avoids unexpected state changes for existing users who already have a saved pass based on the old template.
Minting secure credentials through JWT signing
The core technical hurdle in this integration is the JWT signing process. You do not send a raw JSON object to the API. You must wrap your payload in a JSON Web Token and sign it using a RS256 private key. This ensures the authenticity of the card data. If the signature is invalid, Google rejects the request to generate the button. This prevents unauthorized entities from spoofing your passes.
Your backend needs a Google Cloud Service Account with the proper permissions enabled. Use the private key provided by this account to sign the claims payload before serving it to your frontend. The payload itself must include the issuer ID and the specific class and object IDs. Without a valid RS256 signature, the browser cannot verify that the request originated from your authenticated server.
The signing step is essentially a cryptography handshake. You are telling Google that you hold the secret key associated with the registered issuer ID. Because the process is stateless, the frontend does not need to talk directly to the backend to generate the final pass object. Instead, the backend generates the token, which the browser sends to Google to initialize the save flow.
{
"iss": "service-account-email",
"aud": "google",
"origins": [
"https://yourdomain.com"
],
"payload": {
"loyaltyObjects": [
{
"classId": "ISSUER_ID.CLASS_ID",
"id": "ISSUER_ID.OBJECT_ID"
}
]
}
}The mechanics of the 'Save to Google Wallet' badge
Once your backend has minted the signed JWT, you need a way to trigger the saving flow in the user browser. This is done via a specialized hyperlink that carries your encoded token. You should render this as the official 'Add to Google Wallet' badge, which provides a familiar interface for the user.
When they tap it, the browser interprets the link, validates the token against your issuer credentials, and prompts the user to add the pass to their device. You do not need to build custom UI components for the saving action, as Google provides the assets. Stick to these official assets to maintain user trust and avoid conversion friction.
The link format includes the base URL for the save request with the token appended as a parameter. It is a simple GET request that triggers a redirect chain. The final destination is the Google-managed page that confirms the save operation. The entire process happens within the user browser, so your server does not need to maintain a stateful connection once it sends the signed link to the client.
Handling offline updates and class mutations
One of the most powerful features of the API is the ability to push updates to existing passes. If a business changes its operating hours, you can push a patch to the specific Pass Class. Because every user Pass Object references that Class, the update appears on their phone automatically. This mechanism removes the need to manually send notifications for every minor change.
For individual data changes, like a point balance update, you need to update the specific Pass Object. I recommend building a background worker to handle these mutations. You do not want to block your primary web requests while waiting for the Google API to confirm an update. 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 update process is asynchronous. You make a REST call to the Google API endpoint with the updated object fields. Google accepts the request and pushes the change to the user device during the next sync interval. If your update fails due to a network error, you must handle the retry logic in your background queue. This is a classic distributed systems problem where the remote state may drift from the local record.
A practical checklist for production deployment
Before you deploy your integration to production, verify that you have covered these essential points. This checklist is the result of lessons learned from my own implementation cycle. It ensures that your backend handles the external API dependencies with the necessary defensive posture.
Ensure your Google Cloud Service Account has the Google Wallet API enabled and that you are using a secure private key storage strategy. You should never commit keys to version control. Load them from a secure vault or an environment variable at runtime.
Test your JWT generation logic against the sandbox environment before going live. Validate that the expiration times in your tokens provide enough buffer for the user intent to save the card. A short token expiration period can lead to save failures if the user waits too long after the page load to click the badge.
Implement a logging system that tracks the lifecycle of every pass object creation. If an API request fails, you need to know exactly which payload caused the error. Log the request parameters and the API response codes for easier debugging.
Verify your class design by using the official pass builder tool to ensure it looks correct on both light and dark mode displays. Aesthetic issues on the pass card itself are often harder to debug than API errors because they are visual.
Setup a periodic reconciliation job to query the API for your existing pass objects. This ensures your local data state hasn't drifted from reality. If you find discrepancies, use the reconciliation job to patch the object data so that the user experience remains consistent.
Sources
- Loyalty card overview | Loyalty cards | Google for Developers
- Everything you need to know about Google Wallet - Google Developers Blog
- Issuing passes for web, email, SMS | Loyalty cards | Google for Developers
- Introducing the Google Wallet API - Google Developers Blog
- Google Wallet API tutorial for Google Developers | Google I/O 2022
- Building a Web Service for Google Wallet Passes with Node.js: A Comprehensive Guide
Related articles
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.
From Paper to Structured Data: Building a Lightweight OCR Profile Builder
A deep dive into chaining Vision APIs with structured LLM schemas to convert messy, physical cards and flyers into reliable database records during onboarding.