> For the complete documentation index, see [llms.txt](https://docs.tiun.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tiun.io/guides/subscriptions/verify-server-side.md).

# Verify subscriptions server-side

Subscription verification on the backend is **identity verification plus a `productAccess` check**. If your server serves data that should only be returned to subscribers of a specific product, this guide is what you want.

It uses the same endpoint as [server-side authentication verification](/guides/authentication/verify-server-side.md). The flow is identical up to step 3 — the only difference is what you check on the verified user object.

<figure><img src="/files/CB6kj4XujWcZNwqxgWEL" alt="Verify subscriptions flow: frontend gets token, sends to backend, backend exchanges with tiun API for the user object"><figcaption></figcaption></figure>

***

## How it works

1. Your frontend asks tiun for a signed verification token.
2. It sends that token to your backend on protected requests.
3. Your backend exchanges the token with the [tiun UserVerification API](https://app.gitbook.com/s/ZMTdS5A9nvRqJOIo1RJM/userverification) for the user object.
4. If `isAuthenticated: true` **and** `userInfo.productAccess` contains the required product ID, you serve the protected data.

The verified user object includes `isAuthenticated` plus a `userInfo` payload (`userId`, `email`, `productAccess`) — see [User object](/reference/authentication/user-object.md) and [Product access](/reference/checkout/product-access.md) in Reference.

***

## Setup: API key

Create an API key in the dashboard under **APIs**, then store it in your backend environment variables. Same key as the one used for [server-side authentication verification](/guides/authentication/verify-server-side.md).

***

## 1. Get the verification token

Identical to the auth verification flow — call `tiun.getUserVerificationToken()` on the frontend and send the result to your backend as a Bearer token. See [getting the verification token](/guides/authentication/verify-server-side.md#1-get-the-verification-token) for the snippet.

***

## 2. Verify the token on your server

Same endpoint as the auth verification flow — POST the token to the [tiun UserVerification API](https://app.gitbook.com/s/ZMTdS5A9nvRqJOIo1RJM/userverification) and read the user object from the response.

**Endpoint:**

`POST /live_api/s2s/v1/users/verification`

**Base URLs:**

| Environment | URL                             |
| ----------- | ------------------------------- |
| Live        | `https://api.tiun.live`         |
| Sandbox     | `https://api-sandbox.tiun.live` |

Use the base URL and API key from the **same environment** as your frontend (`sandbox: true` in the SDK → sandbox URL and sandbox key). API keys are not shared between live and sandbox.

**Header:** `X-TIUN-API-KEY: <your-api-key>`

**Body:** `{ "userVerificationToken": "<token>" }`

A `200` response carries the user object:

```json
{
  "isAuthenticated": true,
  "userInfo": {
    "userId": "u-...",
    "email": "user@example.com",
    "productAccess": ["p-live-pro"]
  }
}
```

***

## 3. Check `productAccess`

Once you have the user object, gate the response on two things:

* `isAuthenticated` must be `true` — the user is signed in.
* `userInfo.productAccess` must include the **product ID** required by this endpoint.

Splitting these two checks lets you distinguish "not signed in" from "signed in but without the subscription" — useful UX feedback (prompt login vs. prompt upgrade).

***

## Full round-trip

**Frontend** — request the protected resource when the user is authenticated:

```javascript
tiun.on('userChange', async (data) => {
  if (!data.isAuthenticated) {
    showPublicContent();
    return;
  }

  const result = await fetchProContent();

  if (result) {
    showProContent(result);
  }
});

async function fetchProContent() {
  const token = await tiun.getUserVerificationToken();

  if (!token) return null;

  const res = await fetch('/api/pro-content', {
    headers: { Authorization: `Bearer ${token}` },
  });

  if (!res.ok) return null;
  return res.json();
}
```

**Backend** — exchange the token, check identity, then check product access:

```javascript
import express from 'express';

const app = express();

const BASE_URL = process.env.TIUN_API_BASE || 'https://api-sandbox.tiun.live';
const API_KEY = process.env.TIUN_API_KEY;
const REQUIRED_PRODUCT = 'p-live-pro';

app.get('/api/pro-content', async (req, res) => {
  const auth = req.headers.authorization;
  const token = auth?.startsWith('Bearer ') ? auth.slice(7) : null;

  if (!token) {
    // missing token → reject as unauthenticated
    return;
  }

  const tiunResponse = await fetch(
    `${BASE_URL}/live_api/s2s/v1/users/verification`,
    {
      method: 'POST',
      headers: {
        'X-TIUN-API-KEY': API_KEY,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ userVerificationToken: token }),
    },
  );

  if (tiunResponse.status !== 200) {
    // API key invalid or upstream error → fail closed
    return;
  }

  const user = await tiunResponse.json();

  if (!user.isAuthenticated) {
    // not signed in → reject
    return;
  }

  if (!user.userInfo?.productAccess?.includes(REQUIRED_PRODUCT)) {
    // signed in but no subscription → reject (prompt upgrade)
    return;
  }

  // user has access → serve the pro content
});
```

***

## Multiple plans

If you offer tiers (for example **Light** unlocks some endpoints, **Pro** unlocks all), check for the highest applicable tier first:

```javascript
const hasPro = user.userInfo?.productAccess?.includes('p-live-pro');
const hasLight = user.userInfo?.productAccess?.includes('p-live-light');

if (hasPro) {
  // serve full Pro content
} else if (hasLight) {
  // serve Light content
} else {
  // no qualifying subscription → reject
}
```

***

{% hint style="info" %}
Live and sandbox are independent environments — each has its own API base URL and API keys. Use sandbox credentials while your app runs with `sandbox: true`; switch URL and key together when you ship live traffic. See [Sandbox](/reference/generic/sandbox.md).
{% endhint %}


---

# 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 dynamically by asking a question.

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

```
GET https://docs.tiun.io/guides/subscriptions/verify-server-side.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.
