Authentication
All RTCstack API endpoints (except GET /v1/health) require two headers on every request.
Headers
| Header | Description |
|---|---|
X-Api-Key | The API key configured in API_KEY env var |
X-RTCstack-Timestamp | Unix timestamp in seconds (string) |
X-RTCstack-Signature | HMAC-SHA256 signature of the request |
Signature Construction
The signature is computed over a canonical string:
METHOD\n
PATH\n
TIMESTAMP\n
SHA256(body)Where:
METHODis uppercase (POST,GET, etc.)PATHis the URL path including query string (e.g./v1/token)TIMESTAMPis the same value asX-RTCstack-TimestampSHA256(body)is the hex-encoded SHA-256 hash of the raw request body (empty string for bodyless requests)
The HMAC key is API_SECRET from your .env.
TypeScript Example
ts
import { createHmac, createHash } from 'crypto'
async function signedFetch(url: string, options: RequestInit = {}) {
const method = (options.method ?? 'GET').toUpperCase()
const path = new URL(url).pathname + new URL(url).search
const timestamp = Math.floor(Date.now() / 1000).toString()
const body = options.body ? String(options.body) : ''
const bodyHash = createHash('sha256').update(body).digest('hex')
const canonical = `${method}\n${path}\n${timestamp}\n${bodyHash}`
const signature = createHmac('sha256', process.env.API_SECRET!)
.update(canonical)
.digest('hex')
return fetch(url, {
...options,
headers: {
...options.headers,
'Content-Type': 'application/json',
'X-Api-Key': process.env.API_KEY!,
'X-RTCstack-Timestamp': timestamp,
'X-RTCstack-Signature': signature,
},
})
}Replay Protection
The API rejects requests with a timestamp more than 5 minutes in the past or future. This prevents replay attacks even if a signature is intercepted.
Error Responses
| Status | Cause |
|---|---|
401 | Missing or invalid X-Api-Key |
401 | Missing signature headers |
403 | Invalid HMAC signature |
403 | Timestamp outside 5-minute window |
Production Checklist
- Store
API_KEYandAPI_SECRETin your secrets manager (Vault, AWS Secrets Manager, etc.) - Never log the full signature header
- Rotate
API_SECRETby updating.envand restarting the API container - The SDK never calls the RTCstack API directly — only your backend should

