> For the complete documentation index, see [llms.txt](https://docs.thewalletcrew.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.thewalletcrew.io/guides-enrolment/enrolment/on-your-mobile-app.md).

# On your mobile app

Add an Add to Wallet button to a native iOS or Android app, with pass lookup and install payloads served by a backend.

A brand that already has a mobile app can let customers save their pass to Apple Wallet or Google Wallet without leaving the app. The app stays in charge of sign-in, account data and entitlements, while the wallet gives the pass offline access, surfaces it on the lock screen at the right time and keeps it updated. Customers can then present the pass at a store or event without reopening the app.

<figure><img src="https://831874859-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlFokgwgJiwLXu7G8MVSJ%2Fuploads%2F6tPWU0PPHxbK1uKEfsfe%2FMobileAppSDK.png?alt=media&amp;token=ad91ba3d-1a55-4976-a8ac-0b16b5b1f6f8" alt="A mobile app screen showing an Add to Wallet button next to a loyalty card, and the resulting pass in the wallet"><figcaption><p>The Add to Wallet button sits inside the brand's app; installation itself happens in the system wallet sheet.</p></figcaption></figure>

<details>

<summary><strong>Real-world examples</strong></summary>

* A retailer shows an Add to Apple Wallet button on the loyalty card screen of its app, so members can scan the card at the till even with no network.
* A ticketing app offers Add to Google Wallet on the order confirmation screen, right after checkout.
* A membership app suggests saving the card during onboarding, then keeps a smaller button on the profile screen.
* Each of these placements carries its own `neo.src` tag, so the brand can compare which screen drives the most installations.

</details>

### Why the app and the wallet work together

A wallet pass does not replace the native app; the two solve different jobs. The app handles accounts, authentication, settings and the full feature set. The wallet handles presentation and convenience: quick access, offline use, OS-level storage and timely surfacing on the lock screen based on time or location. The Wallet Crew sits behind both, creating, updating and revoking the passes.

Adding the button at the right touchpoints removes friction. Customers save the pass in one tap and present it later without opening the app, which matters when the queue is long and the network is weak. The pass also stays available in the wallet for as long as it is valid, even if the app is later uninstalled.

{% hint style="info" %}
An app cannot install a pass silently. The customer always confirms in the system wallet sheet.
{% endhint %}

### How the flow works

The flow is the same on both platforms. The customer signs in to the app and taps the button. The app asks the brand's backend for an install payload. The backend finds the customer's pass in The Wallet Crew, creating it if needed, and fetches the payload that matches the device: a `.pkpass` file for Apple Wallet or a JWT for Google Wallet. The app hands this payload to the native wallet, which shows a preview, and the customer confirms. The wallet then registers the installation with The Wallet Crew, which can notify the brand's backend through a webhook.

{% @mermaid/diagram content="sequenceDiagram; autonumber; actor Customer; participant App as Mobile app; participant Backend as Brand backend; participant TWC as The Wallet Crew; participant Wallet as Apple or Google Wallet; Customer->>App: Tap Add to Wallet; App->>Backend: Request install payload (customer + platform); Backend->>TWC: Look up pass by identifier (API key); TWC-->>Backend: passId; Backend->>TWC: Download pass (passId, device, neo.src); TWC-->>Backend: .pkpass file or Google JWT; Backend-->>App: Install payload; App->>Wallet: Open system save sheet; Wallet-->>Customer: Preview; Customer->>Wallet: Confirm; Wallet->>TWC: Register installation; TWC-->>Backend: Pass:Installed webhook (optional)" %}

{% hint style="warning" %}
Never ship an API key inside a mobile app. An app binary can be decompiled, and a leaked key gives access to every pass on the tenant. Keep the key and the pass lookup on the backend.
{% endhint %}

### Apple Wallet and Google Wallet differences

The two wallets receive different payloads, so the backend picks the payload from the platform the app reports.

{% tabs %}
{% tab title="Apple Wallet (iOS)" %}
Apple Wallet uses a signed pass file. The Wallet Crew returns a `.pkpass` file when the pass is downloaded with `device=apple`.

The iOS app loads the file into a `PKPass` object and presents it with PassKit's `PKAddPassesViewController`. iOS shows the standard preview sheet, and once the customer confirms, the pass is stored on the device. Use the official **Add to Apple Wallet** button (`PKAddPassButton`).
{% endtab %}

{% tab title="Google Wallet (Android)" %}
Google Wallet uses passes stored in the customer's Google account. The Wallet Crew returns a JWT when the pass is downloaded with `device=google`.

The Android app passes this JWT to the Google Wallet Android SDK save flow. Once the customer confirms, the pass is linked to their Google account and can sync across their devices. Use the official **Add to Google Wallet** button.
{% endtab %}
{% endtabs %}

### Implement Add to Wallet in a mobile app

Before starting, the tenant must be set up for Apple Wallet, Google Wallet or both, and a [pass template](/configure/advanced-configuration/wallet/template-configuration/how-to-create-a-template.md) must exist for the passes to issue. The brand also needs a backend that the app can call, because the API key and the pass lookup must stay server-side.

The backend turns a stable customer identifier into a `passId`, then downloads the pass for the right platform. The app only ever receives the platform-specific payload.

{% stepper %}
{% step %}

### Create an API key for the backend

The backend authenticates to The Wallet Crew with an API key created in the admin console. Give the key only the scopes this flow needs: `tenant.pass:read` to look up passes, plus `tenant.pass:write` if the backend also creates passes for new customers. Store the key in the backend's secret store and send it in the `X-API-KEY` header.
{% endstep %}

{% step %}

### Find the customer's pass

The backend looks up the pass using an identifier it already knows, such as a loyalty number, customer ID or ticket ID. This identifier must be the one stored on the pass at creation, under a key of the form `<namespace>.<name>`. The list endpoint filters on it as `identifiers.<namespace>.<name>`:

```bash
curl --globoff -X GET \
  'https://app.neostore.cloud/api/<tenantId>/passes?pageIndex=0&pageSize=10&filter[0].field=identifiers.<namespace>.<name>&filter[0].operator=equals&filter[0].value=<customerId>' \
  -H 'accept: application/json' \
  -H 'X-API-KEY: <api_key>'
```

The response is a JSON array of passes. The `id` of the matching pass is the `passId` used in the next step. Each pass also has a `providers` object listing the wallets it is already installed on, which the app can use to show "View in Wallet" instead of "Add to Wallet".

If the array is empty, the customer has no pass yet. The backend creates one with the same identifier:

```bash
curl -X POST \
  'https://app.neostore.cloud/api/<tenantId>/passes?passType=<passType>' \
  -H 'Content-Type: application/json' \
  -H 'X-API-KEY: <api_key>' \
  -d '{ "identifiers": { "<namespace>.<name>": "<customerId>" } }'
```

The response body is the new pass id, which can be used directly as `passId`. [Manage pass lifecycle with the API](/developers-guides/integration-guides/wallet/pass-lifecycle.md) covers the full creation options, and the [Pass API reference](/api-reference/pass-management/pass.md) details every field.
{% endstep %}

{% step %}

### Download the install payload

With the `passId`, the backend downloads the pass for the customer's platform. The `device` parameter selects the payload: `apple` returns the `.pkpass` file and `google` returns the JWT.

```
GET https://app.neostore.cloud/api/<tenantId>/passes/<passId>?device=apple&neo.src=<tracking>
```

The `neo.src` parameter attributes the installation to a placement. Its format is `tags|medium|origin`: comma-separated tags, then the channel, then the origin. For example, `neo.src=loyalty-card|app|profile` tags installations started from the profile screen of the app. Use a distinct value for each screen that shows the button.

{% hint style="info" %}
This endpoint needs no API key, because a `passId` is opaque and cannot be guessed. The backend can therefore return the `passId` to the app and let the app download the payload itself. The lookup by customer identifier must still stay on the backend.
{% endhint %}
{% endstep %}

{% step %}

### Open the native wallet sheet

The app hands the payload to the platform's wallet SDK. On iOS, it presents the `.pkpass` file with PassKit. On Android, it passes the JWT to the Google Wallet save flow. Both platforms document the integration:

{% embed url="<https://developer.apple.com/documentation/passkit/pkaddpassbutton/>" %}

{% embed url="<https://developers.google.com/wallet/retail/loyalty-cards/android>" %}
{% endstep %}

{% step %}

### Track installations

Once a customer confirms, The Wallet Crew records the installation together with its `neo.src` source. Installation counts by medium and source are available in Insights, which shows which screens of the app perform best. To react in real time, for example to hide the button or update the customer record, subscribe the backend to the `Pass:Installed` and `Pass:Uninstalled` events through a [webhook](https://docs.thewalletcrew.io/developers-guides/integration-guides/webhooks).
{% endstep %}
{% endstepper %}

### Validate the integration

Test the flow on a real iPhone and a real Android device with a test customer before release. Confirm that an existing customer gets their existing pass rather than a duplicate, that a new customer gets a pass created on first tap, and that the installation is reported in Insights under the expected `neo.src` source. Also confirm that tapping the button a second time opens the same pass instead of creating another one.

The most common errors come from the download step. A `400` means the `device` parameter is missing or invalid. A `404` means no pass has this `passId`, often because a test id was used against the wrong tenant or environment. A `409` with `device=apple` means the pass template lacks an image Apple Wallet requires, such as the icon, and must be completed in the template before the pass can be saved.

### FAQ

<details>

<summary><strong>Can the API key be stored in the mobile app?</strong></summary>

No. Treat API keys like passwords. Anything shipped in an app can be extracted, so every call that needs a key must go through the brand's backend.

</details>

<details>

<summary><strong>Can the app download the pass directly from The Wallet Crew?</strong></summary>

Yes, as long as it only uses the `passId` and no API key. The download endpoint is public because a `passId` cannot be guessed. Looking up a pass by customer identifier must stay on the backend, since customer identifiers are often predictable.

</details>

<details>

<summary><strong>What happens when the customer has no pass yet?</strong></summary>

The lookup returns an empty array. The backend then creates the pass with `POST /api/{tenantId}/passes?passType={passType}` and the same identifier, and uses the returned id to download the payload. A new customer therefore needs one more backend call than an existing one.

</details>

<details>

<summary><strong>Does <code>PATCH</code> create a pass when none exists?</strong></summary>

No. `PATCH /api/{tenantId}/passes` only updates a pass that matches the given identifiers and returns `404` when none matches. Use the lookup, then `POST` when the pass is missing.

</details>

<details>

<summary><strong>Should a mobile app use native wallet flows or the website SDK?</strong></summary>

Native wallet flows give the smoothest experience in an app, with no web redirect. The website SDK suits web pages and desktop journeys that need a QR code fallback; see [On your website](/guides-enrolment/enrolment/on-your-website.md).

</details>

<details>

<summary><strong>How is Add to Wallet integrated in a React Native app?</strong></summary>

The backend part does not change; only the last step, opening the wallet sheet, needs a native bridge. The open-source library [react-native-wallet-manager](https://github.com/dev-family/react-native-wallet-manager) wraps both wallets behind one JavaScript API. On iOS, it requires the Wallet capability in Xcode; on Android, it requires the `com.google.android.gms:play-services-pay` dependency.

Each platform uses a different method. On iOS, `addPassFromUrl(url)` takes the download URL of the pass with `device=apple` and opens the Apple Wallet sheet. On Android, `addPassToGoogleWallet(jwt)` takes the JWT returned with `device=google`. Avoid `addPassFromUrl` on Android, because it opens a browser instead of the Google Wallet sheet. `canAddPasses()` checks that the device supports a wallet before the button is shown.

```javascript
import { Platform } from 'react-native';
import WalletManager from 'react-native-wallet-manager';

// passId and googleJwt come from the brand's backend
if (Platform.OS === 'ios') {
  const src = encodeURIComponent('loyalty-card|app|profile');
  await WalletManager.addPassFromUrl(
    `https://app.neostore.cloud/api/${tenantId}/passes/${passId}?device=apple&neo.src=${src}`
  );
} else {
  await WalletManager.addPassToGoogleWallet(googleJwt);
}
```

This library is maintained by a third party, not by The Wallet Crew. Check its current version and compatibility with the app's React Native version before adopting it.

</details>

<details>

<summary><strong>How is Add to Wallet integrated in a Flutter app?</strong></summary>

The backend part does not change. In the app, Flutter calls the native PassKit and Google Wallet save flows, either through a maintained wallet plugin or through a small platform channel for each platform, following the same split as above: the `.pkpass` file on iOS and the JWT on Android.

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.thewalletcrew.io/guides-enrolment/enrolment/on-your-mobile-app.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
