Skip to main content

API Keys Documentation

Table of Contents

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

  1. API keys are generated with the format: hklst_<random_string>
  2. Keys are hashed before storage - we never store the plain text key
  3. Each key is associated with your organization
  4. 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:

  1. The organization settings page in the dashboard
  2. 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:

  1. Navigate to https://app.hooklistener.com/organization/settings/api-keys
  2. Click on "Create New API Key"
  3. Enter a descriptive name for your key (e.g., "Production Server", "CI/CD Pipeline", "Development")
  4. Click "Create"
  5. 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:

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:

  1. Authorization header with Bearer hklst_ prefix
  2. X-API-Key header with hklst_ prefix
  3. 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:

  1. Create a new API key
  2. Update your applications to use the new key
  3. Monitor the old key's usage to ensure migration is complete
  4. 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

  1. Deploy changes to staging environment
  2. Verify all API calls work correctly
  3. Monitor for any authentication failures
  4. Test error handling scenarios

Step 5: Deploy to Production

  1. Update production environment variables
  2. Deploy the updated code
  3. Monitor logs for authentication issues
  4. Keep JWT implementation as fallback initially

Step 6: Clean Up

Once stable:

  1. Remove JWT refresh logic
  2. Delete unused JWT-related code
  3. Update documentation
  4. 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:

  1. The key was accidentally revoked
  2. Your organization's plan was downgraded
  3. 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:

  1. Check the API Status Page: https://status.hooklistener.com
  2. Review Error Logs: Check your application logs for detailed error messages
  3. Contact Support: [email protected]
  4. 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