> 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/developers-guides/integration-guides/wallet/pass-lifecycle.md).

# Manage pass lifecycle with the API

Create, update, notify, deactivate, and track Apple Wallet and Google Wallet passes with The Wallet Crew API.

## Manage pass lifecycle with the API

The Wallet Crew API manages a pass from creation to wallet removal. It supports pass updates, wallet notifications, and lifecycle events for Apple Wallet and Google Wallet.

Use this guide to choose the right lifecycle action. Use the [API reference](https://docs.thewalletcrew.io/api-reference/) for schemas, parameters, and responses.

<details>

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

* A retailer creates a loyalty pass using a CRM customer identifier.
* An airline updates gate information and sends a wallet refresh.
* An event platform records wallet removal through `Pass:Uninstalled`.

</details>

### Before managing a pass

Passes use identifiers to connect each wallet pass to external records. These identifiers let connectors fetch the latest source data during rendering.

Review [Pass data and sync](/developers-guides/pass-architecture/pass-data-and-sync.md) before designing an integration. Use [Structure](/developers-guides/pass-architecture/structure.md) to choose stable identifier keys.

### Pass state model

The Wallet Crew has no universal pass state field. Pass data defines the state. [Liquid templates](/developers-guides/integration-guides/wallet/templating-engine.md) map that data to native wallet fields.

Apple Wallet supports these state-related fields:

* `voided` — a boolean that marks the pass invalid in the wallet. Set it to `false` to reverse the change.
* `expirationDate` — an ISO 8601 date that defines when the pass expires.

Google Wallet supports these state-related fields:

* `state` — one of `ACTIVE`, `COMPLETED`, `EXPIRED`, or `INACTIVE`.
* `validTimeInterval` — the time window during which the pass remains valid.

Map the relevant fields in the Apple Wallet and Google Wallet templates. The displayed state changes only after a pass renders again.

{% hint style="info" %}
The Wallet Crew has no soft-delete or archive state. The API does not delete pass records. To deactivate a pass, update the relevant platform state field.
{% endhint %}

### Creating a pass

Create a pass through the operation below. Supply at least one external identifier from the source system.

The Wallet Crew validates identifiers through one of these authorization methods:

* HMAC signature
* Registered secret
* Security allowlist
* `Pass.Write` scope

A pass requires data at creation. The Wallet Crew fetches connector data lazily when the pass first renders. Include data that allows a connector to locate the external record.

## Issue a pass

> Without \`tenant.pass:write\`, each identifier must be proven (see Neo.Web.Api.Controllers.PassController.PassData.Identifiers); unproven ones are dropped and the request is refused when none is left. The identifiers of an authenticated holder are added. Nothing is sent to a device until the holder downloads the pass.\
> \`\`\`\
> POST /api/{tenantId}/passes?passType=loyalty\
> { "identifiers": { "shopify.customerId": "12345", "shopify.customerId.hmac": "{hmac}" }, "additionalData": { "loyaltyTier": "silver" } }\
> \`\`\`

````json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Pass"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:write"]},{"apiKey":[]},{}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"CreatePassData":{"type":"object","allOf":[{"description":"Fields a caller can set on a pass, at creation or on update.","$ref":"#/components/schemas/PassData"}],"properties":{"extensions":{"type":["null","object"],"additionalProperties":{},"description":"Initial state of the pass's extensions, keyed by name (`apple`, `google`)."}},"additionalProperties":false,"description":"Content of a new pass."},"PassData":{"type":"object","properties":{"identifiers":{"type":"object","additionalProperties":{"type":"string"},"description":"Identifiers of the holder in other systems, keyed by system and field (`shopify.customerId`), matched by `findPass`, `id.{key}` lookups and filters. Avoid keys starting with `id.`."},"additionalData":{"type":["null","object"],"additionalProperties":{},"description":"Free data stored with the pass and available to its templates."},"themeId":{"type":["null","string"],"description":"Theme applied to the pass. Omit or `null` to leave it unchanged (none on create); an empty string removes it. An unknown id is refused with a 400."}},"additionalProperties":false,"description":"Fields a caller can set on a pass, at creation or on update."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes":{"post":{"tags":["Pass"],"summary":"Issue a pass","description":"Without `tenant.pass:write`, each identifier must be proven (see Neo.Web.Api.Controllers.PassController.PassData.Identifiers); unproven ones are dropped and the request is refused when none is left. The identifiers of an authenticated holder are added. Nothing is sent to a device until the holder downloads the pass.\n```\nPOST /api/{tenantId}/passes?passType=loyalty\n{ \"identifiers\": { \"shopify.customerId\": \"12345\", \"shopify.customerId.hmac\": \"{hmac}\" }, \"additionalData\": { \"loyaltyTier\": \"silver\" } }\n```","parameters":[{"name":"passType","in":"query","description":"Pass template to issue the pass from; matched exactly.","schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Identifiers, data and theme of the pass.","content":{"application/json":{"schema":{"description":"Content of a new pass.","$ref":"#/components/schemas/CreatePassData"}},"text/json":{"schema":{"description":"Content of a new pass.","$ref":"#/components/schemas/CreatePassData"}},"application/*+json":{"schema":{"description":"Content of a new pass.","$ref":"#/components/schemas/CreatePassData"}}}},"responses":{"200":{"description":"The pass was created; the body is its id.","content":{"text/plain":{"schema":{"type":"string"}},"application/json":{"schema":{"type":"string"}},"text/json":{"schema":{"type":"string"}}}},"400":{"description":"`passType` is missing, or it or `themeId` names a template or theme the tenant does not have.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"401":{"description":"The caller is not authenticated and no identifier could be verified.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"Authenticated without `tenant.pass:write` and no identifier verified, or the action is not enabled for the tenant.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
````

{% hint style="info" %}
Pass creation does not fetch connector data immediately. The first installation or render triggers that fetch.
{% endhint %}

### Updating pass data

Use the operation below to update pass data and request a wallet refresh. Submitted fields merge with the existing pass data. Omitted fields remain unchanged. An empty value or `null` removes the submitted field.

## Update a pass by identifiers

> Same changes, delivery and responses as \`PATCH /passes/{passId}\`, addressing the pass by \`id\` or by external identifiers.\
> \`\`\`\
> PATCH /api/{tenantId}/passes?id.shopify.customerId=12345\
> { "additionalData": { "loyaltyTier": "gold" } }\
> \`\`\`

````json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Pass"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:write"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"SingleUpdatePassData":{"type":"object","allOf":[{"description":"Changes to apply to an existing pass, with options.","$ref":"#/components/schemas/UpdatePassData_SingleUpdatePassDataOptions"}],"properties":{"bypassQueue":{"type":"boolean","description":"Replaced by `options.bypassQueue`.","default":false,"deprecated":true}},"additionalProperties":false,"description":"Changes to one pass. Omitted fields are left as they are."},"UpdatePassData_SingleUpdatePassDataOptions":{"type":"object","properties":{"identifiers":{"type":"object","additionalProperties":{"type":"string"},"description":"Identifiers of the holder in other systems, keyed by system and field (`shopify.customerId`), matched by `findPass`, `id.{key}` lookups and filters. Avoid keys starting with `id.`."},"additionalData":{"type":["null","object"],"additionalProperties":{},"description":"Free data stored with the pass and available to its templates."},"themeId":{"type":["null","string"],"description":"Theme applied to the pass. Omit or `null` to leave it unchanged (none on create); an empty string removes it. An unknown id is refused with a 400."},"passType":{"type":["null","string"],"description":"Pass template to move the pass to. Omitted or blank keeps the current one; an unknown name is refused with a 400."},"updateMetadata":{"type":"boolean","description":"Replaced by `options.updateMetadata`.","default":false,"deprecated":true},"options":{"description":"How the update is applied. Omitted, the defaults of each option apply.","$ref":"#/components/schemas/SingleUpdatePassDataOptions"}},"additionalProperties":false,"description":"Changes to apply to an existing pass, with options."},"SingleUpdatePassDataOptions":{"type":"object","allOf":[{"description":"How an update is applied.","$ref":"#/components/schemas/UpdatePassDataOptions"}],"properties":{"bypassQueue":{"type":"boolean","description":"Applies and pushes the update within the request (200) instead of queuing it (202).","default":false}},"additionalProperties":false,"description":"Options for a single pass update."},"UpdatePassDataOptions":{"type":"object","properties":{"updateMetadata":{"type":"boolean","description":"Recomputes the stored pass metadata read by filters and sorts. Slower; unneeded when only displayed content changes. Default `true`, except on a bulk update sent without a body."},"correlationId":{"type":["null","string"],"description":"Groups updates into one operation tracked by `GET /passes/pushUpdate/status` and stopped by `POST /passes/pushUpdate/{correlationId}/cancel`. Random when omitted; a synchronous update then records no progress."}},"additionalProperties":false,"description":"How an update is applied."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes":{"patch":{"tags":["Pass"],"summary":"Update a pass by identifiers","description":"Same changes, delivery and responses as `PATCH /passes/{passId}`, addressing the pass by `id` or by external identifiers.\n```\nPATCH /api/{tenantId}/passes?id.shopify.customerId=12345\n{ \"additionalData\": { \"loyaltyTier\": \"gold\" } }\n```","parameters":[{"name":"identifiers","in":"query","description":"Either `id={passId}` alone, or external identifiers prefixed with `id.` (`id.y2.customerId=1233332`), all of which a pass must carry. When several passes match, one is updated.","schema":{"type":"object","additionalProperties":{"type":"string"}}},{"name":"passType","in":"query","description":"Query: only a pass of this pass template matches. Use `passType` in the body to move the pass to another template.","schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"The changes and how to apply them.","content":{"application/json":{"schema":{"description":"Changes to one pass. Omitted fields are left as they are.","$ref":"#/components/schemas/SingleUpdatePassData"}},"text/json":{"schema":{"description":"Changes to one pass. Omitted fields are left as they are.","$ref":"#/components/schemas/SingleUpdatePassData"}},"application/*+json":{"schema":{"description":"Changes to one pass. Omitted fields are left as they are.","$ref":"#/components/schemas/SingleUpdatePassData"}}}},"responses":{"200":{"description":"Applied and pushed within the request (`options.bypassQueue` true)."},"202":{"description":"Queued."},"400":{"description":"The body's `passType` or `themeId` names a template or theme the tenant does not have, or `id` is combined with other identifiers.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:write</c> scope, or the endpoint is not enabled for the tenant.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"No pass matches the identifiers, or no `id` or `id.` parameter is given.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"409":{"description":"Synchronous update only: the pass was changed concurrently; retry the request.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 50000 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
````

#### Push notification behavior

**Backend and device updates**

Pass updates operate at two independent levels:

1. **Backend update** — The Wallet Crew updates the pass record.
2. **Device notification** — The Wallet Crew notifies wallet platforms when active registrations exist.

The backend update completes regardless of installation status. Device delivery depends on whether the pass remains installed.

**Device notification**

After the backend updates, The Wallet Crew attempts to notify Apple Wallet and Google Wallet. The notification depends on active registrations:

* An installed pass can receive an update through its wallet platform.
* An uninstalled pass has no active registrations, so no device notification is sent.
* A later installation receives the latest pass version from The Wallet Crew.

**Queue and deduplication**

The Wallet Crew queues updates by default with `bypassQueue: false`. The queue protects system throughput and deduplicates work. If another update reaches the same pass before the queued task runs, the queue skips the earlier task.

Set `bypassQueue: true` for urgent synchronous updates. Use it for changes such as boarding status updates that must reach the wallet immediately.

**Re-rendering on device**

{% hint style="info" %}
A notification does not update a property directly. When a device receives a notification, its wallet requests the latest pass version. The Wallet Crew fetches fresh connector data before returning that version.
{% endhint %}

{% hint style="info" %}
Backend updates persist. This supports safe updates while a pass is uninstalled.
{% endhint %}

For template mappings and pass fields, see [Update pass data in templates](/developers-guides/integration-guides/wallet/update-pass-data-in-templates.md).

### Sending a standalone notification

Send a text notification without changing pass data with the operation below. The notification appears in the wallet application independently of a data update.

Use `content` for a plain string. Use `localizedContent` for an object keyed by ISO 639 language codes. Use one content format per request.

## Notify a pass holder by pass id

> Queued by default: 202 means accepted, not yet sent. \`options.bypassQueue\` sends synchronously (200). The message appears on the pass in the wallet: Apple devices also refresh the whole pass; Google passes get the message added.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Pass"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:write"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"NotifyRequest":{"type":"object","properties":{"content":{"type":["null","string"],"description":"Message for devices whose language has no entry in `localizedContent`. Optional when `localizedContent` has text; defaults to the tenant's default language entry, else the first language in ordinal order."},"localizedContent":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Message per device language. Keys are a lowercase ISO 639-1 code, optionally with an uppercase\nregion (`en`, `fr-FR`). Entries with blank text are not sent.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}},"options":{"description":"Delivery options. Omitted, the notification is queued without refreshing pass data.","$ref":"#/components/schemas/NotifyRequestOptions"}},"additionalProperties":false,"description":"The message to show the holder, in one or several languages."},"NotifyRequestOptions":{"type":"object","properties":{"updateData":{"type":"boolean","description":"Also refreshes the Google Wallet pass content with the message. Apple always refreshes the whole pass.","default":false},"correlationId":{"maxLength":100,"minLength":10,"type":["null","string"],"description":"Caller-chosen id for tracing, such as a campaign or batch. 10 to 100 characters after trimming, otherwise 400."},"bypassQueue":{"type":"boolean","description":"Sends within the request (200) instead of queuing (202), for urgent low-volume messages.","default":false}},"additionalProperties":false,"description":"How the notification is delivered."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/{passId}/notification":{"put":{"tags":["Pass"],"summary":"Notify a pass holder by pass id","description":"Queued by default: 202 means accepted, not yet sent. `options.bypassQueue` sends synchronously (200). The message appears on the pass in the wallet: Apple devices also refresh the whole pass; Google passes get the message added.","parameters":[{"name":"passId","in":"path","description":"Pass id assigned by the platform.","required":true,"schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"The message and delivery options.","content":{"application/json":{"schema":{"description":"The message to show the holder, in one or several languages.","$ref":"#/components/schemas/NotifyRequest"}},"text/json":{"schema":{"description":"The message to show the holder, in one or several languages.","$ref":"#/components/schemas/NotifyRequest"}},"application/*+json":{"schema":{"description":"The message to show the holder, in one or several languages.","$ref":"#/components/schemas/NotifyRequest"}}}},"responses":{"200":{"description":"Notification sent synchronously (`options.bypassQueue` true)."},"202":{"description":"Notification queued for delivery."},"400":{"description":"Neither `content` nor `localizedContent` has text, or `correlationId` is outside 10 to 100 characters.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:write</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"No pass has this id.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 50000 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

### Wallet removal

When an end-user removes a pass, the wallet platform notifies The Wallet Crew. Apple sends an HTTP `DELETE` request to the Apple registrations route. Google sends a message with `eventType = "del"`.

The Wallet Crew sends `Pass:Uninstalled` when the last active registration reaches zero. Removing a pass from one of two iPhones does not send the event.

The webhook payload includes:

* `passId`, `passType`, `identifiers`, and `metadata`
* `device` and `deviceIdentifier` — Google sets `deviceIdentifier` to `null`
* `registrationInformation.activeRegistrationCount` and `registrationInformation.totalRegistrationCount`

The Wallet Crew delivers the event through the configured webhook endpoint. The request includes `X-NEOSTORE-EVENTNAME: Pass:Uninstalled`.

{% hint style="info" %}
Removing a pass from a wallet does not delete its The Wallet Crew record. The pass can be installed again later.
{% endhint %}

Configure endpoint delivery and validate signatures in [Webhooks](/developers-guides/integration-guides/webhooks.md).

### FAQ

<details>

<summary><strong>Does updating pass data immediately change an installed pass?</strong></summary>

No. The update creates a new pass version and triggers a wallet refresh. Apple Wallet or Google Wallet then requests that version.

</details>

<details>

<summary><strong>Can a pass be deleted through the API?</strong></summary>

No. The API does not delete pass records. Set the platform-specific state fields to deactivate a pass.

</details>

<details>

<summary><strong>Why was a wallet update not delivered?</strong></summary>

Push delivery requires an active provider registration. Confirm that the pass was installed on the relevant wallet platform.

</details>

<details>

<summary><strong>Can I use failed updates to detect uninstalled passes?</strong></summary>

No. Updates do not fail when a pass is uninstalled. The Wallet Crew processes the backend update successfully. No device push is sent when the pass has no active registrations.

Check installation status with the [pass details endpoint](https://docs.thewalletcrew.io/api-reference/). Alternatively, listen for [Pass:Installed and Pass:Uninstalled events](#wallet-removal).

</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/developers-guides/integration-guides/wallet/pass-lifecycle.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.
