# End User Access Token

The Gamopanda End User Display requires a signed **access token** to display a logged-in customer's streak and challenge progress. Your backend generates this token and passes it to the Javascript script during initialization — it is never fetched client-side.

The token is a **JWT** signed with your Gamopanda API secret and scoped to a single end user.

---

## How it works

1. A customer loads your page and is identified by your platform (e.g. session, cookie, OAuth token).
2. Your backend looks up the customer's identity — typically their platform-specific user ID.
3. Your backend signs a JWT using your Gamopanda **API key** and **API secret**, embedding the customer's `externalId`.
4. Your backend returns the token to the frontend (in the page HTML, a cookie, or an API response).
5. The frontend passes the token to the widget via the `accessToken` field in the `initialize` call.

---

## Token structure

The JWT payload must contain:

| Field | Description |
|---|---|
| `sub` | Your Gamopanda `accountId` |
| `userData.externalId` | Your system's unique identifier for this customer |

The JWT must be signed with:

| JWT field | Value |
|---|---|
| Secret (`secretOrPrivateKey`) | Your Gamopanda **API secret** (`x-api-secret`) |
| `keyid` header | Your Gamopanda **API key** (`x-api-key`) |
| `expiresIn` | `30m` (recommended) |

---

## Implementation

### Node.js (jsonwebtoken)

```js
import jwt from 'jsonwebtoken';

function generateGamopandaAccessToken({ externalId, accountId, apiKey, apiSecret }) {
  return jwt.sign(
    {
      userData: { externalId },
      sub: accountId
    },
    apiSecret,
    {
      expiresIn: '30m',
      keyid: apiKey
    }
  );
}
```

Call this on your backend when serving a page that embeds the widget:

```js
const accessToken = generateGamopandaAccessToken({
  externalId: 'Your Customer Id',
  accountId: process.env.GAMOPANDA_ACCOUNT_ID,
  apiKey: process.env.GAMOPANDA_API_KEY,
  apiSecret: process.env.GAMOPANDA_API_SECRET
});

// Pass accessToken to your frontend template / API response
```

:::warning
Your API secret must only be used on your backend server. If your API secret is compromised, rotate it immediately via the [API Keys](/api-key) page.
:::

---

### Python (PyJWT)

Install the library: `pip install PyJWT`

```python
import jwt
import os
from datetime import timedelta

def generate_gamopanda_access_token(external_id: str) -> str:
    payload = {
        "userData": {"externalId": external_id},
        "sub": os.environ["GAMOPANDA_ACCOUNT_ID"],
    }
    token = jwt.encode(
        payload,
        os.environ["GAMOPANDA_API_SECRET"],
        algorithm="HS256",
        headers={"kid": os.environ["GAMOPANDA_API_KEY"]},
        # PyJWT accepts timedelta for expiry
        options={"exp": int((timedelta(minutes=30)).total_seconds())},
    )
    return token
```

A simpler form using `exp` directly:

```python
import jwt
import os
import time

def generate_gamopanda_access_token(external_id: str) -> str:
    payload = {
        "userData": {"externalId": external_id},
        "sub": os.environ["GAMOPANDA_ACCOUNT_ID"],
        "exp": int(time.time()) + 1800,  # 30 minutes
        "iat": int(time.time()),
    }
    return jwt.encode(
        payload,
        os.environ["GAMOPANDA_API_SECRET"],
        algorithm="HS256",
        headers={"kid": os.environ["GAMOPANDA_API_KEY"]},
    )
```

---

### Java (java-jwt)

Add the dependency to your `pom.xml`:

```xml
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.4.0</version>
</dependency>
```

```java
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

import java.time.Instant;
import java.util.Date;
import java.util.Map;

public class GamopandaTokenService {

    private final String accountId  = System.getenv("GAMOPANDA_ACCOUNT_ID");
    private final String apiKey     = System.getenv("GAMOPANDA_API_KEY");
    private final String apiSecret  = System.getenv("GAMOPANDA_API_SECRET");

    public String generateAccessToken(String externalId) {
        Algorithm algorithm = Algorithm.HMAC256(apiSecret);

        return JWT.create()
                .withSubject(accountId)
                .withKeyId(apiKey)
                .withClaim("userData", Map.of("externalId", externalId))
                .withIssuedAt(Date.from(Instant.now()))
                .withExpiresAt(Date.from(Instant.now().plusSeconds(1800))) // 30 minutes
                .sign(algorithm);
    }
}
```

---

### PHP (firebase/php-jwt)

Install the library: `composer require firebase/php-jwt`

```php
use Firebase\JWT\JWT;

function generateGamopandaAccessToken(string $externalId): string {
    $payload = [
        'sub'      => getenv('GAMOPANDA_ACCOUNT_ID'),
        'userData' => ['externalId' => $externalId],
        'iat'      => time(),
        'exp'      => time() + 1800, // 30 minutes
    ];

    return JWT::encode(
        $payload,
        getenv('GAMOPANDA_API_SECRET'),
        'HS256',
        getenv('GAMOPANDA_API_KEY') // keyid header
    );
}
```

---

### Ruby (ruby-jwt)

Install the gem: `gem install jwt`

```ruby
require 'jwt'

def generate_gamopanda_access_token(external_id)
  payload = {
    userData: { externalId: external_id },
    sub: ENV['GAMOPANDA_ACCOUNT_ID'],
    iat: Time.now.to_i,
    exp: Time.now.to_i + 1800 # 30 minutes
  }

  headers = { kid: ENV['GAMOPANDA_API_KEY'] }

  JWT.encode(payload, ENV['GAMOPANDA_API_SECRET'], 'HS256', headers)
end
```

---

| Resource | Description |
|---|---|
| [Member](/member) | Upsert end users by `externalId` — the same ID embedded in the token |
| [API Keys](/api-key) | Manage your API key and secret used to sign tokens |
| [JavaScript Widget Integration](/javascript-integration) | How to pass the token to the widget |
| [Shopify Integration](/shopify) | Automatic token generation for Shopify stores |
