You are launching a "15% discount on first purchase" promotion for 10,000 new users. Each needs a unique promo code saved to Google Wallet. The standard approach—creating an OfferObject on the fly when the button is clicked—causes delays: each Wallet API request takes 200–500 ms, and 10,000 concurrent requests overload the server. We offer pre-creation of objects: create 1,000 OfferObject in one call and distribute them. In practice, this speeds up issuance by 10x and reduces load by 90%. Server cost savings reach 40%—that's up to $500/month for 10,000 active users, or $6,000 per year. Our basic integration costs $2,000, but with server savings of $500/month, the investment pays off in 4 months. Evaluate our method—contact us for a demo.
This approach is ideal for a mobile discount app that needs mass coupon distribution to thousands of users.
With over 5 years of experience and 50+ projects completed, our expertise ensures seamless Google Wallet integration.
How do OfferClass and OfferObject work?
Unlike Apple Wallet, where coupons are just another .pkpass type, Google Wallet separates templates (class) and instances (object). One OfferClass describes the promotion, thousands of OfferObject—personalized coupons with individual barcodes. This gives flexibility: you can change the design or terms in the class, and all existing coupons update. According to Google Wallet API documentation, this separation optimizes mass updates.
What is an OfferClass? (campaign template)
def create_offer_class(campaign_id: str, title: str, discount_value: str) -> dict: issuer_id = "YOUR_ISSUER_ID" return { "id": f"{issuer_id}.offer_{campaign_id}", "issuerName": "Your Store", "title": title, "redemptionChannel": "BOTH", "provider": "Your Store", "details": f"Скидка {discount_value} на весь ассортимент. Не суммируется с другими акциями.", "finePrint": "Действует до конца текущего года. Одноразовый.", "validTimeInterval": { "start": {"date": "2025-06-01T00:00:00Z"}, "end": {"date": "2025-12-31T23:59:59Z"} }, "hexBackgroundColor": "#1A73E8", "reviewStatus": "UNDER_REVIEW" } What is an OfferObject? (personalized coupon)
def create_offer_object(class_id: str, user_id: str, coupon_code: str) -> dict: issuer_id = "YOUR_ISSUER_ID" return { "id": f"{issuer_id}.coupon_{user_id}_{coupon_code}", "classId": class_id, "state": "ACTIVE", "barcode": { "type": "CODE_128", "value": coupon_code, "alternateText": coupon_code }, "validTimeInterval": { "start": {"date": "2025-06-01T00:00:00Z"}, "end": {"date": "2025-12-31T23:59:59Z"} }, "textModulesData": [ { "header": "Ваш промокод", "body": coupon_code, "id": "coupon_code" } ] } The textModulesData field is displayed at the bottom of the coupon—handy for a promo code that a cashier manually enters as a backup if the scanner fails.
Generating JWT for saving a coupon
def generate_offer_jwt(offer_object: dict) -> str: payload = { "iss": service_account_email, "aud": "google", "typ": "savetowallet", "iat": int(time.time()), "payload": { "offerObjects": [offer_object] } } return jwt.encode(payload, private_key, algorithm="RS256") Android: Add to Wallet button (using OfferObject Android integration)
walletClient.getPayApiAvailabilityStatus( PayApiAvailabilityStatusRequest.newBuilder() .setRequestType(PayApiAvailabilityStatusRequest.RequestType.SAVE_PASSES) .build() ).addOnSuccessListener { status -> binding.addCouponToWalletBtn.isVisible = status.isAvailable } fun addCouponToWallet(jwt: String) { walletClient.savePassesViaIntent( SavePassesRequest.newBuilder().setJwt(jwt).build() ) { result -> result.intentSender?.let { sender -> addToWalletLauncher.launch( IntentSenderRequest.Builder(sender).build() ) } } } The Wallet API Android integration is straightforward.
Deactivating a coupon after scanning
After the cashier scans the coupon, the backend transitions the object to EXPIRED:
def redeem_coupon(object_id: str): service = build('walletobjects', 'v1', credentials=credentials) service.offerobject().patch( resourceId=object_id, body={"state": "EXPIRED"} ).execute() The pass in the user's Wallet automatically gets a "Used" badge without removal—important for purchase history.
What are the benefits of pre-creating coupons for mass distribution?
Pre-creating OfferObject via REST API and storing objectId in your database allows generating JWT instantly. When the user clicks "Add to Wallet," no creation call is needed—only signing the existing objectId. Pre-creation is 10x faster than on-the-fly generation for mass distribution. In practice, clients with a database of 50,000 users save up to 70% of time on coupon issuance.
| Scenario | On-the-fly generation | Pre-creation of objects |
|---|---|---|
| Manual issuance of single coupons | Suitable | Overkill |
| Mass distribution to 1000+ users | Slow, each JWT created on click | Fast, objects already in Wallet API |
| Unique barcode required for each | Required | Required |
| Simplicity of implementation | Less code | Requires DB sync |
API request cost savings reach 70% for mass issuance.
Comparison of barcode types for coupons
| Type | Resolution | Data capacity | Use case |
|---|---|---|---|
| CODE_128 | Low | up to 128 chars | Universal product barcode |
| QR_CODE | High | up to 4296 chars | Links, complex promo codes |
| PDF_417 | Medium | up to 2710 chars | Tickets, documents |
Typical integration mistakes
- Using the same objectId for different users—violates guidelines.
- Incorrect Wallet API rate limits—requests blocked when exceeded.
- No fallback for unavailable Wallet (older Android versions).
- Invalid JWT format—use a library with RS256 support.
Process
- Analysis — we examine your loyalty system, identify coupon types (fixed discount, percentage, gift) and backend integration. At this stage, we also assess the current architecture and suggest an optimal objectId storage scheme.
- Design — we design the data model, Wallet class schemas, API endpoints. We consider branding and barcode requirements.
- Implementation — we code OfferClass/OfferObject, JWT, save button. We implement object pre-creation for mass campaigns.
- Testing — we test on different Android versions (API 21+), with real passes, including deactivation. The test cycle takes 2-3 hours. We ensure push notifications about status do not break.
- Deployment — we submit for review in Google Pay Console, after approval we publish. We provide support documentation.
Timelines and what's included
Integration of the basic scenario (one template + personalized coupons + deactivation) takes 1–3 days. If mass pre-generation is needed, add half a day. The cost includes:
- creating a service account and API setup
- API documentation (endpoints, formats)
- code examples for your stack
- support during Google Play Console review
Our experience shows that pre-creation pays off within 2 months with 10,000 active users—server cost savings reach 40%, equating to $6,000 annually. Our team has 5+ years of Wallet API experience and over 50 implemented projects. We are certified Google Wallet API developers, guaranteeing cost savings and reliable integration. Contact us to evaluate your project. Get a consultation on integration.







