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

# Enforcement Snippet

> Add one script tag to enforce billing gates across your web app.

## Installation

Add the snippet to every authenticated page — typically inside your app shell's `<body>`, after the user is confirmed to be logged in:

```html theme={null}
<script
  src="https://api.gatlio.io/v1/enforce.js"
  data-tenant-slug="acme"
  data-publishable-key="pk_live_abc123"
  data-customer-id="cus_xxxxxxxxxxxxxxxx"
  data-hmac="SERVER_GENERATED_HMAC"
></script>
```

On load, the snippet fetches the subscriber's billing status and renders the appropriate UI. Render this tag server-side — `data-hmac` must be generated on the server using your HMAC secret, never in client-side code.

## Attributes

| Attribute              | Required | Description                                                                                                                               |
| ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `data-tenant-slug`     | ✓        | Your Gatlio tenant slug, visible in the dashboard                                                                                         |
| `data-publishable-key` | ✓        | Your Gatlio publishable key (`pk_live_…` or `pk_test_…`)                                                                                  |
| `data-customer-id`     |          | Stripe customer ID (`cus_…`) for auto-init. If omitted, call `window.Gatlio.check(cid, hmac)` manually                                    |
| `data-hmac`            |          | `HMAC-SHA256(hmacSecret, stripe_customer_id)` — generated server-side. Strongly recommended in production to prevent customer ID spoofing |
| `data-api-base`        |          | Base URL of your Gatlio API. Defaults to the Gatlio-hosted API                                                                            |
| `data-gate-base`       |          | Origin for the gate iframe. Defaults to `data-api-base`. Must be a Gatlio-controlled origin                                               |
| `data-locale`          |          | UI locale — `en`, `fr`, `es`, `de`. Defaults to `navigator.language`, fallback `en`                                                       |

## Manual init (SPAs)

If your app renders the subscriber's customer ID after the initial page load (e.g. after an auth redirect), omit `data-customer-id` and call `window.Gatlio.check()` manually once the ID is available:

```javascript theme={null}
// After the user is authenticated and you have their Stripe customer ID and HMAC:
window.Gatlio.check('cus_xxxxxxxxxxxxxxxx', 'server_generated_hmac')
```

Call `Gatlio.check()` again any time the signed-in user changes (e.g. account switcher). Always pass the HMAC alongside the customer ID.

## Enforcement behaviour

### Active

No UI is shown. The subscriber's session continues unaffected.

### Warning (soft decline)

A dismissable amber banner appears fixed at the top of the viewport:

* Includes a localized message and an **Update card** CTA if `card_update_url` is configured in your dashboard
* The subscriber can dismiss the banner, but it reappears on the next page load
* Polling continues every 10 minutes so the banner disappears automatically once the issue resolves

### Lockout (hard decline or exhausted retries)

A full-screen iframe overlay covers the entire viewport:

* `z-index: 2147483647` — rendered above all app content
* `sandbox="allow-scripts allow-same-origin allow-popups allow-forms"` — the gate page cannot access parent DOM or storage
* Contains a Stripe Payment Element for immediate card update
* Dismisses automatically once the subscriber completes the card update flow

## Polling

After the initial check, the snippet polls the status API every **10 minutes** while the tab is visible. Polling pauses when the tab is hidden and resumes on visibility.

Once a subscriber enters lockout, polling stops and the gate page takes over. After recovery, the snippet goes idle for the remainder of the session.

## Double-load protection

The snippet is guarded against being loaded more than once per page. If the script tag is injected multiple times (e.g. via a tag manager), only the first load takes effect.
