HMAC Validation
HMAC validation is how FastOG verifies that every credit-based OG image request came from your own server — signed URLs keep your credits safe. Sign up free for 100 starter credits (create your API key and HMAC secret in the dashboard); free renders carry a FastOG watermark until your first payment. This page covers the server-side rules behind dynamic Open Graph images, from the 5-minute timestamp window to constant-time comparison.
Overview
HMAC-SHA256 signature validation has been added to the /api/v1/og endpoint to prevent unauthorized use of API credits. This ensures only requests from the legitimate website can generate OG images.
Architecture
Two-layer authentication:HmacSignature runs BEFORE ApiKeyAuth to fail fast on invalid signatures.
Query parameters, not headers: Since OG images are fetched by social network crawlers (LinkedIn, Twitter, Facebook) that don't send custom headers, the signature is passed in URL query parameters:
GET /api/v1/og?title=Hello&key=sk_quick_123abc&s=<hmac>&t=<timestamp>Implementation
Files Created/Modified
app/Http/Middleware/HmacSignature.php- New middleware validating HMAC signatures (query params)routes/api.php- Added HMAC middleware to OG routebootstrap/app.php- Registeredhmacmiddleware aliasapp/Models/ApiKey.php- Addedhmac_secretfield (per-user secrets)app/Http/Controllers/DashboardController.php- Generates HMAC secret on API key creationdatabase/migrations/2026_08_03_214931_add_hmac_secret_to_api_keys_table.php- Migration for hmac_secret columnresources/js/Pages/Dashboard/ApiKeys.svelte- Shows HMAC secret to user (once)docs/hmac-signature-guide.md- Client implementation guidetests/Feature/HmacSignatureTest.php- 8 passing tests
Signature Algorithm
timestamp = current Unix timestamp (seconds)
method = HTTP method (e.g., "GET")
path = request path (e.g., "/api/v1/og") - NO query string
apiKey = the full API key (e.g., "sk_quick_123abc")
message = method + path + timestamp + apiKey
signature = HMAC-SHA256(message, hmac_secret)
Query parameters:
key=<apiKey>
s=<signature>
t=<timestamp>Security Features
- 5-minute timestamp window - Prevents replay attacks
- Constant-time comparison (
hash_equals()) - Prevents timing attacks - API key in message - Prevents signature reuse across different API keys
- Path-specific - Signature valid only for exact path
- Method-specific - GET signature won't work for POST
- Per-user secrets - Each API key has its own HMAC secret, can be revoked independently
Configuration
Generate Secret (Automatic)
The HMAC secret is automatically generated when creating an API key in the dashboard. It's shown once alongside the API key, then never shown again.
Manual generation (if needed):openssl rand -hex 32Database Schema
ALTER TABLE api_keys ADD COLUMN hmac_secret VARCHAR(64)
COMMENT 'HMAC-SHA256 secret for signing OG image URLs (hex, 64 chars)';Environment Variables
No global environment variable needed - secrets are stored per-user in the api_keys table.
Usage Example (Website Backend)
// Node.js example - generate signed OG image URL
const crypto = require("crypto");
function generateOgImageUrl(baseUrl, params, apiKey, hmacSecret) {
const timestamp = Math.floor(Date.now() / 1000);
const url = new URL(baseUrl);
// Add query params
url.searchParams.set("title", params.title);
url.searchParams.set("key", apiKey);
url.searchParams.set("t", timestamp);
// Compute signature: method + path + timestamp + apiKey
const path = url.pathname;
const message = `GET${path}${timestamp}${apiKey}`;
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(message)
.digest("hex");
url.searchParams.set("s", signature);
return url.toString();
}
// Usage in meta tag
const ogUrl = generateOgImageUrl(
"https://fastog.com/api/v1/og",
{ title: "Como criar um SaaS" },
"sk_quick_123abc",
process.env.HMAC_SECRET,
);
// <meta property="og:image" content="{ogUrl}" />const message = method + path + timestamp + apiKey;
const signature = crypto.createHmac('sha256', hmacSecret)
.update(message)
.digest('hex');
return {
key: apiKey,
s: signature,
t: timestamp.toString(),
};
}
// Usage in meta tag:
const params = signRequest('GET', '/api/v1/og', 'sk_quick_123abc', process.env.HMAC_SECRET);
const ogUrl = https://fastog.com/api/v1/og?title=Hello&key=${params.key}&s=${params.s}&t=${params.t};
//
``
## Error Responses
| Status | Code | Description |
|--------|------|-------------|
| 401 | `missing_signature` | Missing `s`, `t`, or `key` query parameter |
| 401 | `invalid_signature_format` | Signature not 64 hex characters |
| 401 | `expired_timestamp` | Timestamp > 5 minutes old |
| 401 | `invalid_signature` | Signature doesn't match expected value |
| 401 | `missing_api_key` | Missing API key |
## Testing
Run tests:
All 8 tests pass:
- ✅ Request without signature is rejected
- ✅ Request with missing key param is rejected
- ✅ Request with expired timestamp is rejected
- ✅ Request with invalid signature is rejected
- ✅ Request with valid signature passes HMAC check
- ✅ Signature is path-specific
- ✅ Signature is method-specific
- ✅ Different API keys have different secrets
<h2>Migration Notes</h2>
<h3>For Existing API Clients</h3>
If you have existing API clients (other than the website), they need to be updated to:
1. Generate HMAC signatures
2. Include key, s, and t query parameters
<strong>OR</strong> create a separate endpoint without HMAC for trusted internal services.
<h3>Rollback</h3>
To disable HMAC validation temporarily:
1. Remove HmacSignature::class from routes/api.php`
Performance Impact
- Negligible - HMAC-SHA256 computation is ~0.1ms per request
- No additional network calls - Validation is local
- Fails fast - Invalid signatures rejected before API key lookup
Security Considerations
References
- HMAC Signature Guide for Clients
- Quick Start Guide
- OWASP: Cryptographic Storage Cheat Sheet
- PHP: hash_hmac()
Full API Reference
Related
Try it free — no signup required
Preview and test dynamic OG images in seconds.
Open the Free OG Image Tester