Authentication
The Gateway supports multiple authentication methods to accommodate different client types and security requirements.
Authentication Methods
| Method | Use Case | Header Format |
|---|---|---|
| API Key | Third-party integrations | Authorization: Bearer olive_live_xxx |
| JWT | Admin dashboard, internal UI | Authorization: Bearer eyJ... |
| Service Auth | Internal services (Agent-TS) | X-Service-Name, X-Service-Timestamp, X-Service-Signature |
| POS HMAC | Merchant terminals | X-API-Key-ID, X-Timestamp, X-Signature |
| Partner HMAC | Partner integrations | X-API-Key-ID, X-Partner-ID, X-Timestamp, X-Signature |
API Key Authentication
For third-party integrations and external clients.
Header Format
Authorization: Bearer olive_live_xxxxxxxxxxxxxxxxxxxxxxxx
API Key Structure
| Prefix | Environment |
|---|---|
olive_live_ | Production |
olive_test_ | Staging/Testing |
Creating API Keys
Admin users can create API keys via the admin endpoint:
curl -X POST http://localhost:8080/api/v1/admin/api-keys \
-H "Authorization: Bearer ADMIN_JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "Partner Integration",
"scopes": ["payments:read", "payments:write", "balance:read"],
"expires_at": "2025-12-31T23:59:59Z"
}'
API Key Scopes
| Scope | Description |
|---|---|
payments:read | Read transaction history |
payments:write | Create payments and transfers |
balance:read | Read account balances |
subscribers:read | Read subscriber information |
subscribers:write | Create and update subscribers |
cards:read | List cards |
cards:write | Link/block cards |
JWT Authentication
For admin users and internal dashboards.
Obtaining a JWT
curl -X POST http://localhost:8080/api/v1/admin/login \
-H "Content-Type: application/json" \
-d '{
"email": "admin@smartpay.example.com",
"password": "secure-password"
}'
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 86400
}
Using JWT
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
JWT Claims
{
"sub": "admin-user-id",
"email": "admin@smartpay.example.com",
"role": "system_admin",
"iat": 1704067200,
"exp": 1704153600
}
Admin Roles
| Role | Permissions |
|---|---|
system_admin | Full access to all operations |
compliance_user | Compliance alerts and monitoring |
support_user | Read-only access, basic support ops |
finance_user | Transaction and reconciliation access |
Service Authentication
For internal service-to-service communication (e.g., Agent-TS to Gateway).
Headers
X-Service-Name: gateway
X-Service-Timestamp: 1710072000
X-Service-Signature: <hex-hmac>
Signature Generation
import crypto from 'crypto';
function generateServiceHeaders(
method: string,
path: string,
query: string,
secret: string,
timestamp: string,
serviceName: string
) {
const payload = `${method}|${path}|${query}|${timestamp}`;
const signature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return {
'X-Service-Name': serviceName,
'X-Service-Timestamp': timestamp,
'X-Service-Signature': signature,
};
}
// Usage
const timestamp = Math.floor(Date.now() / 1000).toString();
const headers = generateServiceHeaders(
'POST',
'/api/v1/compliance/alerts',
'',
process.env.AGENT_TS_SECRET,
timestamp,
'agent-ts'
);
Configuration
In config.yaml:
service_auth:
agent_ts:
secret: ${AGENT_TS_SECRET}
name: "agent-ts"
pos_service:
secret: ${POS_SERVICE_SECRET}
name: "pos-service"
POS HMAC Authentication
Per-client HMAC for POS terminal authentication.
Headers Required
X-API-Key-ID: pos_key_abc123
X-Timestamp: 2026-03-10T12:00:00Z
X-Signature: 5d41402abc4b2a76b9719d911017c592
Signature Calculation
func generatePOSSignature(method, path, timestamp string, body []byte, secret string) string {
payload := strings.Join([]string{method, path, timestamp, string(body)}, "\n")
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
return hex.EncodeToString(mac.Sum(nil))
}
Client Implementation
// Client-side (JavaScript)
const crypto = require('crypto');
function signPOSRequest(apiKeyId, secret, method, path, rawBody) {
const timestamp = new Date().toISOString();
const payload = `${method}\n${path}\n${timestamp}\n${rawBody}`;
const signature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return {
'X-API-Key-ID': apiKeyId,
'X-Timestamp': timestamp,
'X-Signature': signature
};
}
Partner HMAC Authentication
Partner routes use the same newline-delimited signature format as POS, with one additional header:
X-API-Key-ID: partner_key_abc123
X-Partner-ID: VULT
X-Timestamp: 2026-03-10T12:00:00Z
X-Signature: <hex-hmac>
The canonical string is:
METHOD
PATH
TIMESTAMP
RAW_BODY
Combined Authentication
Some endpoints accept multiple authentication methods using AllowServiceOrUser middleware:
// Accepts either service auth OR user API key/JWT
api.GET("/subscribers/:id",
mw.AllowServiceOrUser(),
h.GetSubscriber,
)
Webhook Authentication
VULT webhooks use HMAC signature verification:
Configuration
webhook:
vult_hmac_secret: ${VULT_WEBHOOK_SECRET}
Verification Header
X-Webhook-Signature: <hex-hmac-of-raw-body>
Security Best Practices
API Key Security
- Store keys securely (never in client-side code)
- Rotate keys periodically
- Use scopes to limit access
- Set expiration dates
JWT Security
- Use strong secrets (minimum 32 characters)
- Set appropriate expiration times
- Implement token refresh flows
- Validate all claims
HMAC Security
- Include timestamp to prevent replay attacks
- Validate timestamp is within acceptable window (5 min)
- Use constant-time comparison
- Rotate secrets periodically
Troubleshooting
401 Unauthorized
- Verify header format is correct
- Check API key or JWT is valid and not expired
- Ensure key has required scopes
403 Forbidden
- Check user has required role
- Verify endpoint is accessible with current auth method
HMAC Signature Mismatch
- Verify secret matches on both sides
- Check timestamp format: RFC3339 for POS/Partner, unix seconds for service auth
- Ensure the exact raw body used for signing is what was sent
- Verify correct encoding (UTF-8)