API Key Authentication Best Practices for Document APIs

Published March 28, 2026 · 8 min read · Doxnex Engineering

API keys are the most common authentication mechanism for document generation APIs. They are simple to implement, easy for developers to use, and work well across every programming language and HTTP client. However, their simplicity hides real security risks. A leaked or poorly managed API key can expose your entire document pipeline, leaking sensitive templates, customer data, and billing credentials.

This guide covers four pillars of API key security that every document API should implement: SHA-256 hashing for storage, automated key rotation, intelligent rate limiting, and HMAC-based webhook verification.

1. SHA-256 Hashing: Never Store Keys in Plain Text

The cardinal rule of API key management is to never store the raw key in your database. Treat API keys with the same respect you give passwords. When a user generates a new key, show it to them exactly once, then store only a SHA-256 hash.

import hashlib

def hash_api_key(raw_key: str) -> str:
    return hashlib.sha256(raw_key.encode('utf-8')).hexdigest()

def verify_api_key(raw_key: str, stored_hash: str) -> bool:
    return hash_api_key(raw_key) == stored_hash

SHA-256 is preferred over bcrypt for API keys because keys are long, random strings (not human-chosen passwords), so brute-force attacks are already impractical. The speed of SHA-256 means authentication adds negligible latency to each API call.

Key Prefix Strategy

Store a non-secret prefix (e.g., the first 8 characters) alongside the hash. This lets you look up the key in the database without scanning every row. At Doxnex, keys follow the format dxn_live_xxxxxxxxxxxxxxxxxxxxxxxx where the prefix dxn_live_ identifies the key type and environment.

2. Key Rotation: Automate the Lifecycle

Keys should not live forever. A 90-day rotation policy balances security with operational convenience. The most important feature to build is a grace period: when a user generates a new key, the old key continues to work for a configurable window (typically 24 to 72 hours).

Implementation Pattern

Use a database table with columns for key_hash, prefix, created_at, expires_at, revoked_at, and account_id. On each request, look up the key by prefix, verify the hash, and check that revoked_at is null and expires_at is in the future.

3. Rate Limiting: Protect Your Infrastructure

Document generation is CPU and memory intensive. A single PDF render can consume 100-500 MB of memory depending on template complexity. Without rate limiting, a single runaway client can bring down your entire cluster.

Sliding Window Algorithm

The sliding window counter, backed by Redis, offers the best balance of accuracy and performance. Unlike fixed windows, it prevents burst attacks at window boundaries.

-- Redis sliding window rate limiter
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)

if count < limit then
    redis.call('ZADD', key, now, now .. math.random())
    redis.call('EXPIRE', key, window)
    return 1
end
return 0

Tiered Limits

Different subscription plans should have different limits. A practical starting point for document APIs:

Always return rate limit headers in every response: X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. When a client is throttled, return HTTP 429 with a Retry-After header.

4. HMAC Webhook Verification

When your document API sends webhooks (e.g., "document generation complete" or "export ready for download"), the receiver must verify that the webhook genuinely came from your service. HMAC-SHA256 is the industry standard for this.

import hmac
import hashlib

def sign_webhook(payload: bytes, secret: str) -> str:
    return hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = sign_webhook(payload, secret)
    return hmac.compare_digest(expected, signature)

Key implementation details:

5. Additional Security Layers

IP Allowlisting

For enterprise customers, allow restricting API key usage to specific IP ranges. This adds defense-in-depth: even if a key leaks, it cannot be used from unauthorized networks.

Scope Restrictions

Not every key needs full access. Implement scoped keys that can only perform specific operations. For a document API, useful scopes include documents:generate, templates:read, templates:write, and billing:read.

Audit Logging

Log every API key usage event with the timestamp, endpoint, response status, and client IP. This data is invaluable for detecting anomalies (sudden spike in requests, access from unusual geographies) and for compliance requirements.

Putting It All Together

A secure API key system is not a single feature but a layered architecture. Hash keys at rest, enforce rotation policies, protect infrastructure with rate limits, and verify webhooks with HMAC signatures. Each layer compensates for potential failures in the others.

At Doxnex, we implement all of these practices in our document generation API. Every key is SHA-256 hashed, rotation is enforced and automated, rate limits scale with your plan, and all webhooks are HMAC-signed with per-endpoint secrets. Get started with a free account and see these practices in action.

Frequently Asked Questions

Should I store API keys in plain text?

Never store API keys in plain text. Always store a SHA-256 hash of the key in your database. When a request arrives, hash the provided key and compare it against the stored hash. This way, even if your database is compromised, the actual keys remain safe.

How often should I rotate API keys?

Best practice is to rotate API keys every 90 days. Support a grace period where both old and new keys work simultaneously so consumers can migrate without downtime. Automated rotation with advance notification is ideal for production systems.

What rate limits should I set for a document generation API?

For document generation APIs, a reasonable default is 60 requests per minute for standard tiers and 300 per minute for premium tiers. Use a sliding window algorithm backed by Redis for accurate counting. Always return X-RateLimit headers so consumers can self-throttle.

How does HMAC webhook verification work?

HMAC webhook verification uses a shared secret between the sender and receiver. The sender computes an HMAC-SHA256 signature of the request body using the secret and includes it in a header. The receiver recomputes the signature and compares it using a constant-time comparison to prevent timing attacks.