> 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.md).

# Wallet

Functions to add passes, loyalty cards, tickets, boarding passes, coupons, and similar to the device's built-in wallet app Google Wallet on Android and Apple Wallet on iOS.

Your backend creates the actual pass (a signed Google Wallet JWT on Android, a signed `.pkpass` file on iOS); WebToNative's job is handing that ready-made pass to the OS wallet app and reporting back whether it was saved.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

> **Platform support:** `isSupported` and `addPasses` work on both Android and iOS. `getName` is Android-only. `getPasses`, `checkPassExists`, `removePass`, and `replacePass` are iOS-only — see [Platform Differences](#platform-differences).

{% hint style="warning" %}
**Read this before you write error-handling code.** The two platforms use different field names and nesting for callback responses (`success` vs. a nested `data`/`error`), and iOS's `addPasses` resolves several "unsuccessful" outcomes (cancelled, already-in-wallet) through the *success* shape, not the failure one. Skimming the tables below and skipping straight to the JSON examples in [Callback Response Format](#callback-response-format) will save you a debugging session.
{% endhint %}

***

## Before You Start

WebToNative doesn't create or sign passes for you — you need one already built per platform:

* **Android (Google Wallet):** a signed JWT from the [Google Wallet Issuer API](https://developers.google.com/wallet), or the `https://pay.google.com/gp/v/save/<jwt>` save link Google returns for one.
* **iOS (Apple Wallet):** a signed `.pkpass` file, either as a URL your server hosts or as base64-encoded data — built with a Pass Type ID from your Apple Developer account.

Building and signing that pass — setting up your Issuer account and pass classes on Android, or generating and signing the `.pkpass` bundle on iOS — is covered step by step, with backend code examples, on [Creating & Signing Wallet Passes](broken://pages/1ca7ccdb0071e64d94a1041a73e1bc36aad09e48). Come back here once you have a JWT (Android) or a `.pkpass` URL/base64 string (iOS) in hand.

***

## Setting Up Wallet

1. Go to your **WebToNative dashboard** → **Add-ons** → **Wallet** and enable it.
2. No further credentials are required on the WebToNative side — pass creation and signing happens on your own backend.

{% hint style="info" %}
Every function below is a no-op unless the add-on is enabled in the dashboard.
{% endhint %}

***

## JavaScript API Reference

### isSupported

Checks whether the device has a wallet available to add passes to.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Wallet.isSupported({
  callback: function (response) {
    console.log(response.isSupported);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { isSupported } from "webtonative/Wallet";

isSupported({
  callback: (response) => {
    console.log(response.isSupported);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response — Android:**

```json
{ "type": "isWalletSupported", "success": true, "isSupported": true }
```

```json
{
  "type": "isWalletSupported",
  "success": false,
  "error": { "code": "AVAILABILITY_CHECK_FAILED", "message": "..." }
}
```

Android error codes for this call: `NOT_INITIALIZED`, `AVAILABILITY_CHECK_FAILED`, `UNEXPECTED_ERROR`.

**Callback Response — iOS:**

```json
{
  "type": "isWalletSupported",
  "success": true,
  "data": { "isSupported": true, "canAddPass": true }
}
```

iOS always resolves this call successfully (there is no failure shape for `isSupported` on iOS) and nests the result under `data`, alongside an extra `canAddPass` flag not present on Android — check `data.isSupported` there, not a top-level field.

{% hint style="warning" %}
The two platforms do **not** return the same shape: Android is flat (`isSupported` at the top level, and it can fail), iOS is nested under `data` (with an extra `canAddPass`, and it never fails). Branch on platform if you need the exact field, or just treat `response.isSupported ?? response.data?.isSupported` as the supported flag.
{% endhint %}

***

### getName

Returns the name of the wallet product available on the device (e.g. `"Google Wallet"`).

> Android only. The callback is never invoked on iOS.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Wallet.getName({
  callback: function (response) {
    console.log(response.productName);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getName } from "webtonative/Wallet";

getName({
  callback: (response) => {
    console.log(response.productName);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

```json
{ "type": "getWalletName", "success": true, "productName": "Google Wallet" }
```

```json
{
  "type": "getWalletName",
  "success": false,
  "error": { "code": "UNEXPECTED_ERROR", "message": "Product name is null." }
}
```

***

### addPasses

Adds one or more passes to the device wallet. This is the main function of the add-on — works cross-platform, but the pass format each platform expects is different, so give each platform the field it understands:

* **Android:** put the pass string(s) — signed JWT, or a `pay.google.com/gp/v/save/...` save link — in `passes`. `urls` / `base64s` are used as a fallback **only when `passes` is omitted entirely** — if you pass a non-empty `passes` array alongside `urls`/`base64s`, the latter are ignored rather than combined; use `passes` for clarity.
* **iOS:** use `urls` for a hosted `.pkpass` file, or `base64s` for base64-encoded `.pkpass` data. `passes` also works on iOS — each entry is auto-classified as a URL or base64 string and routed accordingly. If you pass both non-empty `urls` and `base64s` together, only `urls` is used; `base64s` is ignored.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
// Android: a signed Google Wallet JWT
window.WTN.Wallet.addPasses({
  passes: [googleWalletJwt],
  callback: function (response) {
    console.log(response.success);
  },
});

// iOS: a hosted .pkpass file
window.WTN.Wallet.addPasses({
  urls: ["https://example.com/passes/loyalty-card.pkpass"],
  callback: function (response) {
    console.log(response.success, response.data);
  },
});

// iOS: base64-encoded .pkpass data
window.WTN.Wallet.addPasses({
  base64s: [pkpassBase64String],
  callback: function (response) {
    console.log(response.success, response.data);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { addPasses } from "webtonative/Wallet";

// Android: a signed Google Wallet JWT
addPasses({
  passes: [googleWalletJwt],
  callback: (response) => {
    console.log(response.success);
  },
});

// iOS: a hosted .pkpass file
addPasses({
  urls: ["https://example.com/passes/loyalty-card.pkpass"],
  callback: (response) => {
    console.log(response.success, response.data);
  },
});

// iOS: base64-encoded .pkpass data
addPasses({
  base64s: [pkpassBase64String],
  callback: (response) => {
    console.log(response.success, response.data);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                                                                                                       |
| ---------- | ---------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `passes`   | `String[]` | No\*     | Android: signed JWTs or Google Wallet save links, one per pass. iOS: any mix of `.pkpass` URLs or base64 strings — auto-detected. |
| `urls`     | `String[]` | No\*     | iOS: `.pkpass` file URLs. Android: used only as a fallback when `passes` is omitted.                                              |
| `base64s`  | `String[]` | No\*     | iOS: base64-encoded `.pkpass` data. Android: used only as a fallback when `passes` is omitted.                                    |
| `callback` | `Function` | No       | Callback function invoked with the response.                                                                                      |

\* Provide at least one of `passes` / `urls` / `base64s`.

**Callback Response:** see [Callback Response Format](#callback-response-format) — the shape differs by platform.

***

### getPasses

Reads passes already in the wallet — every pass of a given type, or one specific pass.

> iOS only.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
// A specific pass
window.WTN.Wallet.getPasses({
  passTypeIdentifier: "pass.com.example.loyalty",
  serialNumber: "12345",
  callback: function (response) {
    console.log(response);
  },
});

// Every pass of a type
window.WTN.Wallet.getPasses({
  passType: "all",
  callback: function (response) {
    console.log(response);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getPasses } from "webtonative/Wallet";

// A specific pass
getPasses({
  passTypeIdentifier: "pass.com.example.loyalty",
  serialNumber: "12345",
  callback: (response) => console.log(response),
});

// Every pass of a type
getPasses({
  passType: "all",
  callback: (response) => console.log(response),
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key                  | Type       | Required | Description                                                       |
| -------------------- | ---------- | -------- | ----------------------------------------------------------------- |
| `passTypeIdentifier` | `String`   | No       | Paired with `serialNumber` to look up one specific pass.          |
| `serialNumber`       | `String`   | No       | Paired with `passTypeIdentifier`.                                 |
| `passType`           | `String`   | No       | Use `"all"` for every pass, or a specific pass type to filter by. |
| `callback`           | `Function` | No       | Callback function invoked with the response.                      |

**Callback Response:**

The `type` field on this response is always `"getWalletPasses"` (not `"getPasses"`) — filter on that if you're listening for it outside the SDK's own callback registration. The response shape also differs depending on which lookup you made:

```json
// passTypeIdentifier + serialNumber lookup, pass found
{ "type": "getWalletPasses", "success": true, "data": { "pass": { "...": "..." } } }
```

```json
// passTypeIdentifier + serialNumber lookup, pass NOT found
// note: "pass" is the empty string "" here, not null
{ "type": "getWalletPasses", "success": true, "data": { "pass": "" } }
```

```json
// passType: "all" (or a specific pass type), zero or more results
{ "type": "getWalletPasses", "success": true, "data": { "passes": [ { "...": "..." } ] } }
```

```json
{
  "type": "getWalletPasses",
  "success": false,
  "error": { "code": "INVALID_PAYLOAD", "message": "passType \"foo\" is not recognized." }
}
```

***

### checkPassExists

Checks whether a pass is already in the wallet — either by identifier, or by handing over the pass itself (its URL or base64 data).

> iOS only.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Wallet.checkPassExists({
  passTypeIdentifier: "pass.com.example.loyalty",
  serialNumber: "12345",
  callback: function (response) {
    console.log(response);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { checkPassExists } from "webtonative/Wallet";

checkPassExists({
  passTypeIdentifier: "pass.com.example.loyalty",
  serialNumber: "12345",
  callback: (response) => console.log(response),
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key                  | Type       | Required | Description                                    |
| -------------------- | ---------- | -------- | ---------------------------------------------- |
| `passTypeIdentifier` | `String`   | No\*     | Paired with `serialNumber`.                    |
| `serialNumber`       | `String`   | No\*     | Paired with `passTypeIdentifier`.              |
| `url`                | `String`   | No\*     | Alternatively, the pass's `.pkpass` URL.       |
| `base64`             | `String`   | No\*     | Alternatively, the pass's base64-encoded data. |
| `callback`           | `Function` | No       | Callback function invoked with the response.   |

\* Provide either `passTypeIdentifier` + `serialNumber`, or `url`, or `base64`.

**Callback Response:**

```json
{ "type": "checkPassExists", "success": true, "data": { "isAvailable": true } }
```

```json
{
  "type": "checkPassExists",
  "success": false,
  "error": { "code": "INVALID_PAYLOAD", "message": "..." }
}
```

***

### removePass

Removes a pass from the wallet — either by identifier, or by handing over the pass itself.

> iOS only.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Wallet.removePass({
  passTypeIdentifier: "pass.com.example.loyalty",
  serialNumber: "12345",
  callback: function (response) {
    console.log(response.success);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { removePass } from "webtonative/Wallet";

removePass({
  passTypeIdentifier: "pass.com.example.loyalty",
  serialNumber: "12345",
  callback: (response) => console.log(response.success),
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key                  | Type       | Required | Description                                    |
| -------------------- | ---------- | -------- | ---------------------------------------------- |
| `passTypeIdentifier` | `String`   | No\*     | Paired with `serialNumber`.                    |
| `serialNumber`       | `String`   | No\*     | Paired with `passTypeIdentifier`.              |
| `url`                | `String`   | No\*     | Alternatively, the pass's `.pkpass` URL.       |
| `base64`             | `String`   | No\*     | Alternatively, the pass's base64-encoded data. |
| `callback`           | `Function` | No       | Callback function invoked with the response.   |

\* Provide either `passTypeIdentifier` + `serialNumber`, or `url`, or `base64`.

**Callback Response:**

```json
{
  "type": "removePass",
  "success": true,
  "data": {
    "removed": true,
    "passTypeIdentifier": "pass.com.example.loyalty",
    "serialNumber": "12345"
  }
}
```

```json
{
  "type": "removePass",
  "success": false,
  "error": { "code": "PASS_NOT_FOUND", "message": "This pass is not present in the user's Wallet." }
}
```

***

### replacePass

Replaces a pass already in the wallet with an updated version — e.g. after a loyalty card's points balance changes.

> iOS only.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Wallet.replacePass({
  url: "https://example.com/passes/loyalty-card.pkpass",
  callback: function (response) {
    console.log(response.success);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { replacePass } from "webtonative/Wallet";

replacePass({
  url: "https://example.com/passes/loyalty-card.pkpass",
  callback: (response) => console.log(response.success),
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                            |
| ---------- | ---------- | -------- | ------------------------------------------------------ |
| `url`      | `String`   | No\*     | The updated pass's `.pkpass` URL.                      |
| `base64`   | `String`   | No\*     | Alternatively, the updated pass's base64-encoded data. |
| `callback` | `Function` | No       | Callback function invoked with the response.           |

\* Provide `url` or `base64`.

**Callback Response:**

```json
{ "type": "replacePass", "success": true, "data": { "replace": true } }
```

```json
{
  "type": "replacePass",
  "success": false,
  "error": { "code": "PASS_NOT_FOUND", "message": "This pass is not present in the user's Wallet." }
}
```

***

## Callback Response Format

Every Wallet callback response carries a `type` and a `success` boolean (**not** `isSuccess` — both platforms use `success`). Where the payload beyond that lives — top-level, under `data`, or under `error` — differs by platform and by function, and `addPasses` in particular differs by platform in ways worth reading carefully.

### Android — single pass

```json
{ "type": "addWalletPasses", "isEncodedData": true, "success": true }
```

```json
{
  "type": "addWalletPasses",
  "isEncodedData": true,
  "success": false,
  "error": { "code": "SAVE_ERROR", "message": "..." }
}
```

{% hint style="info" %}
Every `addWalletPasses` response on Android — success or failure, single pass or batch — carries an extra `isEncodedData: true` field. It's an internal transport flag (the payload is URL-encoded on its way to the WebView for this call type); it isn't meaningful to your app, but don't be surprised to see it — it is not stripped before your callback runs.
{% endhint %}

### Android — multiple passes

All succeeded:

```json
{ "type": "addWalletPasses", "isEncodedData": true, "success": true }
```

One or more failed — `data` is keyed by the index of the pass in the array you sent, `true` for a pass that saved and an error object for one that didn't:

```json
{
  "type": "addWalletPasses",
  "isEncodedData": true,
  "success": false,
  "data": {
    "0": true,
    "1": { "code": "INVALID_PASS", "message": "..." }
  }
}
```

**Android error codes:** `NOT_INITIALIZED`, `AVAILABILITY_CHECK_FAILED`, `INVALID_PASS`, `CANCELED`, `SAVE_ERROR`, `EMPTY_BATCH`, `BATCH_IN_PROGRESS`, `UNEXPECTED_ERROR`.

### iOS — single pass

A single pass always **resolves** through the success shape for every expected outcome — added, cancelled by the user, or already in the wallet — and only ever fails (the `success: false` shape) for a genuinely invalid pass or an unexpected native error. `data.status` and the failure shape are mutually exclusive: a response never has both.

```json
{ "type": "addWalletPasses", "success": true, "data": { "status": "added" } }
```

```json
{ "type": "addWalletPasses", "success": true, "data": { "status": "cancelled" } }
```

```json
{
  "type": "addWalletPasses",
  "success": false,
  "error": { "code": "INVALID_PASS_DATA", "message": "..." }
}
```

`data.status` (success shape only) is one of: `added`, `cancelled`, `already_exists`, `unsupported`. It never contains `invalid_pass` or `error` — those two outcomes always come back through the failure shape instead, with a SNAKE\_CASE `error.code` from the table below (e.g. `INVALID_PASS_DATA`, `UNABLE_TO_CREATE_ADD_CONTROLLER`, `NO_PRESENTER_AVAILABLE`, `PASS_LIBRARY_UNAVAILABLE`) rather than the literal string `"invalid_pass"` or `"error"`.

### iOS — multiple passes

All succeeded — minimal response:

```json
{ "type": "addWalletPasses", "success": true, "data": {} }
```

Partial or total failure — note this is the `success: false` shape, and the summary is nested under **`error`**, not `data`:

```json
{
  "type": "addWalletPasses",
  "success": false,
  "error": {
    "total": 3,
    "succeededCount": 2,
    "failedCount": 1,
    "failedPasses": [
      { "error": "The .pkpass file is malformed...", "index": 2 }
    ]
  }
}
```

Each `failedPasses` entry has an `error` message string plus, when it can be determined, the batch `index` of the failed item (falling back to an `identifier` string like `"urls[2]"` if the index can't be parsed) — there is no separate `code` field in these entries.

Every other function (`isSupported`, `getName`, `getPasses`, `checkPassExists`, `removePass`, `replacePass`) reports `{ type, success, ...data }` on success and `{ type, success: false, error: { code, message } }` on failure, following the same pattern as above for its platform — see each function's own **Callback Response** section above for the exact shape of `data`.

***

## Platform Differences

| Function          | Android | iOS |
| ----------------- | :-----: | :-: |
| `isSupported`     |    ✅    |  ✅  |
| `getName`         |    ✅    |  ❌  |
| `addPasses`       |    ✅    |  ✅  |
| `getPasses`       |    ❌    |  ✅  |
| `checkPassExists` |    ❌    |  ✅  |
| `removePass`      |    ❌    |  ✅  |
| `replacePass`     |    ❌    |  ✅  |

***

## Implementation Checklist

### WebToNative Dashboard

* [ ] Wallet add-on enabled

### Your Backend

* [ ] Android: signing Google Wallet JWTs via the Google Wallet Issuer API
* [ ] iOS: signing `.pkpass` files with a Pass Type ID from your Apple Developer account

### Your Website

* [ ] Imported the [WebToNative JavaScript bridge](https://docs.webtonative.com/javascript-apis/getting-started)
* [ ] Calling `isSupported` before showing an "Add to Wallet" button, so it isn't shown where there's nothing to add to — remember iOS nests the flag under `data.isSupported`, Android puts it at the top level
* [ ] Sending the pass in the field each platform expects — `passes` (Android) vs. `urls`/`base64s` (iOS) — rather than assuming one shape works everywhere
* [ ] Reading `response.success` (not `response.isSuccess`) on both platforms
* [ ] Handling the batch response shape — `data` keyed by index on Android, `error.failedPasses` (not `data.failedPasses`) on iOS
* [ ] On iOS, treating `cancelled`/`already_exists`/`unsupported` as successful resolutions (`data.status`), not errors

***

## Frequently Asked Questions

<details>

<summary>Can WebToNative generate or sign the pass for me?</summary>

No. Your backend builds and signs the pass (a Google Wallet JWT on Android, a `.pkpass` file on iOS) using Google's or Apple's own tools. WebToNative only hands the finished pass to the device's wallet app.

</details>

<details>

<summary>Why does `getName`'s callback never fire?</summary>

`getName` is Android-only — on iOS it's simply never called.

</details>

<details>

<summary>I sent one pass to `addPasses` — why does the failure response look different from a batch failure?</summary>

A single pass reports a top-level `error`/`status`. Two or more passes report a summary object describing every pass in the batch — `data` keyed by index on Android, but on iOS that summary (`total`/`succeededCount`/`failedCount`/`failedPasses`) is nested under **`error`**, alongside `success: false` — see [Callback Response Format](#callback-response-format).

</details>

<details>

<summary>Why does my iOS `addPasses` callback show `success: true` when the user cancelled?</summary>

For a single pass, `cancelled` and `already_exists` are expected outcomes, not errors — iOS resolves them through the success shape as `data.status`, the same way `added` is reported. Check `data.status`, not `success`, to distinguish these cases.

</details>

<details>

<summary>Does WebToNative validate the pass before sending it?</summary>

Only structurally, and only on Android — a malformed JWT/URL/claims payload is caught locally as `INVALID_PASS` instead of reaching the Wallet API. Neither platform verifies your pass's signature; an incorrectly signed pass will still fail, just with a native `SAVE_ERROR` / `INVALID_PASS_DATA` instead.

</details>
