Authentication
Hookbase supports two authentication methods: JWT tokens and API keys.
JWT Tokens
JWT tokens are obtained by logging in and are used for browser-based access. Access tokens expire after 15 minutes; refresh tokens last 7 days.
Register
curl -X POST https://api.hookbase.app/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "your-password",
"displayName": "John Doe"
}'Response:
{
"user": {
"id": "33333333-3333-4333-8333-333333333333",
"email": "user@example.com",
"displayName": "John Doe",
"emailVerified": false
},
"token": "eyJhbGciOiJIUzI1NiIs...",
"message": "Verification email sent"
}Email Verification
# Verify email with token from email link.
# The token goes in the path, and you must be signed in as the account being verified.
curl -X POST https://api.hookbase.app/api/auth/verify/verification-token-from-email \
-H "Authorization: Bearer YOUR_JWT_TOKEN"# Resend verification email
curl -X POST https://api.hookbase.app/api/auth/resend-verification \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Login
curl -X POST https://api.hookbase.app/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "your-password"
}'Response:
{
"user": {
"id": "33333333-3333-4333-8333-333333333333",
"email": "user@example.com",
"displayName": "John Doe"
},
"token": "eyJhbGciOiJIUzI1NiIs...",
"expiresAt": "2024-01-22T10:30:00Z"
}Login with 2FA
If the user has two-factor authentication enabled, the initial login returns a challenge:
{
"requiresTwoFactor": true,
"challengeToken": "2fa-challenge-token"
}Complete the login with the TOTP code:
curl -X POST https://api.hookbase.app/api/auth/login/2fa \
-H "Content-Type: application/json" \
-d '{
"challengeToken": "2fa-challenge-token",
"code": "123456"
}'Forgot Password
# Request password reset
curl -X POST https://api.hookbase.app/api/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'# Reset password with token from email. The token is a path segment; the body carries
# only the new password.
curl -X POST https://api.hookbase.app/api/auth/reset-password/reset-token-from-email \
-H "Content-Type: application/json" \
-d '{"password": "new-secure-password"}'Check a token before showing the form — GET the same path, which returns 400 for an unknown
token and 410 once it has expired:
curl https://api.hookbase.app/api/auth/reset-password/reset-token-from-emailOAuth Login
# Redirect user to OAuth provider
# GitHub
GET https://api.hookbase.app/api/auth/oauth/github
# Google
GET https://api.hookbase.app/api/auth/oauth/googleThe OAuth flow redirects the user to the provider, then back to Hookbase with an auth token.
Device Flow (CLI)
The CLI uses the device authorization flow:
# 1. Request device code
curl -X POST https://api.hookbase.app/api/auth/device \
-H "Content-Type: application/json" \
-d '{"clientId": "hookbase-cli"}'
# Response:
# {
# "deviceCode": "abc123",
# "userCode": "ABCD-1234",
# "verificationUri": "https://www.hookbase.app/device",
# "expiresIn": 900,
# "interval": 5
# }
# 2. User visits verificationUri and enters userCode
# 3. CLI polls for token
curl -X POST https://api.hookbase.app/api/auth/device/token \
-H "Content-Type: application/json" \
-d '{"deviceCode": "abc123", "clientId": "hookbase-cli"}'Using JWT Tokens
Include the token in the Authorization header:
curl https://api.hookbase.app/api/organizations \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Token Refresh
Tokens expire after 7 days. Refresh before expiration:
curl -X POST https://api.hookbase.app/api/auth/refresh \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Logout
curl -X POST https://api.hookbase.app/api/auth/logout \
-H "Authorization: Bearer YOUR_JWT_TOKEN"API Keys
API keys are recommended for programmatic access. They don't expire and can be scoped to specific permissions.
Creating an API Key
curl -X POST https://api.hookbase.app/api/api-keys \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "CI/CD Pipeline",
"scopes": ["read", "write"]
}'Response:
{
"apiKey": {
"id": "55555555-5555-4555-8555-555555555555",
"name": "CI/CD Pipeline",
"key": "whr_4f3c2a1b9d8e7f6a5b4c3d2e1f0a9b8c",
"scopes": ["read", "write"],
"createdAt": "2024-01-15T10:30:00Z"
}
}Warning
The full API key is only shown once. Store it securely!
Using API Keys
Include the API key in the Authorization header:
curl https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key"API Key Format
A key is the literal string whr_ followed by 32 lowercase hex characters:
whr_4f3c2a1b9d8e7f6a5b4c3d2e1f0a9b8cOnly the first 12 characters (whr_ plus 8 hex) are stored in readable form, as a display prefix;
the key itself is stored as a SHA-256 hash and is shown exactly once, at creation.
Warning
There are no environment-scoped key prefixes — whr_live_ and whr_test_ do not exist, and
Hookbase has no test or sandbox mode. Separate production from testing by using separate
organizations.
Available Scopes
Scopes are global verbs, not per-resource permissions. There are exactly three:
| Scope | Allows |
|---|---|
read | GET requests |
write | Creating and updating resources, and replaying deliveries |
delete | Deleting resources |
A key created without an explicit scopes array gets ["read", "write"]. Anything outside the
three values above is rejected with 400 Invalid scopes: ....
An endpoint accepts a request when the key holds at least one of the scopes that endpoint
requires; otherwise it returns 403 with a message naming both the required and the held scopes:
{
"error": "API key missing required scope. Required: write. Your scopes: read"
}Info
Scope checks apply only to API key auth. A request authenticated with a JWT skips them entirely and is governed by the user's organization role instead.
Warning
Resource-qualified scopes such as sources:read, events:write or admin are not a Hookbase
format. Sending them returns 400.
Managing API Keys
List API Keys
curl https://api.hookbase.app/api/api-keys \
-H "Authorization: Bearer whr_your_api_key"Revoke API Key
curl -X DELETE https://api.hookbase.app/api/api-keys/{keyId} \
-H "Authorization: Bearer whr_your_api_key"Security Best Practices
For JWT Tokens
- Store securely: Use secure, HTTP-only cookies in browsers
- Short expiration: Tokens expire after 7 days
- Refresh proactively: Refresh tokens before they expire
- Logout on security events: Logout after password changes
For API Keys
- Use scoped keys: Only grant necessary permissions
- Rotate regularly: Rotate keys periodically
- Use environment variables: Never commit keys to code
- Monitor usage: Review API key activity in the dashboard
- Revoke unused keys: Delete keys that are no longer needed
Environment-Specific Keys
Create separate API keys for each environment:
# Production key
curl -X POST .../api-keys \
-d '{"name": "Production API", "scopes": ["admin"]}'
# Staging key (read-only)
curl -X POST .../api-keys \
-d '{"name": "Staging API", "scopes": ["read"]}'Error Responses
401 Unauthorized
Missing or invalid authentication:
{
"error": "Unauthorized",
"message": "Invalid or expired token"
}403 Forbidden
Valid authentication but insufficient permissions:
{
"error": "API key missing required scope. Required: write. Your scopes: read"
}Example: Complete Auth Flow
// 1. Login to get JWT
const loginResponse = await fetch('https://api.hookbase.app/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
password: 'password'
})
});
const { token, user } = await loginResponse.json();
// 2. Get organizations
const orgsResponse = await fetch('https://api.hookbase.app/api/organizations', {
headers: { 'Authorization': `Bearer ${token}` }
});
const { data: orgs } = await orgsResponse.json();
const orgId = orgs[0].id;
// 3. Create an API key for programmatic use
const keyResponse = await fetch(`https://api.hookbase.app/api/organizations/${orgId}/api-keys`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'My App',
scopes: ['read']
})
});
const { key } = await keyResponse.json();
// 4. Use API key for subsequent requests
const sourcesResponse = await fetch('https://api.hookbase.app/api/sources', {
headers: { 'Authorization': `Bearer ${key}` }
});