npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

google-pass

v0.0.2

Published

NodeJS wrapper for Google Wallet Pass creation

Readme

google-pass

A TypeScript library for generating and signing Google Wallet passes. Provides a typed interface for building JWT payloads compatible with the Google Wallet API, along with utilities to generate the "Add to Google Wallet" save URL.

npm version TypeScript License: ISC


Table of Contents


Installation

npm install google-pass

When using workspaces or a monorepo, reference the package directly in your package.json:

{
  "dependencies": {
    "google-pass": "workspace:*"
  }
}

Authentication

The library requires credentials from a Google Cloud Service Account with access to the Google Wallet API. You can download the service account key file from the Google Cloud Console under IAM & Admin > Service Accounts.

import { GoogleWalletLib } from 'google-pass';

const wallet = new GoogleWalletLib({
  iss: '[email protected]',
  client_email: '[email protected]',
  private_key: '-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----\n',
  issuerName: 'Your Company Name',
});

| Field | Type | Description | | -------------- | -------- | --------------------------------------------------------- | | iss | string | The service account client_email, used as the JWT issuer | | client_email | string | Same as iss, included in the JWT payload | | private_key | string | RSA private key from the service account JSON file | | issuerName | string | Display name shown as the issuer on the pass |

Security: Never expose private_key on the client side. All token generation must happen server-side (Node.js, Edge Functions, etc.).


Core Concepts

Class vs Object

The Google Wallet API uses a two-layer model that applies to all pass types:

Class
  Defines the shared template: branding, venue or merchant details,
  date/time, background colors, logos, and security settings.
  All passes of the same event or campaign share a single class.

  └── Object
        Represents the individual pass assigned to a user.
        Contains user-specific data: holder name, seat, ticket number,
        unique barcode, validity period, points, etc.

Updating a class propagates changes to all associated objects. Updating an object affects only that specific pass.


Static vs Rotating Barcodes

Two barcode modes are available on pass objects:

| Property | barcode | rotatingBarcode | | ----------------- | -------------------------------- | ---------------------------------------------- | | Value | Fixed — always the same | Changes every N seconds using TOTP | | Security | Low — vulnerable to screenshots | High — captured codes expire quickly | | Anti-fraud | No | Yes — server-side secret key required | | Implementation | Simple | Requires TOTP key configuration | | Recommended for | Loyalty cards, low-sensitivity passes | Event tickets, transit, high-value access |

Important: barcode and rotatingBarcode cannot be used simultaneously on the same object. Google will reject the pass or render duplicate barcode elements.

Rotating barcode TOTP key requirements:

The key field must be exactly 40 hexadecimal characters (20 bytes in Base16 encoding).

rotatingBarcode: {
  type: 'QR_CODE',
  alternateText: 'TICKET-VIP-001',
  valuePattern: 'TICKET-VIP-001-{totp_value_0}',
  totpDetails: {
    periodMillis: '30000', // Code regenerates every 30 seconds
    algorithm: 'TOTP_SHA1',
    parameters: [
      {
        key: '3132333435363738393031323334353637383930', // Exactly 40 hex chars
        valueLength: 6,
      },
    ],
  },
},

Security Animation

Configured at the class level, securityAnimation enables an animated rainbow border on the pass inside the native Google Wallet app.

securityAnimation: {
  animationType: 'FOIL_SHIMMER',
}

The FOIL_SHIMMER effect renders only in the native Google Wallet app on iOS and Android. It does not appear in web previews or the Google Pay & Wallet Console emulator.


Usage

Event Tickets

The following example demonstrates the complete flow to create and deliver an event ticket.

import { GoogleWalletLib } from 'google-pass';

const ISSUER_ID = '1234567890123456789'; // Your Issuer ID from Google Pay & Wallet Console

const wallet = new GoogleWalletLib({ /* credentials */ });

const eventClass = {
  id: `${ISSUER_ID}.festival_2026`,
  issuerName: 'Acme Events',
  reviewStatus: 'UNDER_REVIEW',
  hexBackgroundColor: '#1a1a2e',
  eventName: { defaultValue: { language: 'en-US', value: 'Annual Developer Summit 2026' } }
};

const eventObject = {
  id: `${ISSUER_ID}.ticket_001`,
  classId: eventClass.id,
  state: 'ACTIVE',
  ticketHolderName: 'Jane Doe',
  barcode: { type: 'QR_CODE', value: 'TICKET-001' }
};

// 1. Create Class (Handle if it already exists)
let cResult: any = await wallet.createClassEvent(eventClass);
if (cResult.error) cResult = await wallet.patchClassEvent(ISSUER_ID, 'festival_2026', eventClass);

// 2. Create Object (Handle if it already exists)
let oResult: any = await wallet.createObjectEvent(eventObject);
if (oResult.error) oResult = await wallet.patchObjectEvent(ISSUER_ID, 'ticket_001', eventObject);

// 3. Build the payload and generate the save URL
const payload = wallet.createPayloadEvent(Math.floor(Date.now() / 1000), ['https://www.example.com'], eventClass, eventObject);
const saveUrl = wallet.generateSaveUrl(payload);
// → https://pay.google.com/gp/v/save/<signed_jwt>

Loyalty Cards

Loyalty cards are ideal for representing store programs, coffee clubs, and badge collections.

const loyaltyClass = {
  id: `${ISSUER_ID}.coffee_club`,
  issuerName: 'Acme Cafe',
  reviewStatus: 'UNDER_REVIEW',
  programName: 'Coffee Lovers',
  hexBackgroundColor: '#6F4E37',
  programLogo: { sourceUri: { uri: 'https://example.com/logo.png' } }
};

const loyaltyObject = {
  id: `${ISSUER_ID}.loyalty_001`,
  classId: loyaltyClass.id,
  state: 'ACTIVE',
  accountName: 'Jane Doe',
  accountId: 'C-98765',
  loyaltyPoints: {
     label: 'Visits',
     balance: { string: '8 / 10' }
  },
  barcode: { type: 'QR_CODE', value: 'C-98765' }
};

let cResult: any = await wallet.createClassLoyalty(loyaltyClass);
if (cResult.error) cResult = await wallet.patchClassLoyalty(ISSUER_ID, 'coffee_club', loyaltyClass);

let oResult: any = await wallet.createObjectLoyalty(loyaltyObject);
if (oResult.error) oResult = await wallet.patchObjectLoyalty(ISSUER_ID, 'loyalty_001', loyaltyObject);

const payload = wallet.createPayloadLoyalty(Math.floor(Date.now() / 1000), ['https://www.example.com'], loyaltyClass, loyaltyObject);
const saveUrl = wallet.generateSaveUrl(payload);

API Reference

Core Methods

| Method | Description | | --- | --- | | createClassEvent(class: EventTicketClass) | Creates a new Event Class | | createObjectEvent(object: EventTicketObject) | Creates a new Event Object | | getClassEvent(issuerId, identifier) | Retrieves an existing Event Class | | getObjectEvent(issuerId, identifier) | Retrieves an existing Event Object | | patchClassEvent(issuerId, identifier, class) | Updates an existing Event Class | | patchObjectEvent(issuerId, identifier, object)| Updates an existing Event Object | | createPayloadEvent(...) | Builds the JWT payload for Event |

All the methods above exist similarly for Loyalty Cards (e.g., createClassLoyalty, createObjectLoyalty, patchObjectLoyalty).

Push Notifications

You can trigger push notifications when updating a pass by using the pushNotification method:

await wallet.pushNotification(ISSUER_ID, 'ticket_001', 'eventTicketObject', {
  message: { body: 'Your seat has been upgraded!', id: `msg_${Date.now()}`, messageType: 'TEXT' }
});

Valid targets for the type property are: 'loyaltyClass' | 'loyaltyObject' | 'eventTicketClass' | 'eventTicketObject'.


Technical Notes

Handling Existing Passes (409 Conflicts)

When calling createClassEvent or createObjectEvent (or their Loyalty equivalents), if the ID already exists in your issuer account, the Google Wallet API returns an HTTP 409 Conflict.

By design, this library does not throw a JavaScript error when the fetch API call receives a 400-level HTTP response; instead, it returns the error JSON block generated by Google.

To handle this smoothly, you must check for .error on the result and fallback to patching:

let classResult: any = await wallet.createClassLoyalty(myLoyaltyClass);
if (classResult.error) {
  // Already exists, update it instead
  classResult = await wallet.patchClassLoyalty(ISSUER_ID, 'my_identifier', myLoyaltyClass);
}

iat must be in seconds

The JWT iat (issued at) field must be expressed in seconds, not milliseconds.

// Incorrect — causes JWT signature errors
const unixTime = Date.now(); // 1748982876543 (milliseconds)

// Correct
const unixTime = Math.floor(Date.now() / 1000); // 1748982876 (seconds)

Image URLs must be publicly accessible

The logo.sourceUri.uri and heroImage.sourceUri.uri fields must point to resources that are publicly reachable on the internet. If the URL returns a 404 or requires authentication, Google Wallet will reject the pass with a generic error.

ID format requirements

Class and object IDs must follow the format {issuerID}.{your_identifier}. Only alphanumeric characters, ., _, and - are allowed.

// Valid
id: `${ISSUER_ID}.festival_2026`
id: `${ISSUER_ID}.ticket-vip-00123`

// Invalid (spaces and slashes not allowed)
id: `${ISSUER_ID}.festival 2026`
id: `${ISSUER_ID}.ticket/vip/00123`

reviewStatus on existing classes

When updating an already-approved class, always set reviewStatus: 'UNDER_REVIEW'. The status cannot be reverted from UNDER_REVIEW back to DRAFT.


Roadmap

The following pass types are planned for future releases:

  • Generic passes
  • Offers and coupons
  • Gift cards
  • Transit passes
  • Boarding passes

License

ISC