---
title: "JSON Web Token (JWT)"
canonical_url: "https://apidocs.sportradar.com/resources/widgets/docs/tutorials/jwt"
markdown_url: "https://apidocs.sportradar.com/resources/widgets/docs/tutorials/jwt.md"
last_updated: "2026-08-11T09:00:08Z"
---

# JSON Web Token (JWT)

Some widget use cases require validated data integrity from your backend to prevent misuse by end-users or malicious actors. Create and sign a JSON Web Token (JWT) with a public/private key pair, then pass that token to widgets as a property.

> **Signing Algorithm**
>
> JWT tokens must be signed with **RS256** (RSA signature with SHA-256).

## Prerequisites

### Tools

- OpenSSL toolkit
- JWT library for your language (e.g. `jsonwebtoken`)

### Backend

- Server-side environment (Node.js, Python, Java, C#, …)
- Ability to sign tokens securely

### Sportradar Access

- Account representative for public key setup
- Product credentials when needed (VS API key, Bet Concierge brand ID)

## Implementation Overview

The following diagram illustrates the JWT authentication flow:

![JWT authentication flow](https://apidocs.sportradar.com/resources/widgets/static/img/jwt-flow.svg)

1. **Generate RSA Keys**

   Create a 2048-bit RSA key pair with OpenSSL.

2. **Share the Public Key**

   Send `rsa-public.pem` to Sportradar so tokens can be validated.

3. **Sign Tokens Server-Side**

   Build the payload with required claims and sign with your private key (RS256).

4. **Pass JWT to the Widget**

   Deliver the token from your backend and set it on widget initialization.

***

## Step 1: Generate RSA Key Pair

Generate a pair of RSA keys using the OpenSSL toolkit:

```bash
# Generate private key (2048-bit RSA)
openssl genrsa -out rsa-private.pem 2048

# Extract public key from private key
openssl rsa -in rsa-private.pem \
  -pubout \
  -outform PEM \
  -out rsa-public.pem
```

### `rsa-private.pem`

Keep this secure on your backend server and use it to sign tokens.

### `rsa-public.pem`

Send this file to Sportradar for token validation setup.

**Optional: Verify the Generated Keys**

```bash
# View private key
openssl rsa -in rsa-private.pem -text -noout

# View public key
openssl rsa -in rsa-public.pem -pubin -text -noout
```

Example public key format (`rsa-public.pem`):

```plaintext
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
...
-----END PUBLIC KEY-----
```

> **Security Best Practices**
>
> - Never expose your private key (`rsa-private.pem`) in client-side code, version control, or logs
> - Never share private keys with third parties
> - Store private keys in secure key management systems or environment variables
> - Never generate or sign JWT tokens in client-side JavaScript
> - Rotate keys periodically
> - Generate separate key pairs for each environment (dev, staging, production)

> **Next Action**
>
> Send the `rsa-public.pem` file to your Sportradar sales or client setup representative. They will configure the system to validate tokens signed with your private key.

> **Non-Production Environments**
>
> If you need development or non-production JWTs, tell client setup which environment the public key is for (`prod`, `nonprod`, or `dev`), and put the same value in the `env` claim on JWTs signed with that key. Production-only flows can omit `env`; it defaults to production behavior.

***

## Step 2: Create JWT Tokens for Users

Every logged-in user requires their own unique JWT.

### Generate On-Demand

Create a new token for each page request.

### Cache With TTL

Generate once and cache on your backend until near expiration.

### The Token Object

JSON Web Token (JWT) is an open standard ([RFC 7519](https://tools.ietf.org/html/rfc7519)) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.

Learn more about [JWT structure](https://jwt.io/introduction#what-is-json-web-token-structure).

### Header

Algorithm and token type

### Payload

Claims (identity, scope, product data)

### Signature

RS256 signature over header + payload

```json
{
  "header": {
    "alg": "RS256",
    "typ": "JWT"
  },
  "payload": {
    "iss": "your-organization",
    "sub": "user123",
    "scope": "vs",
    "iat": 1681718850,
    "apiKey": "your-api-key",
    "displayName": "John Doe",
    "userType": "Normal"
  },
  "signature": "..."
}
```

### Required Base Claims

All JWT tokens must include these standard claims:

```javascript
{
    iss: '<organization>',
    sub: '<id>',
    scope: 'av' | 'vs' | 'sb' | 'av vs sb',
    iat: 1681718850
}
```

| Claim   | Type              | Description                                                                                                                                                                                                                                                                                                 | Example                                                             |
| ------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `iss`   | string            | Issuer identifier — identifies the principal that issued the JWT. Typically your organization or application identifier.                                                                                                                                                                                    | `'your-organization'`                                               |
| `sub`   | string            | Unique end-user identifier. Use your system's user ID or a salted hash if you prefer not to expose actual IDs.                                                                                                                                                                                              | `'user_12345'` or `'a3f5b2c8d1e4'`                                  |
| `scope` | string            | Space-separated permissions for this user. Controls access to specific features.<br/>`av` - Enables audio/video stream access in Live Match Tracker.<br/>`vs` - Virtual Stadium access.<br/>`sb` - Bet Concierge access                                                                                     | `'av vs'` for LMT audio/video and Virtual Stadium (space-separated) |
| `iat`   | number            | Token issued-at timestamp in **seconds** since Unix epoch (UTC). Default validity: 16 hours. Most JWT libraries add this automatically.                                                                                                                                                                     | `1681718850`                                                        |
| `exp`   | number (optional) | Expiration timestamp in **seconds** since Unix epoch (UTC). If omitted, defaults to 16 hours after `iat`.                                                                                                                                                                                                   | `1681776450`                                                        |
| `env`   | string (optional) | Which environment the **signing key** is for: `prod`, `nonprod`, or `dev`. Set when you use a non-production key (for example a development-only key pair); tell client setup the same when you submit that key's public key. Omit or leave empty for your production key—then behavior matches **`prod`**. | `'dev'`                                                             |

> **Tip**
>
> The `iat` claim is typically added automatically by JWT libraries. You can also add an `exp` (expiration) claim to set a shorter validity period than the default 16 hours. Shorter token lifespans improve security.

**Standard Claims**

```json
{
  "iss": "your-organization",
  "sub": "user123",
  "scope": "vs",
  "iat": 1681718850,
  "exp": 1681776450
}
```

**Multiple Products**

```json
{
  "scope": "av vs"
}
```

**Calculate Expiration**

```js
// 16 hours from now
const iat = Math.floor(Date.now() / 1000);
const exp = iat + (16 * 60 * 60);

// Token payload
{
  "iat": iat,
  "exp": exp
}
```

### Product-Specific Claims

Add product claims only for the scopes you enable.

**Virtual Stadium (`vs`)**

```javascript
{
    // Base claims (iss, sub, scope, iat)
    apiKey: '<key>',
    displayName: '<userName>',
    userType: 'Normal' | 'VIP'
}
```

| Claim         | Type   | Description                                                                         | Example                      |
| ------------- | ------ | ----------------------------------------------------------------------------------- | ---------------------------- |
| `apiKey`      | string | Your Virtual Stadium API key (provided during onboarding)                           | `'vs_api_key_abc123'`        |
| `displayName` | string | Public name shown to other users in the Virtual Stadium interface                   | `'JohnDoe'` or `'Player123'` |
| `userType`    | string | User access level: `'Normal'` or `'VIP'`. VIP users have highlighted chat messages. | `'VIP'`                      |

> **VIP Users**
>
> VIP users receive enhanced visibility with different design styling. Their chat messages are highlighted to stand out in conversations. For more details, see the [VIP functionality documentation](https://apidocs.sportradar.com/resources/virtual-stadium/docs/web/widgets/virtualStadium/features/vip.md).

**Virtual Stadium Payload Examples**

***Normal User Claims:***

```json
{
  "apiKey": "vs_live_abc123xyz789",
  "displayName": "John Doe",
  "userType": "Normal"
}
```

***VIP User Claims:***

```json
{
  "apiKey": "vs_live_abc123xyz789",
  "displayName": "Jane Smith",
  "userType": "VIP"
}
```

***Complete JWT Payload:***

```json
{
  "iss": "your-organization",
  "sub": "user123",
  "scope": "vs",
  "iat": 1681718850,
  "exp": 1681776450,
  "apiKey": "vs_live_abc123xyz789",
  "displayName": "John Doe",
  "userType": "Normal"
}
```

**Bet Concierge (`sb`)**

```javascript
{
    // Base claims (sub, scope, iat)
    sbBrandId: '<id>',
    sbUserType: '<type>'
}
```

| Claim        | Type   | Description                                                                                                                                                                                                                                                                                                                                                                   | Example                     |
| ------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `sbBrandId`  | string | Your brand identifier (provided during onboarding)                                                                                                                                                                                                                                                                                                                            | `'brand_xyz789'`            |
| `sbUserType` | string | Access level or category for the user in the bet concierge system. Can be any string value agreed upon with Sportradar during your client setup. This value is used for rate limiting and must be registered with your client setup configuration before issuing JWTs with it. Can be left empty if only a single user type is expected (e.g., in a development environment). | `'premium'` or `'standard'` |

***

## Step 3: Sign the Token

Sign your JWT using the **RS256** algorithm with your private key. Most JWT libraries support RS256 signing.

#### Parameters

**`payload`** `object` required

Token payload containing all required claims (see claim descriptions above).

**`privateKey`** `string` required

Your RSA private key in PEM format.

**`algorithm`** `string` required

Must be `RS256` (RSA signature with SHA-256).

#### Returns

Returns a signed JWT token string that can be used to authenticate with Sportradar widgets and products.

> **Token Security**
>
> - Generate tokens server-side only
> - Create unique tokens for each user session
> - Never share tokens between users
> - Tokens are valid for 16 hours maximum (or until `exp` if specified)
> - Shorter token lifespans improve security

**Node.js**

***Sign JWT Token:***

```javascript
import jwt from "jsonwebtoken";
import fs from "fs";

// Load private key
const privateKey = fs.readFileSync("rsa-private.pem", "utf8");

// Create token with base + Virtual Stadium claims
const token = jwt.sign(
  {
    iss: "your-organization",
    sub: "user123",
    scope: "vs",
    apiKey: "vs_live_abc123xyz789",
    displayName: "John Doe",
    userType: "Normal",
    // iat is added automatically
  },
  privateKey,
  { algorithm: "RS256" },
);

console.log(token);
```

**Python**

***Sign JWT Token:***

```python
import jwt
from datetime import datetime, timedelta

# Load private key
with open('rsa-private.pem', 'r') as key_file:
    private_key = key_file.read()

# Create payload
payload = {
    'iss': 'your-organization',
    'sub': 'user123',
    'scope': 'vs',
    'iat': datetime.utcnow(),
    'exp': datetime.utcnow() + timedelta(hours=16),
    'apiKey': 'vs_live_abc123xyz789',
    'displayName': 'John Doe',
    'userType': 'Normal'
}

# Sign token
token = jwt.encode(
    payload,
    private_key,
    algorithm='RS256'
)

print(token)
```

**Java**

***Sign JWT Token:***

```java
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;

// Load private key
byte[] keyBytes = Files.readAllBytes(
    Paths.get("rsa-private.pem")
);
PKCS8EncodedKeySpec spec =
    new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(spec);

// Create token
String token = Jwts.builder()
    .setIssuer("your-organization")
    .setSubject("user123")
    .claim("scope", "vs")
    .claim("apiKey", "vs_live_abc123xyz789")
    .claim("displayName", "John Doe")
    .claim("userType", "Normal")
    .setIssuedAt(new Date())
    .signWith(privateKey, SignatureAlgorithm.RS256)
    .compact();

System.out.println(token);
```

**C#**

***Sign JWT Token:***

```csharp
using System;
using System.IO;
using System.Security.Cryptography;
using Jose;

// Load private key
var privateKeyText = File.ReadAllText(
    "rsa-private.pem"
);
var rsa = RSA.Create();
rsa.ImportFromPem(privateKeyText);

// Create payload
var payload = new Dictionary<string, object>
{
    { "iss", "your-organization" },
    { "sub", "user123" },
    { "scope", "vs" },
    { "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
    { "apiKey", "vs_live_abc123xyz789" },
    { "displayName", "John Doe" },
    { "userType", "Normal" }
};

// Sign token
string token = JWT.Encode(
    payload,
    rsa,
    JwsAlgorithm.RS256
);

Console.WriteLine(token);
```

**Example Token Output**

```plaintext
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwic2NvcGUiOiJ2cyIsImlhdCI6MTY4MTcxODg1MCwiYXBpS2V5Ijoidi3NfbGl2ZV9hYmMxMjN4eXo3ODkiLCJ1c2VySWQiOiJ1c2VyMTIzIiwiZGlzcGxheU5hbWUiOiJKb2huIERvZSIsInVzZXJUeXBlIjoiTm9ybWFsIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c...
```

**Example With Multiple Scopes (Node.js)**

```javascript
import jwt from "jsonwebtoken";

const token = jwt.sign(
  {
    sub: "user_12345",
    scope: "av vs", // Audio/video + Virtual Stadium
    // iat is added automatically
  },
  process.env.PRIVATE_SIGNING_KEY,
  { algorithm: "RS256" },
);
```

***

## Step 4: Pass JWT to Widgets

Deliver the signed JWT from your backend to your frontend application, then pass it to the widget during initialization.

**SIR Integration**

Pass the JWT as a property when adding the widget using [SIR](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/SIR.md):

```javascript
SIR("addWidget", "#my-widget", "widget_name_here", {
  jwt: generatedJwtToken,
  // ... other widget properties
});
```

**Complete Frontend Example**

```html
<div id="lmt-widget"></div>

<script>
  // Assume 'userJWT' is passed from your backend
  const userJWT = "{{ jwt_from_backend }}";

  // Initialize Live Match Tracker with JWT for audio/video
  SIR("addWidget", "#lmt-widget", "sr-widgets-lmt", {
    matchId: "sr:match:12345",
    jwt: userJWT,
    // ... other configuration
  });
</script>
```

***

## Key Rotation

Rotate signing keys without service interruption:

1. **Generate a New Key Pair**

   Create a new RSA key pair using the same process as Step 1.

2. **Submit the Public Key**

   Send the new public key to your Sportradar representative.

3. **Wait for Confirmation**

   Sportradar adds the new key to your configuration and notifies you. Both old and new keys work during the transition.

4. **Switch Your Backend**

   Start signing with the new private key, then inform Sportradar when migration is complete so the old public key can be removed.
