Authentication
JWT session tokens, API keys, and webhook signature verification.
Credentials
After your sandbox is provisioned, you receive three credentials:
| Credential | Used For | Where |
|---|---|---|
| API Key | Server-to-server API calls | X-API-Key header |
| API Secret | Signing JWT session tokens | Your backend only |
| Webhook Secret | Verifying webhook signatures | Your 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), 200Token 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.