> For the complete documentation index, see [llms.txt](https://docs.webtonative.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.webtonative.com/javascript-apis/wallet-1/wallet-creating-passes.md).

# Wallet - Creating Passes

This page covers the part that happens **before** you ever call `Wallet.addPasses` — building and signing the pass itself, on your own backend. WebToNative never creates, edits, or signs a pass; it only hands whatever you give it to the OS wallet app. See [Google Wallet / Apple Wallet](broken://pages/cb4b9a4539b59b6185ef742b2ac5c7af694fb33e) for the JS API this feeds into.

***

## Google Wallet (Android)

A Google Wallet pass isn't a file you generate once and host — it's a **JWT** your backend signs on the fly (or a save link built from one), referencing pass data that lives in your Google Wallet Issuer account.

### 1. Create a Google Wallet Issuer Account

Sign up at the [Google Wallet Business Console](https://pay.google.com/business/console) and get your **Issuer ID** — every pass class/object you create is scoped to it.

### 2. Set Up a Service Account

In Google Cloud Console, enable the **Google Wallet API**, create a service account, and download its JSON key. This key's private key is what signs the JWT in step 4 — keep it server-side only.

### 3. Define a Pass Class

A **class** is the reusable template for a pass type — the loyalty program, the event, the flight route. Create it once via the Google Wallet REST API (`POST https://walletobjects.googleapis.com/walletobjects/v1/<type>Class`), picking the type that matches what you're issuing:

`genericClass` · `loyaltyClass` · `offerClass` · `eventTicketClass` · `flightClass` · `giftCardClass` · `transitClass`

### 4. Create a Pass Object

An **object** is one instance of that class for one specific user — their points balance, their seat, their gift card value. Create it via the matching `<type>Object` endpoint (e.g. `loyaltyObject`), referencing the `classId` from step 3.

### 5. Build & Sign the Save JWT

To hand a pass to `Wallet.addPasses`, sign a JWT whose claims are:

| Claim     | Value                                                                                                                                                                                                                                                                                                                                                                               |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `iss`     | Your service account's email address.                                                                                                                                                                                                                                                                                                                                               |
| `aud`     | Always `"google"`.                                                                                                                                                                                                                                                                                                                                                                  |
| `typ`     | Always `"savetowallet"`.                                                                                                                                                                                                                                                                                                                                                            |
| `iat`     | Issued-at timestamp.                                                                                                                                                                                                                                                                                                                                                                |
| `payload` | An object keyed by the class/object type, e.g. `{ "loyaltyObjects": [{ "id": "...", "classId": "..." }] }`. Accepted keys: `genericClasses`/`genericObjects`, `loyaltyClasses`/`loyaltyObjects`, `offerClasses`/`offerObjects`, `eventTicketClasses`/`eventTicketObjects`, `flightClasses`/`flightObjects`, `giftCardClasses`/`giftCardObjects`, `transitClasses`/`transitObjects`. |

Sign with **RS256** using the service account's private key.

{% tabs %}
{% tab title="Node.js" %}

```javascript
const jwt = require("jsonwebtoken");
const serviceAccount = require("./service-account.json");

function buildSaveJwt(loyaltyObject) {
  const claims = {
    iss: serviceAccount.client_email,
    aud: "google",
    typ: "savetowallet",
    iat: Math.floor(Date.now() / 1000),
    payload: {
      loyaltyObjects: [loyaltyObject],
    },
  };

  return jwt.sign(claims, serviceAccount.private_key, { algorithm: "RS256" });
}
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
use Firebase\JWT\JWT;

function buildSaveJwt(array $loyaltyObject, array $serviceAccount): string
{
    $claims = [
        'iss' => $serviceAccount['client_email'],
        'aud' => 'google',
        'typ' => 'savetowallet',
        'iat' => time(),
        'payload' => [
            'loyaltyObjects' => [$loyaltyObject],
        ],
    ];

    return JWT::encode($claims, $serviceAccount['private_key'], 'RS256');
}
```

Requires `firebase/php-jwt` (`composer require firebase/php-jwt`).
{% endtab %}
{% endtabs %}

### Handing It to WebToNative

The signed JWT is exactly what `Wallet.addPasses` expects in `passes`:

```javascript
window.WTN.Wallet.addPasses({
  passes: [savedJwtFromYourBackend],
});
```

You can also wrap it as `https://pay.google.com/gp/v/save/<jwt>` and pass that string instead — both are accepted.

***

## Apple Wallet (iOS)

An Apple Wallet pass **is** a file — a `.pkpass`, which is really just a signed zip. Unlike Google Wallet, there's no live API call per pass; you build and sign the file, then host it or hand over its bytes.

### 1. Get a Pass Type ID & Certificates

In your [Apple Developer account](https://developer.apple.com/account/resources/identifiers/list/passTypeId), register a **Pass Type ID** (e.g. `pass.com.example.loyalty`) and generate its certificate. You'll also need Apple's **WWDR intermediate certificate**.

### 2. Build the Pass Bundle

A pass is a folder containing:

* `pass.json` — the pass's data: style (`boardingPass`, `coupon`, `eventTicket`, `storeCard`, or `generic`), your Pass Type ID, a `serialNumber` + `authenticationToken` (needed if you'll later use `getPasses`/`checkPassExists`/`removePass`), and the fields shown on the pass (header/primary/secondary/auxiliary/back fields).
* Images — `icon.png`, `logo.png`, and style-specific assets (`@2x`/`@3x` variants).

### 3. Generate & Sign the Manifest

`manifest.json` lists a SHA hash of every file in the bundle. Sign that manifest with your Pass Type ID certificate + the WWDR certificate to produce a detached `signature` file:

```bash
openssl smime -binary -sign \
  -certfile wwdr.pem \
  -signer signerCert.pem \
  -inkey signerKey.pem \
  -in manifest.json \
  -out signature -outform DER
```

### 4. Package as `.pkpass`

Zip `pass.json`, `manifest.json`, `signature`, and the images together (flat, no subfolder) with a `.pkpass` extension.

{% hint style="info" %}
Hand-rolling steps 2–4 for every pass is tedious — most teams use a maintained pass-generation library that wraps this pipeline. For Node.js, [`passkit-generator`](https://github.com/alexandercerutti/passkit-generator) is a commonly used one:

```javascript
const { PKPass } = require("passkit-generator");
const fs = require("fs");

async function buildPkpass(serialNumber, points) {
  const pass = await PKPass.from(
    {
      model: "./pass-models/loyalty.pass",
      certificates: {
        wwdr: fs.readFileSync("./certs/wwdr.pem"),
        signerCert: fs.readFileSync("./certs/signerCert.pem"),
        signerKey: fs.readFileSync("./certs/signerKey.pem"),
        signerKeyPassphrase: process.env.PASS_SIGNER_PASSPHRASE,
      },
    },
    { serialNumber, authenticationToken: generateToken() }
  );

  pass.secondaryFields.push({ key: "points", label: "POINTS", value: points });
  return pass.getAsBuffer();
}
```

There isn't a single dominant equivalent library across every backend language — for PHP or others, the OpenSSL-based signing steps above are the language-agnostic fallback (shell out to `openssl`, or call it via an extension).
{% endhint %}

### 5. Host or Base64-Encode It

Once you have the `.pkpass` bytes:

* **Host it** at an HTTPS URL (serve with `Content-Type: application/vnd.apple.pkpass`), or
* **Base64-encode it** and send the string directly.

### Handing It to WebToNative

```javascript
// Hosted
window.WTN.Wallet.addPasses({
  urls: ["https://example.com/passes/loyalty-card.pkpass"],
});

// Or base64
window.WTN.Wallet.addPasses({
  base64s: [pkpassBase64String],
});
```

***

## See Also

* [Google Wallet / Apple Wallet](broken://pages/cb4b9a4539b59b6185ef742b2ac5c7af694fb33e) — the `Wallet.*` JavaScript API this feeds into.
* [Google Wallet API documentation](https://developers.google.com/wallet)
* [Apple Wallet developer documentation](https://developer.apple.com/documentation/walletpasses)
