> ## Documentation Index
> Fetch the complete documentation index at: https://docs.masivo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Add a loyalty card to a wallet

> Issue, read, and revoke a customer's loyalty card for Apple Wallet and Google Wallet, and use it at the POS.

A loyalty card shows the customer's points balance, tier, the other rewards
they hold, their `customer_id` as member ID, and a QR or Code 128 that a POS
or kiosk resolves with Masivo. Each
customer has at most one card. The customer opens `claim_url` and adds it to Apple Wallet or
Google Wallet with no app install and no login.

Dashboard setup (credentials and the loyalty template) is in
[Wallet pass credentials](https://docs.masivo.ai/guides/settings/wallet-passes.md) and
[Loyalty cards](https://docs.masivo.ai/guides/wallet-passes.md#loyalty-cards).

## Prerequisites

* SERVER API key and `x-account-id`
* An active loyalty template with a main reward
* Apple and/or Google credentials saved for the platforms you will use
* A registered customer

All examples use:

```
Authorization: Bearer <SERVER_API_KEY>
x-account-id: <ACCOUNT_ID>
Content-Type: application/json
```

## 1. Issue the card

[`POST /customers/{id}/loyalty-wallet-passes`](https://docs.masivo.ai/api-reference/customers/\[id]/loyalty-wallet-passes/post)

```bash theme={null}
curl -X POST "https://app.masivo.ai/api/storefront/v1/customers/user_abc123/loyalty-wallet-passes" \
  -H "Authorization: Bearer <SERVER_API_KEY>" \
  -H "x-account-id: <ACCOUNT_ID>" \
  -H "Content-Type: application/json" \
  -d '{}'
```

| Body field | Required | Notes |
| - | - | - |
| `template_id` | No | Loyalty template UUID. Defaults to the first active loyalty template of the account. |

Example response:

```json theme={null}
{
  "data": {
    "pass_id": "5b0f1c62-2e7a-4a8f-9a3e-1d2c4b6e8f10",
    "customer_id": "user_abc123",
    "template_id": "9c1e2d3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
    "brand_id": null,
    "status": "ISSUED",
    "points_balance": 1250,
    "tier_id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
    "claim_url": "https://app.masivo.ai/w/Q7mXk2pLr9sT4vWy8zA1bC3dE5fG6hJ0",
    "apple_url": "https://app.masivo.ai/api/wallet/apple/5b0f1c62-2e7a-4a8f-9a3e-1d2c4b6e8f10?t=Q7mXk2pLr9sT4vWy8zA1bC3dE5fG6hJ0",
    "google_save_url": "https://app.masivo.ai/api/wallet/google/5b0f1c62-2e7a-4a8f-9a3e-1d2c4b6e8f10?t=Q7mXk2pLr9sT4vWy8zA1bC3dE5fG6hJ0",
    "created_at": "2026-11-07T15:04:05.000Z",
    "updated_at": "2026-11-07T15:04:05.000Z"
  }
}
```

The call is idempotent. While the card is `ISSUED`, repeat calls return the
same `claim_url`. After a revoke, the next call creates a new card with a new
link.

Send `claim_url` only to that customer (email, SMS, WhatsApp, or an in-app
button). It opens that customer's card. `claim_url` detects the OS and opens
Apple Wallet on iPhone and Google Wallet on Android. Desktop sees both
buttons.

| Error | Cause |
| - | - |
| `404 Loyalty wallet template not found` | No active loyalty template, or `template_id` is not a loyalty template |
| `404 Customer not found` | The `customer_id` does not exist |

## 2. Read the card

[`GET /customers/{id}/loyalty-wallet-passes`](https://docs.masivo.ai/api-reference/customers/\[id]/loyalty-wallet-passes/get)

```bash theme={null}
curl "https://app.masivo.ai/api/storefront/v1/customers/user_abc123/loyalty-wallet-passes" \
  -H "Authorization: Bearer <SERVER_API_KEY>" \
  -H "x-account-id: <ACCOUNT_ID>"
```

Returns the same shape as the POST, or `404` when the customer has no card.
It never creates one. Use it to decide whether to show an
**Add to Wallet** button.

`points_balance` and `tier_id` are the values last sent to the phone. For the
live balance, read the customer.

## 3. Use the card at the POS or kiosk

The card QR holds a scan token (`msv_lp_...`), not the `customer_id`. It is
random, unique per card, and stops working when the card is revoked. Resolve
it with Masivo from the POS or kiosk backend, with its SERVER API key.

### Identify the customer (earn points)

[`POST /loyalty-wallet-passes/scan`](https://docs.masivo.ai/api-reference/loyalty-wallet-passes/scan/post)

```bash theme={null}
curl -X POST "https://app.masivo.ai/api/storefront/v1/loyalty-wallet-passes/scan" \
  -H "Authorization: Bearer <SERVER_API_KEY>" \
  -H "x-account-id: <ACCOUNT_ID>" \
  -H "Content-Type: application/json" \
  -d '{"pass_token": "msv_lp_Q7mXk2pLr9sT4vWy8zA1bC3d"}'
```

Returns the same shape as the issue call, with the `customer_id`. No code is
sent. Use it for actions that only add value, like a purchase event. See
[Send an event](https://docs.masivo.ai/api-reference/guides/send-an-event.md).

### Confirm the owner (redeem, sign in)

Anyone can photograph a card, so confirm the owner before anything that
spends balance or shows account data.

**1. Request the code**

Call [`POST /auth/totp`](https://docs.masivo.ai/api-reference/auth/totp/post) with the scanned
value as `pass_token`:

```json theme={null}
{ "pass_token": "msv_lp_Q7mXk2pLr9sT4vWy8zA1bC3d" }
```

Masivo puts a one-minute code on the customer's wallet card and does
not return it:

```json theme={null}
{
  "data": {
    "expired_at": "2026-11-10T15:04:05.000Z",
    "seconds_left": 60,
    "delivery": { "channel": "wallet", "devices": 1, "sent": true }
  }
}
```

While it is valid, the card shows the code in its OTP field (where the
card shows "-" the rest of the time), and the QR itself carries the
code. When it expires, Masivo takes it off the card.

**2. Get the code**

The customer reads the code from the card, or shows the card again and
you scan it: a QR of digits only is the code.

**3. Verify it**

Call [`GET /auth/totp/verify?code=...`](https://docs.masivo.ai/api-reference/auth/totp/verify/get).
A match returns the customer and their wallet: redeem or start the
session.

| Case | Result |
| - | - |
| Scanned again within 20 seconds | Same code, the card is not updated again (`sent: false`) |
| Card not saved in any wallet | The code goes by push (`channel: "push"`) |
| Not in any wallet, no registered device | `422 Customer has no device to receive the code` |
| Not in any wallet, Firebase not configured | `422 Firebase not configured for push notifications` |
| Revoked or unknown card | `404 Loyalty wallet pass not found` |

Apple Wallet usually shows the code within seconds, and the lock screen
shows "Tu código es ...". Google Wallet shows it when the customer opens or
reopens the card. The push fallback goes to the merchant app through the
account Firebase project. It is a transactional message, so it does not need
marketing push consent. Its `data` includes `type: "MASIVO_TOTP"` and the
`code`, so the app can fill it in.

## 4. Live updates

Masivo refreshes the card after anything that changes the points balance,
another reward balance, or the customer tier:

* Events that accumulate or redeem points
* Event reversals and coupon reversals
* Coupon redeems
* Tier upgrades and scheduled tier downgrades

Apple Wallet and Google Wallet push the change to the phone. It usually
arrives in a few seconds. Points that expire on their own appear on the next
refresh.

Template edits do not reach cards already on a phone. The customer must
remove the card and add it again from `claim_url`.

## 5. Revoke

[`POST /loyalty-wallet-passes/{id}/revoke`](https://docs.masivo.ai/api-reference/loyalty-wallet-passes/\[id]/revoke/post)

```bash theme={null}
curl -X POST "https://app.masivo.ai/api/storefront/v1/loyalty-wallet-passes/5b0f1c62-2e7a-4a8f-9a3e-1d2c4b6e8f10/revoke" \
  -H "Authorization: Bearer <SERVER_API_KEY>" \
  -H "x-account-id: <ACCOUNT_ID>"
```

```json theme={null}
{
  "data": {
    "pass_id": "5b0f1c62-2e7a-4a8f-9a3e-1d2c4b6e8f10",
    "status": "REVOKED"
  }
}
```

The card shows as voided in Apple Wallet and inactive in Google Wallet, and
`claim_url` stops working. Masivo also revokes the card when the customer is
deleted or merged into another customer.

## Frequently asked questions

> [accordion] Can a customer have more than one loyalty card?
>
> No. One card per customer. A second POST returns the existing card.

> [accordion] Which rewards does the card show?
>
> The template main reward shows next to the tier. Every other reward
> with a balance in the customer wallet is listed by name, sorted A to Z.
> Apple Wallet shows the first one next to the customer ID and all of them
> on the back. Google Wallet shows up to 10 in the card details.

> [accordion] Google Wallet rejects the card
>
> Loyalty cards need a logo or icon on the template. In Demo mode the Gmail
> on the phone must also be a test account. See
> [Wallet pass credentials](https://docs.masivo.ai/guides/settings/wallet-passes.md).

> [accordion] The balance on the phone is behind
>
> Wallet updates usually take a few seconds. If the phone is offline, it
> updates when it reconnects.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.