---
title: HMAC Session Flow
url: https://docs.kaltura.com/docs/getting-started/authentication/app-tokens-hmac-flow
---

## Starting a Session with AppToken (HMAC Flow)

This is the core flow for integrations that use AppTokens instead of admin secrets.

### The Three-Step Flow

```
session.startWidgetSession  →  HMAC(widget_ks + token_value)  →  appToken.startSession
```

1. **Get a widget session** — an unprivileged KS that identifies your partner
2. **Compute the token hash** — `HASH(widget_ks + token_value)` using the token's hash algorithm
3. **Exchange for a privileged KS** — `appToken.startSession` validates the hash and returns a scoped KS

### appToken.startSession

```
POST /api_v3/service/appToken/action/startSession
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `ks` | string | Yes | The widget session KS (from `startWidgetSession`) |
| `id` | string | Yes | AppToken ID |
| `tokenHash` | string | Yes | `HASH(widget_ks + token_value)` — see section below for computation |
| `userId` | string | No | User ID for the session (overridden by token's `sessionUserId` if set) |
| `type` | int | No | Session type: `0`=USER, `2`=ADMIN (overridden by token's `sessionType`) |
| `expiry` | int | No | Session TTL in seconds (capped by token's `sessionDuration`) |
| `sessionPrivileges` | string | No | Additional privileges (merged with token's `sessionPrivileges`) |

**Response:** A JSON object containing the privileged KS string:
```json
{
  "ks": "djJ8MTIzNDU2fHh4eHh4eHh4...",
  "objectType": "KalturaSessionInfo"
}
```

### Computing the Token Hash

The `tokenHash` is computed by concatenating the widget KS and the AppToken's `token` value, then hashing the result with the algorithm matching the token's `hashType`.

**Formula:** `tokenHash = HASH_ALGORITHM( WIDGET_KS + APP_TOKEN_VALUE )`

The input is the raw string concatenation (no separator, no encoding) of the widget KS followed by the token value. The output is the lowercase hex digest.

**Shell examples by hash type:**

```bash
# SHA256 (recommended)
TOKEN_HASH=$(echo -n "$\{WIDGET_KS\}$\{APP_TOKEN_VALUE\}" | shasum -a 256 | cut -d' ' -f1)

# SHA1
TOKEN_HASH=$(echo -n "$\{WIDGET_KS\}$\{APP_TOKEN_VALUE\}" | shasum -a 1 | cut -d' ' -f1)

# SHA512
TOKEN_HASH=$(echo -n "$\{WIDGET_KS\}$\{APP_TOKEN_VALUE\}" | shasum -a 512 | cut -d' ' -f1)

# MD5
TOKEN_HASH=$(echo -n "$\{WIDGET_KS\}$\{APP_TOKEN_VALUE\}" | md5sum | cut -d' ' -f1)
```

The `echo -n` flag is critical — it ensures the hash input contains only the raw concatenated string with no trailing newline.

### Complete curl Example

```bash
# --- Step 1: Get a widget session (unprivileged) ---
WIDGET_RESPONSE=$(curl -s -X POST "$KALTURA_SERVICE_URL/service/session/action/startWidgetSession" \
  -d "widgetId=_$KALTURA_PARTNER_ID" \
  -d "format=1")
WIDGET_KS=$(echo "$WIDGET_RESPONSE" | jq -r '.ks')

# --- Step 2: Compute the token hash (SHA256) ---
TOKEN_HASH=$(echo -n "$\{WIDGET_KS\}$\{APP_TOKEN_VALUE\}" | shasum -a 256 | cut -d' ' -f1)

# --- Step 3: Exchange for a privileged KS ---
SESSION_RESPONSE=$(curl -s -X POST "$KALTURA_SERVICE_URL/service/appToken/action/startSession" \
  -d "ks=$WIDGET_KS" \
  -d "format=1" \
  -d "id=$APP_TOKEN_ID" \
  -d "tokenHash=$TOKEN_HASH" \
  -d "userId=integration-user" \
  -d "type=0" \
  -d "expiry=86400")
PRIVILEGED_KS=$(echo "$SESSION_RESPONSE" | jq -r '.ks')

# --- Use the privileged KS for API calls ---
curl -X POST "$KALTURA_SERVICE_URL/service/media/action/list" \
  -d "ks=$PRIVILEGED_KS" \
  -d "format=1" \
  -d "pager[pageSize]=5"
```

**Response (Step 1 — session.startWidgetSession):**
```json
{
  "ks": "djJ8MTIzNDU2fDlhN...",
  "partnerId": 123456,
  "userId": "0",
  "objectType": "KalturaStartWidgetSessionResponse"
}
```

**Response (Step 3 — appToken.startSession):**
```json
{
  "ks": "djJ8MTIzNDU2fHh4eHh4eHh4...",
  "objectType": "KalturaSessionInfo"
}
```
