Articles in this section
Category / Section

Custom Connector to call API when pass installation status change

Published:
Updated:

Custom Connector for Pass Installation Status

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

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


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.

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 neostore 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 neostore 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:

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.

Use Cases

  • CRM Synchronization → Update customer records with installation status.
  • Marketing Automation → Trigger campaigns when a pass is added or removed.
  • Analytics Tracking → Send events to BI tools to measure adoption.

Partners are free to adapt the payload to their own API contract.


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.

👉 With this approach, partners can easily integrate real-time pass installation insights into any external system.

Access denied
Access denied