# On your website

Use The Wallet Crew **cinto SDK** to place an **Add to Wallet** button on any website. The SDK detects the device and renders the right call to action automatically.

On iOS, it shows **Add to Apple Wallet**. On Android, it shows **Add to Google Wallet**. On desktop, it can redirect to a hosted pass page or support a QR-based fallback.

<details>

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

* A loyalty account page shows one button for the logged-in customer card.
* A gift card area shows one button per active gift card.
* A ticketing page shows one button per ticket, not one button per order.

</details>

## How website embed works

The integration is simple. A page loads the cinto SDK, then renders a button that resolves one pass.

That pass can be resolved in two ways:

* with a known `passId`
* with `externalIdentifiers` and an optional HMAC

### Each pass must be unique

This point is critical. A wallet pass is **not** a shared asset.

Each pass must represent one customer, one card, one ticket, or one entitlement instance. The button shown on a page must resolve the pass that belongs to the current context only.

For example:

* a loyalty card page should resolve the current customer card
* a ticket list should resolve one distinct pass per ticket
* a gift card list should resolve one distinct pass per gift card

{% hint style="warning" %}
Never reuse the same static identifier value for every customer. If the same `customerId`, ticket id, or pass lookup value is hardcoded for all visitors, the same pass can be returned to everyone.
{% endhint %}

When `externalIdentifiers` are used, the identifier value must be unique enough to resolve **exactly one pass**. If a lookup returns several passes, the identifier strategy is not specific enough for website distribution.

### What changes by device

The SDK renders the correct behavior automatically:

* **iOS:** downloads the Apple Wallet pass
* **Android:** opens the Google Wallet save flow
* **Desktop:** redirects to a hosted pass page

This default can be overridden when needed with `platform`.

#### Example rendering

**iOS**

**Android**

**Desktop**

The desktop fallback opens a hosted pass page such as:

{% hint style="info" %}
Desktop behavior can be customized to display a QR code instead of a standard button.
{% endhint %}

## Choose how the button resolves the pass

The main implementation choice is the pass resolution method.

Use `passId` when the backend already knows the exact pass. Use `externalIdentifiers` when the website has access to a stable business identifier and the pass must be resolved dynamically.

### Start with `tenantId` and `environment`

Two SDK values are especially important:

* `tenantId`
* `environment`

`tenantId` is required. It is the tenant name in The Wallet Crew system. In the examples on this page, `molia` is the `tenantId`.

`environment` is the public base URL used by the SDK.

Use these values as follows:

* **Production:** `https://<customDomain>` the generic `https://app.neostore.cloud` can also be used when no custom domain has been configured
* **Testing / QA:** `https://app-qa.neostore.cloud`

{% hint style="info" %}
If `environment` is omitted, the SDK uses `https://app.neostore.cloud`. This default is valid for production.
{% endhint %}

### Option 1 — Resolve with `passId`

This is the simplest option. It works best when the backend already knows the exact pass to display.

{% tabs %}
{% tab title="Vanilla JavaScript" %}

```html
<script type="text/javascript">
(function (n, e, o) {
var s=n.createElement("script");s.src="https://sdk.neostore.cloud/scripts/"+e+"/cinto@1";s.async=1;
s.onload=function(){neostore.cinto.initialize(e,o)};n.body.appendChild(s);
})(document, "molia", {});
</script>

<div data-neostore-addToWalletButton data-neostore-passId="KlnqcxVLA9pS4ol5"></div>
```

{% endtab %}

{% tab title="npm module" %}

```bash
npm install @neostore/cinto
```

```jsx
import { AddToWalletButton } from "@neostore/cinto";

const btn = new AddToWalletButton("molia", {
    language: "fr",
    passId: "KlnqcxVLA9pS4ol5",
});

btn.render(document.getElementById("btn"));
```

{% endtab %}
{% endtabs %}

### Option 2 — Resolve with `externalIdentifiers`

This option is useful when the page knows a stable business identifier, such as a loyalty id, a CRM id, or a ticket id, but does not know the `passId` yet.

The identifier must belong to the current customer or object. It must not be a shared constant.

When HMAC is used, the signature should be computed server-side with one of The Wallet Crew secrets.

For example, for a `customerId` of `SC103010` and a secret of `I1M8emrrJSns4Hnuibbm45eWfLQMosPGKSp1JzKsCrXeWmhjE8lZhxC2tfSRX5IJ`, the HMAC value is:

`8c5a9ebdd9b4ac8d2307cc34192f0faed441ef724c043162f0618784173d4d93`

Reference tool: [CyberChef](https://gchq.github.io/CyberChef/#recipe=HMAC\({'option':'UTF8','string':'I1M8emrrJSns4Hnuibbm45eWfLQMosPGKSp1JzKsCrXeWmhjE8lZhxC2tfSRX5IJ'},'SHA256'\)\&input=U0MxMDMwMTA)

{% hint style="warning" %}
The HMAC secret must stay server-side. It must never be exposed in browser code.
{% endhint %}

#### Data attributes

```html
<script type="text/javascript">
(function (n, e, o) {
var s=n.createElement("script");s.src="https://sdk.neostore.cloud/scripts/"+e+"/cinto@1";s.async=1;
s.onload=function(){neostore.cinto.initialize(e,o)};n.body.appendChild(s);
})(document, "molia", { language: "fr" });
</script>

<div data-neostore-addToWalletButton
     data-neostore-passType="user"
     data-neostore-externalIdentifiers-y2.customer_Id-value="SC103010"
     data-neostore-externalIdentifiers-y2.customer_Id-hmac="cbbcfc5xxxxx"
></div>
```

#### Component usage

```jsx
import { AddToWalletButton } from "@neostore/cinto";

const btn = new AddToWalletButton("molia", {
    language: "fr",
    passType: "user",
    externalIdentifiers: {
        "y2.customerId": {
            value: "SC103010",
            hmac: "cbbcfc5xxxxx",
        },
    },
});

btn.render(document.getElementById("btn"));
```

#### Identifier key casing in HTML

Browsers lowercase HTML attribute names. To preserve an uppercase character in an identifier key, prefix the character with `_` in the attribute name.

Example:

* `y2.customer_Id` becomes `y2.customerId`

This rule only affects the HTML attribute name. It does not change the signed value.

## Common behavior and options

### Platform detection

When `data-neostore-addToWalletButton` is used, the component selects the right platform automatically:

* `desktop`
* `apple`
* `google`

To force a platform, set `data-neostore-platform="desktop"` or use the `platform` option in the component.

### Language detection

The SDK uses the browser language by default. If the locale is not available, it falls back to English.

To force a language:

* data attributes: `data-neostore-language="fr"`
* component option: `language: "fr"`

Vanilla Javascript example

```html
<script type="text/javascript">
(function (n, e, o) {
var s=n.createElement("script");s.src="https://sdk.neostore.cloud/scripts/"+e+"/cinto@1";s.async=1;
s.onload=function(){neostore.cinto.initialize(e,o)};n.body.appendChild(s);
})(document, "molia", { language: "fr" });
</script>

<div data-neostore-addToWalletButton data-neostore-passId="KlnqcxVLA9pS4ol5"></div>
```

### Full options

Here is the list of full available options.

```typescript
export interface Options {
    /**
     * Public base URL of the environment.
     * Use "https://app-qa.neostore.cloud" for testing.
     * Use "https://app.neostore.cloud" for production.
     * A tenant custom domain can also be used.
     * @default: "https://app.neostore.cloud"
     */
    environment: string;
    /**
     * Tenant name in The Wallet Crew system.
     * This value is required.
     * Example: "molia"
     */
    tenantId: string;
    /**
     * Name of the pass layout to redirect the user when the user is on desktop
     * @default: undefined // default pass layout of the pass template will be used
     */
    passLayoutName: string;
    /**
     * ISO 639 language code to use to do display the button. When omitted, the language will be automatically detected based on the browser settings.
     * If the value doesn't match any available option, the browser settings will be used otherwise english will be used.
     * @default: undefined
     */
    language?: string;
    /**
     * Identifier of the pass to display or promise of it
     */
    passId: string;
    /**
     * Platform to use to display the button. When omitted, the platform will be automatically detected based on the user agent.
     * Possible values are: "apple", "google" or "desktop"
     *
     * @default: undefined
     **/
    platform?: Platform;
    /**
     * External identifiers to use to aquire the passId
     */
    externalIdentifiers?: Record<string, { value: string; hmac?: string }>;
    /**
     * Pass type to use to aquire the passId
     * Required when externalIdentifiers is set
     */
    passType?: string;
    
    /**
     * Source used for analytics purpose
     */
    source?: {
        /**
         * list of tags to associate to this download. utm_source and utm_campaign will automatically be aggregated to this list
         */
        tags?: Array<string>;
        /**
         * medium to associate to this download. utm_medium will be used if no value is specified
         */
        medium?: string;
        /**
         * origin to associate to this download. the current url (without query) will be used if no value is specified
         */
        origin?: string;
    };

    /**
     * Callback called when the button is clicked
     */
    onClick?: (e: MouseEvent, data: { options: Partial<Options>; platform: Platform }) => void;

    /**
     * Callback when an error occurs
     *
     */
    onError?: (error: string) => void;
}

```

### Styling

The rendered structure is:

* a container provided by the website
  * a link with selector `.neostore-link`
    * an image with selectors `.neostore-img` and `.neostore-link-{{ platform }}`

On desktop, the button can be fully customized. The key output is the hosted pass URL, so the default button can be replaced with a custom CTA or a QR code flow.

On mobile, the button should follow Apple and Google design guidelines. The branded asset, label, spacing, and overall presentation should stay compliant with each provider requirements.

Apple and Google button assets follow each provider guideline:

* [Apple guideline](https://developer.apple.com/wallet/add-to-apple-wallet-guidelines/)
* [Google guideline](https://developers.google.com/wallet/generic/resources/brand-guidelines)

## Desktop customization

On desktop, the most important output is the hosted pass URL. That URL can be used to render a QR code instead of the default redirect button.

```html
<script src="https://cdn.rawgit.com/davidshimjs/qrcodejs/gh-pages/qrcode.min.js"></script>

<div id="qrcode"></div>
<script type="module">
    import { AddToWalletButton } from "https://sdk.neostore.cloud/scripts/molia/cinto@1/cinto.mjs";

    const button = new AddToWalletButton("molia", {
        passId: "KlnqcxVLA9pS4ol5"
    });

    const url = await button.getPassPageUrl();
    new QRCode(document.getElementById("qrcode"), url);
</script>
```

## Add analytics information

The button can send three source values that will be visible in the `Pass:Installed` event, the dashboard, and the Insights API.

* `tags`: list of source tags. `utm_source` and `utm_campaign` are added automatically.
* `medium`: channel label such as `ecomm` or `account`.
* `origin`: source page URL. By default, the current page URL is used without query parameters.

All values are optional.

```html
<div data-neostore-addToWalletButton
     data-neostore-src-tags="tag1,tag2"
     data-neostore-src-medium="ecomm"
     data-neostore-src-origin="originA"
></div>
```

## Retrieve `passId` when the pass already exists

When the pass already exists, the website backend can look it up first, then render the button with the returned `passId`.

{% stepper %}
{% step %}

### Create an API key

Create an API key in the admin console with `tenant.pass:read`.

See: [API Key](https://github.com/TheWalletCrew/docs/blob/main/guides/configure/platform/api-key.md)
{% endstep %}

{% step %}

### Query the passes endpoint

Use the configured identifier key to search the pass.

Example:

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

Expected result:

```json
[
  {
    "id": "KlnqcxVLAxxxxxx",
    "passType": "user",
    "identifiers": {
      "y2.customerId": "04101234"
    }
  }
]
```

{% endstep %}

{% step %}

### Validate uniqueness

The lookup should return one pass only.

If zero passes are returned, the pass does not exist yet. If several passes are returned, the identifier is not unique enough for website distribution.
{% endstep %}

{% step %}

### Render the button with `passId`

Once the `id` is known, use it as the `passId` in the website button.
{% endstep %}
{% endstepper %}

## Third-party integration example

<details>

<summary><strong>React wrapper example</strong></summary>

```tsx
import { AddToWalletButton } from "@neostore/cinto";

const CintoMobileAddToWallet = React.forwardRef<
    HTMLButtonElement,
    BoxProps & {
        passId?: string;
    }
>(({ passId, ...props }, buttonRef) => {
    const localRef = useRef<HTMLButtonElement>(null);
    buttonRef = buttonRef || localRef;

    const ctaRef = useRef<HTMLDivElement>(null);
    const cintoButtonRef = useRef<AddToWalletButton>();

    useEffect(() => {
        cintoButtonRef.current = passId
            ? new AddToWalletButton(tenantId, {
                  passId,
              })
            : undefined;
        ctaRef.current && cintoButtonRef.current?.render(ctaRef.current);
        if (passId && cintoButtonRef.current) {
            cintoButtonRef.current?.perform();
        }
    }, [passId, tenantId]);

    return (
        <Box {...props}>
            <div ref={ctaRef} />
        </Box>
    );
});

export default CintoMobileAddToWallet;
```

</details>

## FAQ

<details>

<summary><strong>Should the same button be reused for all customers?</strong></summary>

No. The visual component can be reused, but the resolved pass must change with the current customer, ticket, or gift card context.

</details>

<details>

<summary><strong>Should `passId` or `externalIdentifiers` be used?</strong></summary>

Use `passId` when the backend already knows the exact pass. Use `externalIdentifiers` when the page has a stable identifier and the pass must be resolved dynamically.

</details>

<details>

<summary><strong>Can several buttons be rendered on the same page?</strong></summary>

Yes. This is common for ticket lists and gift card lists. Load the SDK once, then render one button per pass.

</details>

<details>

<summary><strong>Can desktop show a QR code instead of a standard redirect?</strong></summary>

Yes. The hosted pass URL can be used to render a QR code or another desktop-specific CTA.

</details>

<details>

<summary><strong>When should a native app integration be used instead?</strong></summary>

Use a native integration when the wallet flow starts inside an iOS or Android app. For that pattern, see [On your mobile app](/guides-enrolment/enrolment/on-your-mobile-app.md).

</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/guides-enrolment/enrolment/on-your-website.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.
