# Pass installation and uninstallation hooks

It is possible to create a **custom connector** in **The Wallet Crew** to call an external API whenever a pass is **installed** or **uninstalled** on Apple Wallet or Google Wallet.

This allows Brands to **synchronize installation status** with external systems such as a CRM, analytics tool, or marketing automation platform.

<details>

<summary>Real-world examples</summary>

* A retail Brand updates a CRM field like `hasWalletPass=true` right after installation. It then stops sending “add to wallet” reminders.
* A ticketing Brand logs installs and uninstalls in its BI stack. It monitors adoption by event, channel, and device type.
* A Brand triggers a welcome journey after installation. It uses `registrationSource` to segment by UTM campaign.
* A Brand detects unusual uninstall spikes after a bad release. It rolls back quickly and communicates proactively.

</details>

### Runtime Hooks

The platform exposes the following hooks on the `runtime.wallet.passUpdater` endpoint:

* **`OnPassInstalled`** → Triggered when a pass is added to Apple/Google Wallet
* **`OnPassUninstalled`** → Triggered when a pass is removed from Apple/Google Wallet

Both methods receive the same parameters:

| Parameter               | Type                                    | Description                                                   |
| ----------------------- | --------------------------------------- | ------------------------------------------------------------- |
| `passId`                | `string`                                | Unique identifier of the pass                                 |
| `passType`              | `string`                                | Type of pass (loyalty, gift card, event ticket…)              |
| `identifiers`           | `Record<string, any>`                   | Key/value identifiers defined on the pass (e.g. `customerId`) |
| `device`                | `"apple"` or `"google"`                 | Wallet platform                                               |
| `additionalInformation` | `AdditionalPassInstallationInformation` | Includes registration statistics                              |

### Example Implementation

Scripts can be placed in the `server/scripts` folder under advanced configuration.

```js
const API_URL = 'https://partner';
import { getSecret } from 'neo/secrets';

/** 
 * @typedef {Object} RegistrationInformation 
 * @property {number} activeRegistrationCount - Active registrations. 
 * @property {number} totalRegistrationCount - Total registrations. 
 **/ 

/**
 * @typedef {Object} RegistrationSource
 * @property {string[]} tags - list of tags specified by the SDK integration
 * @property {string} medium - medium specified by the SDK integration or utm_medium
 * @property {string} origin  - url from where the pass is installed - could be overriden by the SDK integration
 * @property {string} userAgent - optional UserAgent used to install the pass
 */

/** 
 * @typedef {Object} AdditionalPassInstallationInformation
 * @property {RegistrationInformation} registrationInformation - Registration statistics. 
 * @property {RegistrationSource} registrationSource - Source of the installation when available 
 */

/** methods triggered when a pass is installed 
 * @param {string} passId - unique identifier of the pass
 * @param {string} passType - pass type of the current pass
 * @param {Record<string, any>} identifiers - key value pairs of identifiers of the pass
 * @param {"apple"|"google"} device - named of the device
 * @param {AdditionalPassInstallationInformation} additionalInformation - additionalInformation related to this pass installation */
 async function onPassInstalled(passId, passType, identifiers, device, additionalInformation) {
     await onPassInstallationStatusChanged(passId, passType, identifiers, device, true);
} 

/** methods triggered when a pass is uninstalled 
 * @param {string} passId - unique identifier of the pass 
 * @param {string} passType - pass type of the current pass
 * @param {Record<string, any>} identifiers - key value pairs of identifiers of the pass
 * @param {"apple"|"google"} device - named of the device 
 * @param {AdditionalPassInstallationInformation} additionalInformation - additionalInformation related to this pass installation 
 */ 
async function onPassUninstalled(passId, passType, identifiers, device, additionalInformation) { 
    await onPassInstallationStatusChanged(passId, passType, identifiers, device, false); 
} 

async function onPassInstallationStatusChanged(passId, passType, identifiers, device, isInstalled) {
  const customerId = identifiers["customerId"]; // or any other external identifier
  const API_SECRET = await getSecret('PARTNER-API-SECRET');

  // ⚠️ Note: fetch is not standard here. It uses PascalCase options and object-based Body.
  await fetch(API_URL + '/wallet/installation', {
    Method: "POST",
    Headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      "X-API-KEY": API_SECRET
    },
    Body: {
      customerId,
      isInstalled,
      device
    },
    ThrowOnError: false
  });
}

export default function (context) {
  context.register('runtime.wallet.passUpdater', {
    OnPassInstalled: onPassInstalled,
    OnPassUninstalled: onPassUninstalled
  });
}
```

### Minimal Example

For testing or logging only:

```js
async function onPassInstalled(passId, passType, identifiers, device) {
  console.log(`Pass ${passId} installed on ${device}`);
}

async function onPassUninstalled(passId, passType, identifiers, device) {
  console.log(`Pass ${passId} uninstalled from ${device}`);
}

export default function (context) {
  context.register('runtime.wallet.passUpdater', {
    OnPassInstalled: onPassInstalled,
    OnPassUninstalled: onPassUninstalled
  });
}
```

### When Are Events Triggered?

* **`OnPassInstalled`** → fired when a user successfully adds a pass to Apple or Google Wallet.
* **`OnPassUninstalled`** → fired when a user removes the pass from their wallet.
* Events are reliable: the platform ensures correct delivery even under high load.

### Notes

* `fetch` is **not the standard browser fetch** — it accepts PascalCase options (`Method`, `Headers`, `Body`, `ThrowOnError`).
* Authentication is flexible: you may use API keys (via `getSecret`) or OAuth with the custom `fetch`.
* There are no platform restrictions: execution is safe and fully managed.

### FAQ

<details>

<summary>Are these events real-time?</summary>

They trigger when the pass is added or removed in the wallet. In practice, you should treat them as near real-time. Always design your API to be idempotent, so retries do not create duplicates.

</details>

<details>

<summary>What happens if my API is down?</summary>

Your connector code controls the call. You should handle timeouts and errors gracefully. If the external system is critical, implement retries and a dead-letter strategy on your side.

</details>

<details>

<summary>Can I use this to trigger emails or push campaigns?</summary>

Yes. The most common pattern is to call your marketing automation platform (directly or via your backend) and trigger a journey on install. Use uninstall events to stop or adapt communications.

</details>

<details>

<summary>What is included in <code>additionalInformation</code>?</summary>

It can include registration statistics and, when available, the installation source. Use it to measure adoption and attribute installs to a campaign or SDK integration.

</details>


---

# Agent Instructions: Querying This Documentation

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

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.thewalletcrew.io/connect/custom-connector/installation-changed-extensibility.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
