Authentication

JWT session tokens, API keys, and webhook signature verification.

Credentials

After your sandbox is provisioned, you receive three credentials:

CredentialUsed ForWhere
API KeyServer-to-server API callsX-API-Key header
API SecretSigning JWT session tokensYour backend only
Webhook SecretVerifying webhook signaturesYour backend only

!Security

Never expose your API Secret or Webhook Secret in client-side code, version control, or logs.

JWT Session Tokens

Session tokens are JWTs signed with your API Secret using HMAC-SHA256. They contain the session configuration and are passed to the embed URL.

Node.js
import jwt from 'jsonwebtoken';

const token = jwt.sign(
  {
    partner_id: 'your-partner-id',
    student_id: 'stu_abc123',
    subject: 'math',
    level: '11-12',
    engine: 'standard',
    exp: Math.floor(Date.now() / 1000) + 7200 // 2 hours
  },
  process.env.EVELYN_API_SECRET,
  { algorithm: 'HS256' }
);
Python
import jwt, time, os

token = jwt.encode(
    {
        "partner_id": "your-partner-id",
        "student_id": "stu_abc123",
        "subject": "math",
        "level": "11-12",
        "engine": "standard",
        "exp": int(time.time()) + 7200
    },
    os.environ["EVELYN_API_SECRET"],
    algorithm="HS256"
)
PHP
use Firebase\JWT\JWT;

$token = JWT::encode([
    'partner_id' => 'your-partner-id',
    'student_id' => 'stu_abc123',
    'subject' => 'math',
    'level' => '11-12',
    'engine' => 'standard',
    'exp' => time() + 7200
], $_ENV['EVELYN_API_SECRET'], 'HS256');

API Key Authentication

All REST API calls require your API Key in the X-API-Key header:

curl https://api.evelynlearning.com/v1/sessions \
  -H "X-API-Key: YOUR_API_KEY"

Webhook Signature Verification

Every webhook request includes an X-Evelyn-Signature header containing an HMAC-SHA256 signature of the request body. Always verify this before processing.

Node.js / Express
import crypto from 'crypto';

app.post('/webhooks/evelyn', (req, res) => {
  const signature = req.headers['x-evelyn-signature'];
  const expected = crypto
    .createHmac('sha256', process.env.EVELYN_WEBHOOK_SECRET)
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (signature !== expected) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process the event
  const { event, data } = req.body;
  console.log(`Received ${event}`, data);
  res.status(200).json({ received: true });
});
Python / Flask
import hmac, hashlib, json
from flask import Flask, request, jsonify

@app.route('/webhooks/evelyn', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Evelyn-Signature')
    expected = hmac.new(
        os.environ['EVELYN_WEBHOOK_SECRET'].encode(),
        request.data,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        return jsonify(error='Invalid signature'), 401

    event = request.json
    print(f"Received {event['event']}", event['data'])
    return jsonify(received=True), 200

Token Expiry

Session tokens should have a short expiry (1-2 hours). The token is validated when the embed loads. Once a session is active, it continues regardless of token expiry. Generate a fresh token for each session — do not reuse tokens.