Authentication
PayWarden has two distinct auth surfaces:
| Surface | Used by | Mechanism |
|---|---|---|
| Merchant API | Your server-side integration | X-API-Key header |
| Admin API | Dashboard / operators | httpOnly cookie admin_token |
Merchant API key authentication
All merchant API endpoints require an X-API-Key header. The following endpoints are public (no auth):
GET /api/v1/healthGET /api/v1/health/liveGET /api/v1/checkout/:id/status
curl https://pay.yourdomain.com/api/v1/payments \
-H "X-API-Key: pw_live_abc123..."Each merchant has their own API key. The key is stored in the database as an HMAC-SHA256 hash — the raw key is shown exactly once at creation time and cannot be retrieved again. If a key is lost, rotate it via the admin API.
Keys are prefixed (e.g. pw_live_xxxx) so they can be indexed in logs without exposing the secret portion.
Generating API keys
Via admin dashboard
- Log in at
/admin - Go to Merchants
- Click Create Merchant
- Copy the generated API key — it is shown only once
Via admin API
Admin endpoints are authenticated by the admin_token httpOnly cookie set on login. Use curl -c to save the cookie and -b to reuse it:
# 1. Log in — stores the admin_token cookie in cookies.txt
curl -X POST https://pay.yourdomain.com/api/v1/admin/auth/login \
-c cookies.txt \
-H "Content-Type: application/json" \
-d '{"password": "your-admin-password"}'
# 2. Create a merchant using the saved cookie
curl -X POST https://pay.yourdomain.com/api/v1/admin/merchants \
-b cookies.txt \
-H "Content-Type: application/json" \
-d '{
"name": "My Shop",
"webhook_secret": "a-long-random-string"
}'Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Shop",
"api_key": "pw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"api_key_prefix": "pw_live_xxxx",
"webhook_secret": "a-long-random-string",
"is_active": true,
"created_at": "2026-01-01T00:00:00.000Z"
}Save the API key now
The full api_key is returned only on creation. Store it securely. If lost, rotate via POST /api/v1/admin/merchants/:id/rotate-api-key.
Admin authentication
The admin dashboard uses an httpOnly JWT cookie. There is no Bearer token support — all admin requests must carry the admin_token cookie.
# Login — rate-limited to 5 attempts per 15 minutes
curl -X POST https://pay.yourdomain.com/api/v1/admin/auth/login \
-c cookies.txt \
-H "Content-Type: application/json" \
-d '{"password": "your-admin-password"}'
# Logout
curl -X POST https://pay.yourdomain.com/api/v1/admin/auth/logout \
-b cookies.txtCookie properties: httpOnly, SameSite=Strict, Secure in production, path /, 24-hour max age.
Admin password is set via ADMIN_PASSWORD in .env.
See Admin API for the full admin endpoint reference.
Error responses
Errors follow the shape { error, code }:
{
"error": "Missing API key",
"code": "UNAUTHORIZED"
}| Status | Code | Meaning |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid X-API-Key |
403 | MERCHANT_INACTIVE | Valid key but merchant is suspended |
401 | ADMIN_UNAUTHORIZED | Admin endpoint hit without admin_token cookie |
401 | ADMIN_TOKEN_INVALID | admin_token cookie present but JWT invalid or expired |
401 | INVALID_PASSWORD | Wrong password on admin login |