API Keys Documentation
Table of Contents
- Overview
- Prerequisites
- Getting Started
- Authentication Methods
- API Endpoints
- Code Examples
- Security Best Practices
- Error Handling
- Migration Guide
- FAQ
Overview
API keys provide a secure and convenient way to authenticate your applications with the Hooklistener service. Unlike JWT tokens that require periodic renewal, API keys offer long-lived authentication that's perfect for server-to-server communication, CI/CD pipelines, and automated systems.
Key Benefits
- Long-lived Authentication: No need to refresh tokens periodically
- Simplified Integration: Single key for authentication
- Granular Control: Create multiple keys for different applications
- Usage Tracking: Monitor when each key was last used
- Instant Revocation: Immediately revoke compromised keys
- Secure Storage: Keys are hashed using SHA-256 before storage
How It Works
- API keys are generated with the format:
hklst_<random_string>
- Keys are hashed before storage - we never store the plain text key
- Each key is associated with your organization
- Keys can be used in place of JWT tokens for API authentication
Prerequisites
Plan Requirements
API keys are available exclusively on the Team plan. If you're on a different plan, you'll need to upgrade to access this feature.
Checking Your Plan
You can verify your current plan through:
- The organization settings page in the dashboard
- Attempting to create an API key - you'll receive an error if your plan doesn't support it
Getting Started
Step 1: Create Your First API Key
To create an API key:
- Navigate to https://app.hooklistener.com/organization/settings/api-keys
- Click on "Create New API Key"
- Enter a descriptive name for your key (e.g., "Production Server", "CI/CD Pipeline", "Development")
- Click "Create"
- Copy the API key immediately - it won't be shown again!
⚠️ Important: This is the only time you'll see the full API key. Store it securely immediately!
Step 2: Use Your API Key
Once created, use your API key in requests instead of a JWT token:
curl -X GET https://api.hooklistener.com/api/v1/sources \
-H "Authorization: Bearer <your-api-key>"
Authentication Methods
API keys support two authentication methods for maximum flexibility:
Method 1: Authorization Header (Recommended)
Authorization: Bearer hklst_your_api_key_here
Example:
curl -X GET https://api.hooklistener.com/api/v1/sources \
-H "Authorization: Bearer <your-api-key>"
Method 2: X-API-Key Header
X-API-Key: hklst_your_api_key_here
Example:
curl -X GET https://api.hooklistener.com/api/v1/sources \
-H "X-API-Key: <your-api-key>"
Authentication Priority
The system checks for API keys in this order:
- Authorization header with
Bearer hklst_
prefix - X-API-Key header with
hklst_
prefix - Falls back to JWT authentication if no API key is found
API Endpoints
Create API Key
Endpoint: POST /api/v1/organizations/{org_id}/api-keys
Headers:
Authorization: Bearer YOUR_JWT_TOKEN
(requires JWT for this operation)Content-Type: application/json
Request Body:
{
"name": "My Application"
}
Response (201 Created):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Application",
"key": "hklst_AbC123XyZ789...",
"created_at": "2024-01-15T10:30:00Z",
"last_used_at": null,
"revoked_at": null
}
Error Response (400 Bad Request):
{
"error": "name is required"
}
Error Response (403 Forbidden):
{
"error": "Your plan does not have access to this feature"
}
List API Keys
Endpoint: GET /api/v1/organizations/{org_id}/api-keys
Headers:
Authorization: Bearer YOUR_JWT_TOKEN
Response (200 OK):
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Production Server",
"created_at": "2024-01-15T10:30:00Z",
"last_used_at": "2024-01-20T15:45:00Z",
"revoked_at": null
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Development Environment",
"created_at": "2024-01-10T08:20:00Z",
"last_used_at": "2024-01-19T12:30:00Z",
"revoked_at": null
}
]
Note: The actual key values are never returned after creation for security reasons.
Revoke API Key
Endpoint: DELETE /api/v1/organizations/{org_id}/api-keys/{key_id}
Headers:
Authorization: Bearer YOUR_JWT_TOKEN
Response (200 OK):
{
"message": "API key revoked successfully"
}
Error Response (404 Not Found):
{
"error": "API key not found"
}
Code Examples
JavaScript/Node.js
// Using fetch (Node.js 18+ or with node-fetch)
const API_KEY = '<your-api-key>';
const BASE_URL = 'https://api.hooklistener.com';
// Create a new source
async function createSource(sourceData) {
const response = await fetch(`${BASE_URL}/api/v1/sources`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(sourceData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
// List all sources
async function listSources() {
const response = await fetch(`${BASE_URL}/api/v1/sources`, {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
// Usage
(async () => {
try {
const sources = await listSources();
console.log('Sources:', sources);
} catch (error) {
console.error('Error:', error);
}
})();
Python
import requests
import json
class HooklistenerClient:
def __init__(self, api_key):
self.api_key = api_key
self.base_url = 'https://api.hooklistener.com'
self.headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
def create_source(self, source_data):
"""Create a new source"""
response = requests.post(
f'{self.base_url}/api/v1/sources',
headers=self.headers,
json=source_data
)
response.raise_for_status()
return response.json()
def list_sources(self):
"""List all sources"""
response = requests.get(
f'{self.base_url}/api/v1/sources',
headers={'Authorization': f'Bearer {self.api_key}'}
)
response.raise_for_status()
return response.json()
def delete_source(self, source_id):
"""Delete a source"""
response = requests.delete(
f'{self.base_url}/api/v1/sources/{source_id}',
headers={'Authorization': f'Bearer {self.api_key}'}
)
response.raise_for_status()
return response.json()
# Usage
client = HooklistenerClient('<your-api-key>')
try:
# List sources
sources = client.list_sources()
print(f"Found {len(sources)} sources")
# Create a new source
new_source = client.create_source({
'name': 'My Source',
'type': 'webhook'
})
print(f"Created source: {new_source['id']}")
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e}")
except Exception as e:
print(f"Error: {e}")
Ruby
require 'net/http'
require 'json'
require 'uri'
class HooklistenerClient
def initialize(api_key)
@api_key = api_key
@base_url = 'https://api.hooklistener.com'
end
def create_source(source_data)
uri = URI("#{@base_url}/api/v1/sources")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{@api_key}"
request['Content-Type'] = 'application/json'
request.body = source_data.to_json
response = http.request(request)
JSON.parse(response.body)
end
def list_sources
uri = URI("#{@base_url}/api/v1/sources")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{@api_key}"
response = http.request(request)
JSON.parse(response.body)
end
end
# Usage
client = HooklistenerClient.new('<your-api-key>')
begin
sources = client.list_sources
puts "Found #{sources.length} sources"
rescue => e
puts "Error: #{e.message}"
end
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
type HooklistenerClient struct {
APIKey string
BaseURL string
}
func NewHooklistenerClient(apiKey string) *HooklistenerClient {
return &HooklistenerClient{
APIKey: apiKey,
BaseURL: "https://api.hooklistener.com",
}
}
func (c *HooklistenerClient) ListSources() ([]map[string]interface{}, error) {
req, err := http.NewRequest("GET", c.BaseURL+"/api/v1/sources", nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+c.APIKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var sources []map[string]interface{}
err = json.Unmarshal(body, &sources)
return sources, err
}
func (c *HooklistenerClient) CreateSource(sourceData map[string]interface{}) (map[string]interface{}, error) {
jsonData, err := json.Marshal(sourceData)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", c.BaseURL+"/api/v1/sources", bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+c.APIKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var result map[string]interface{}
err = json.Unmarshal(body, &result)
return result, err
}
func main() {
client := NewHooklistenerClient("<your-api-key>")
// List sources
sources, err := client.ListSources()
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Found %d sources\n", len(sources))
}
cURL Examples
# List all sources
curl -X GET https://api.hooklistener.com/api/v1/sources \
-H "Authorization: Bearer <your-api-key>"
# Create a new source
curl -X POST https://api.hooklistener.com/api/v1/sources \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "My Source",
"type": "webhook"
}'
# Get a specific source
curl -X GET https://api.hooklistener.com/api/v1/sources/{source_id} \
-H "Authorization: Bearer <your-api-key>"
# Update a source
curl -X PUT https://api.hooklistener.com/api/v1/sources/{source_id} \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Source Name"
}'
# Delete a source
curl -X DELETE https://api.hooklistener.com/api/v1/sources/{source_id} \
-H "Authorization: Bearer <your-api-key>"
Security Best Practices
1. Secure Storage
DO:
- Store API keys in environment variables
- Use secret management services (AWS Secrets Manager, HashiCorp Vault, etc.)
- Encrypt keys at rest
- Use
.env
files for local development (never commit them)
DON'T:
- Hard-code keys in source code
- Commit keys to version control
- Share keys via email or chat
- Store keys in client-side code
2. Key Rotation
Regularly rotate your API keys to minimize the impact of potential compromises:
- Create a new API key
- Update your applications to use the new key
- Monitor the old key's usage to ensure migration is complete
- Revoke the old key once it's no longer in use
Recommended rotation schedule:
- Production keys: Every 90 days
- Development keys: Every 30 days
- After any security incident: Immediately
3. Principle of Least Privilege
- Create separate API keys for different applications or environments
- Name keys descriptively (e.g., "Production Server", "CI/CD Pipeline", "Development")
- Revoke unused keys immediately
4. Monitoring and Auditing
- Regularly review the
last_used_at
timestamp for each key - Investigate keys that haven't been used recently
- Set up alerts for unusual API activity
- Keep logs of key creation and revocation
5. Environment-Specific Keys
Never use the same API key across different environments:
# .env.production
HOOKLISTENER_API_KEY=hklst_production_key_here
# .env.development
HOOKLISTENER_API_KEY=hklst_development_key_here
# .env.test
HOOKLISTENER_API_KEY=hklst_test_key_here
6. Secure Transmission
- Always use HTTPS when making API calls
- Never send API keys over unencrypted connections
- Avoid logging API keys in application logs
Error Handling
Common Error Responses
401 Unauthorized
{
"error": "Invalid or missing API key"
}
Causes:
- Invalid API key format
- Revoked API key
- Missing authentication headers
- Malformed authorization header
Solution:
- Verify the API key is correct
- Check if the key has been revoked
- Ensure proper header format
403 Forbidden
{
"error": "Your plan does not have access to this feature"
}
Cause: Your organization is not on the Team plan
Solution: Upgrade to the Team plan to access API keys
404 Not Found
{
"error": "API key not found"
}
Cause: Attempting to revoke a non-existent or already revoked key
Solution: Verify the API key ID is correct
500 Internal Server Error
{
"error": "Failed to create API key"
}
Cause: Server-side issue
Solution: Retry the request or contact support if the issue persists
Handling Errors in Code
JavaScript Example:
async function makeAPICall(url) {
try {
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
});
if (!response.ok) {
const error = await response.json();
switch (response.status) {
case 401:
console.error('Authentication failed. Check your API key.');
// Potentially trigger re-authentication flow
break;
case 403:
console.error('Plan upgrade required for this feature.');
// Notify user about plan limitations
break;
case 429:
console.error('Rate limit exceeded. Retry after delay.');
// Implement exponential backoff
break;
default:
console.error(`API Error: ${error.error}`);
}
return null;
}
return await response.json();
} catch (error) {
console.error('Network error:', error);
// Handle network failures
}
}
Python Example:
import time
from typing import Optional, Dict, Any
def make_api_call_with_retry(
url: str,
api_key: str,
max_retries: int = 3
) -> Optional[Dict[str, Any]]:
"""Make API call with automatic retry logic"""
for attempt in range(max_retries):
try:
response = requests.get(
url,
headers={'Authorization': f'Bearer {api_key}'}
)
if response.status_code == 200:
return response.json()
elif response.status_code == 401:
print("Invalid API key. Please check your credentials.")
return None
elif response.status_code == 429:
# Rate limited - exponential backoff
wait_time = 2 ** attempt
print(f"Rate limited. Waiting {wait_time} seconds...")
time.sleep(wait_time)
continue
elif response.status_code == 403:
print("Feature not available on your plan.")
return None
else:
print(f"Error {response.status_code}: {response.text}")
except requests.exceptions.RequestException as e:
print(f"Network error on attempt {attempt + 1}: {e}")
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
continue
return None
Migration Guide
Migrating from JWT to API Keys
If you're currently using JWT authentication and want to switch to API keys, follow this guide:
Step 1: Assess Your Current Implementation
Identify all places where you're using JWT tokens:
- Application configurations
- CI/CD pipelines
- Scheduled jobs
- Third-party integrations
Step 2: Create API Keys
Create API keys for each distinct use case:
# Create key for production server
curl -X POST https://api.hooklistener.com/api/v1/organizations/{org_id}/api-keys \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Production Server"}'
# Create key for CI/CD
curl -X POST https://api.hooklistener.com/api/v1/organizations/{org_id}/api-keys \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "GitHub Actions CI/CD"}'
Step 3: Update Your Code
Before (JWT):
// JWT implementation
const jwt = await refreshJWTToken();
const response = await fetch('https://api.hooklistener.com/api/v1/sources', {
headers: {
'Authorization': `Bearer ${jwt}`
}
});
After (API Key):
// API key implementation
const response = await fetch('https://api.hooklistener.com/api/v1/sources', {
headers: {
'Authorization': `Bearer ${process.env.HOOKLISTENER_API_KEY}`
}
});
Step 4: Test in Staging
- Deploy changes to staging environment
- Verify all API calls work correctly
- Monitor for any authentication failures
- Test error handling scenarios
Step 5: Deploy to Production
- Update production environment variables
- Deploy the updated code
- Monitor logs for authentication issues
- Keep JWT implementation as fallback initially
Step 6: Clean Up
Once stable:
- Remove JWT refresh logic
- Delete unused JWT-related code
- Update documentation
- Revoke any temporary API keys used during migration
Supporting Both Authentication Methods
During migration, you might want to support both methods:
class HooklistenerClient {
constructor(config) {
this.apiKey = config.apiKey;
this.jwtToken = config.jwtToken;
this.useApiKey = !!this.apiKey;
}
async makeRequest(url, options = {}) {
const headers = {
...options.headers,
'Authorization': this.useApiKey
? `Bearer ${this.apiKey}`
: `Bearer ${await this.getValidJWT()}`
};
return fetch(url, {
...options,
headers
});
}
async getValidJWT() {
// JWT refresh logic if needed
if (this.jwtExpired()) {
this.jwtToken = await this.refreshJWT();
}
return this.jwtToken;
}
}
FAQ
General Questions
Q: How many API keys can I create? A: There's no hard limit on the number of API keys you can create. However, we recommend keeping the number manageable and creating keys only as needed.
Q: Can I restrict API key permissions? A: Currently, API keys have the same permissions as the organization they're associated with. Role-based access control for API keys may be added in future updates.
Q: Do API keys expire? A: No, API keys don't expire automatically. They remain valid until you explicitly revoke them. This is one of the main advantages over JWT tokens.
Q: Can I see my API key again after creation? A: No, for security reasons, the full API key is only shown once during creation. If you lose it, you'll need to create a new one and revoke the old one.
Security Questions
Q: What happens if my API key is compromised? A: Immediately revoke the compromised key through the API or dashboard, then create a new key and update your applications.
Q: Are API keys encrypted in your database? A: Yes, API keys are hashed using SHA-256 before storage. We never store the plain text version of your keys.
Q: Can I use the same API key for multiple applications? A: While technically possible, we strongly recommend using separate keys for different applications for better security and auditing.
Technical Questions
Q: What's the difference between Bearer and X-API-Key headers? A: Both work identically for API keys. The Bearer format follows OAuth 2.0 conventions, while X-API-Key is a common alternative. Use whichever fits your application better.
Q: How do I know which authentication method was used for a request?
A: When authenticated via API key, the auth_method
in the request context will be set to :api_key
. For JWT, it will be :jwt
.
Q: Can I use API keys with websocket connections? A: Currently, API keys are designed for REST API authentication. Websocket authentication may require JWT tokens depending on your implementation.
Troubleshooting Questions
Q: Why am I getting "Your plan does not have access to this feature"? A: API keys are only available on the Team plan. Check your organization's current plan and upgrade if necessary.
Q: My API key was working but suddenly stopped. What happened? A: Check if:
- The key was accidentally revoked
- Your organization's plan was downgraded
- There was a change in the API key format or headers
Q: Can I test API keys in development? A: Yes, create separate API keys for your development environment. Never use production keys in development.
Support
If you encounter any issues or have questions not covered in this documentation:
- Check the API Status Page: https://status.hooklistener.com
- Review Error Logs: Check your application logs for detailed error messages
- Contact Support: [email protected]
- Community Slack: https://join.slack.com/t/hooklistenercommunity/shared_invite/zt-3208b4jyd-2wy2asb2RppM0D52ASBrlw
Changelog
Version 1.0 (Current)
- Initial API keys implementation
- Support for Bearer and X-API-Key headers
- Key creation, listing, and revocation
- Usage tracking with last_used_at timestamp
- SHA-256 hashing for secure storage
- Team plan requirement
Planned Features
- Role-based access control for API keys
- Key expiration policies
- Rate limiting per key
- Detailed usage analytics
- IP allowlisting for keys
- Webhook notifications for key events
Last Updated: January 2024 Documentation Version: 1.0