The Open Chat Widget uses two separate API keys to secure different endpoint categories.
API Key Types
Protects all chat endpoints that the widget and headless clients use:
POST /v1/chat - Non-streaming chat
POST /v1/chat/stream - Streaming chat (NDJSON)
POST /chat - Legacy streaming endpoint
Environment variable:
WIDGET_API_KEY=your-strong-random-secret-key
Required headers (either one):
X-Api-Key: your-strong-random-secret-key
or
X-Widget-Api-Key: your-strong-random-secret-key
ADMIN_API_KEY
Protects admin endpoints for viewing conversations:
GET /v1/admin/conversations - List conversations
GET /v1/admin/conversations/:conversationId - Get conversation thread
Environment variable:
ADMIN_API_KEY=your-strong-random-admin-key
Required header:
X-Admin-Api-Key: your-strong-random-admin-key
ADMIN_API_KEY is optional. If not configured, admin endpoints return 503 Service Unavailable.
Timing-Safe Comparison
All API key validation uses the secureEquals function to prevent timing attacks:
function secureEquals(left: string, right: string): boolean {
const leftBuffer = Buffer.from(left);
const rightBuffer = Buffer.from(right);
if (leftBuffer.length !== rightBuffer.length) {
return false;
}
return timingSafeEqual(leftBuffer, rightBuffer);
}
This implementation:
- Converts both strings to buffers
- Checks length equality first (fast path for obvious mismatches)
- Uses Node.js
crypto.timingSafeEqual() for constant-time comparison
- Prevents attackers from measuring response times to guess key characters
Authentication Flow
Chat Endpoints
The requireChatApiKey middleware validates chat requests:
function requireChatApiKey(req: Request, res: Response): boolean {
const apiKey = getChatApiKey(req);
if (!apiKey || !secureEquals(env.WIDGET_API_KEY, apiKey)) {
res.status(401).json({ error: "Unauthorized" });
return false;
}
return true;
}
Accepts keys from either X-Api-Key or X-Widget-Api-Key headers for backward compatibility.
Admin Endpoints
The requireAdminApiKey middleware validates admin requests:
function requireAdminApiKey(req: Request, res: Response): boolean {
if (!env.ADMIN_API_KEY) {
res.status(503).json({ error: "ADMIN_API_KEY is not configured on this deployment" });
return false;
}
const apiKey = req.header("x-admin-api-key") ?? "";
if (!secureEquals(env.ADMIN_API_KEY, apiKey)) {
res.status(401).json({ error: "Unauthorized" });
return false;
}
return true;
}
API Key Rotation
Follow these steps to rotate API keys without downtime:
- Generate new key: Create a strong random string (32+ characters)
-
Update backend environment: Set new
WIDGET_API_KEY and redeploy
-
Update widget embedding: Change
data-api-key attribute on all embedded widgets
<script
src="https://your-backend.com/widget/chat-widget.js"
data-api-key="new-key-here"
...
></script>
-
Update headless clients: Update
X-Api-Key header in all API calls
-
Verify: Test chat functionality with new key
Rotating ADMIN_API_KEY
- Generate new key: Create a strong random string
-
Update backend environment: Set new
ADMIN_API_KEY and redeploy
-
Update dashboard environment: Set new
ADMIN_API_KEY in dashboard config if applicable
-
Update admin clients: Update
X-Admin-Api-Key header in any admin API integrations
-
Verify: Test admin endpoints with new key
Best Practices
Never commit API keys to version control. Use environment variables or secret management systems.
- Generate strong keys: Use cryptographically secure random generators (32+ characters)
- Rotate regularly: Change keys quarterly or after suspected exposure
- Separate keys: Keep
WIDGET_API_KEY and ADMIN_API_KEY different
- Limit distribution: Only share
WIDGET_API_KEY with trusted frontend deployments
- Monitor usage: Watch for unexpected 401 responses indicating invalid keys
- Use HTTPS: Always transmit keys over encrypted connections
Error Responses
Missing or Invalid Key
{
"error": "Unauthorized"
}
HTTP Status: 401 Unauthorized
{
"error": "ADMIN_API_KEY is not configured on this deployment"
}
HTTP Status: 503 Service Unavailable