Quick Start — Generate Your First OG Image
This FastOG quick start gets your first dynamic Open Graph image live in minutes. Signing up is free — you get 100 credits to start. You create your API key and HMAC secret on demand in the dashboard. Renders carry a small FastOG watermark until your first payment. Try the Free OG Image Tester to see the pipeline in action, then sign your own URLs with the steps below.
How it works — Architecture
FastOG is built for on-demand OG images: your website signs a URL at page-render time, the crawler fetches it, FastOG renders the PNG once, and Cloudflare caches it so every later fetch is instant and free.
sequenceDiagram
participant You as You share a link
participant Crawler as LinkedIn/X/Discord crawler
participant Site as Your website (Laravel/Next.js/…)
participant FastOG as FastOG API
participant Edge as Cloudflare edge cache
You->>Crawler: share https://yoursite.com/blog/my-post
Crawler->>Site: GET page (no JavaScript)
Site->>Site: sign the og:image URL NOW (title, subtitle baked in, t = now)
Site-->>Crawler: HTML with <meta property="og:image" content="https://fastog.com/api/v1/og?...&s=HMAC&t=now">
Crawler->>Edge: fetch og:image URL
Edge->>FastOG: cache MISS (first time)
FastOG->>FastOG: verify HMAC (t within 5 min)
FastOG-->>Edge: render PNG (costs 1 credit) + cache it (1 year)
Edge-->>Crawler: PNG
Crawler-->>You: image shown in the post preview
Note over Edge, Crawler: Every later fetch (even with an expired s/t) → cache HIT → free, instant, no renderThe three rules that make it "just work"
t = now(). The crawler fetches it seconds later → valid → renders → cached. This is why the 5-minute timestamp never bites in normal use.s/t are served from the edge and never hit FastOG (0 credits). A bogus signature still returns the cached PNG (cf-cache-status: HIT).expired_timestamp (401) — and since 401s aren't cached, it stays broken until a freshly signed URL is used. Signing per render avoids this entirely.1. Get Your API Key and HMAC Secret
- API Key: sk_... (32 chars after prefix)
- HMAC Secret: 64-character hex string
# Your website backend .env
FASTOG_API_KEY=sk_your_full_api_key
FASTOG_HMAC_SECRET=a1b2c3d4e5f6... # 64 chars
FASTOG_API_URL=https://fastog.com/api/v1/og2. Implement URL Signing Function
For complete tutorials with multiple frameworks and patterns, see:
- JavaScript/Node.js Tutorial - Next.js App/Pages Router, Express, vanilla Node.js
- PHP/Laravel Tutorial - Coming soon
- Python Tutorial - Coming soon
Node.js / Next.js
// lib/fastog-signer.js
import crypto from "crypto";
export function generateOgImageUrl(params) {
const baseUrl = process.env.FASTOG_API_URL;
const apiKey = process.env.FASTOG_API_KEY;
const hmacSecret = process.env.FASTOG_HMAC_SECRET;
const timestamp = Math.floor(Date.now() / 1000);
// Build URL with params
const url = new URL(baseUrl);
for (const [key, value] of Object.entries(params)) {
url.searchParams.set(key, value);
}
// Compute signature: GET + path + timestamp + apiKey
const path = url.pathname;
const message = `GET${path}${timestamp}${apiKey}`;
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(message)
.digest("hex");
// Add auth params
url.searchParams.set("key", apiKey);
url.searchParams.set("s", signature);
url.searchParams.set("t", timestamp);
return url.toString();
}PHP (Laravel)
// app/Services/QuickogSigner.php
function generateOgImageUrl(array $params): string
{
$baseUrl = config('services.fastog.api_url', 'https://fastog.com/api/v1/og');
$apiKey =
$hmacSecret = config('services.fastog.hmac_secret');
$timestamp = time();
// Compute signature
$message = 'GET/api/v1/og' . $timestamp . $apiKey;
$signature = hash_hmac('sha256', $message, $hmacSecret);
// Build URL
$query = array_merge($params, [
'key' => $apiKey,
's' => $signature,
't' => $timestamp,
]);
return $baseUrl . '?' . http_build_query($query);
}Python (Django/FastAPI)
# utils/fastog.py
import hmac
import hashlib
import time
from urllib.parse import urlencode, urlparse
def generate_og_image_url(params: dict) -> str:
base_url = os.environ['FASTOG_API_URL']
api_key = os.environ['FASTOG_API_KEY']
hmac_secret = os.environ['FASTOG_HMAC_SECRET']
timestamp = int(time.time())
# Compute signature
message = f"GET/api/v1/og{timestamp}{api_key}"
signature = hmac.new(
hmac_secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
# Build URL
query_params = {**params, 'key': api_key, 's': signature, 't': timestamp}
return f"{base_url}?{urlencode(query_params)}"3. Use in Your Blog/Website
Next.js App Router
// app/blog/[slug]/page.tsx
import { generateOgImageUrl } from "@/lib/fastog-signer";
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
const ogImageUrl = generateOgImageUrl({
template: "blog",
title: post.title,
subtitle: post.excerpt,
author: post.author.name,
});
return {
title: post.title,
description: post.excerpt,
openGraph: {
images: [ogImageUrl],
},
};
}Laravel Blade
{{-- resources/views/blog/show.blade.php --}}
@php
$ogImageUrl = generateOgImageUrl([
'template' => 'blog',
'title' => $post->title,
'subtitle' => $post->excerpt,
'author' => $post->author->name,
]);
@endphp
<head>
<meta property="og:image" content="{{ $ogImageUrl }}" />
<meta property="og:title" content="{{ $post->title }}" />
<meta property="og:description" content="{{ $post->excerpt }}" />
</head>Django Template
{# blog/post_detail.html #}
{% load fastog_tags %}
<head>
<meta property="og:image" content="{% generate_og_image_url post %}" />
<meta property="og:title" content="{{ post.title }}" />
<meta property="og:description" content="{{ post.excerpt }}" />
</head>4. Test Your Implementation
# Generate a test URL
node -e "
const { generateOgImageUrl } = require('./lib/fastog-signer');
console.log(generateOgImageUrl({ title: 'Test', template: 'default' }));
"
# Test with curl
curl "https://fastog.com/api/v1/og?title=Test&key=sk_quick_123abc&s=<sig>&t=<ts>"Expected:
- ✅ Valid signature → Returns OG image
- ❌ Expired timestamp →
{"code": "expired_timestamp"} - ❌ Invalid signature →
{"code": "invalid_signature"}
5. Security Checklist
- [ ] HMAC secret stored in environment variables (not in code)
- [ ] Using HTTPS for all requests
- [ ] Secret never exposed in client-side JavaScript
- [ ] Server clock synchronized (NTP)
- [ ] Monitoring failed signature attempts
Troubleshooting
Signature invalid:- Verify you're using the full API key (e.g.
sk_quick_123abc) - Check timestamp is in seconds, not milliseconds
- Ensure message format:
GET/api/v1/og{timestamp}{apiKey}(no separators)
- Generate timestamp immediately before making request
- Maximum drift: 5 minutes
- Use NTP to sync server clock
- Verify API key exists in dashboard
- Check you're using the full API key (e.g.,
sk_quick_123abc) - Ensure key hasn't been revoked
5. Test It
# From your website backend server
curl -v "http://localhost:3000/api/og?template=default&title=Test" \
-H "Authorization: Bearer sk_your_api_key"Expected: 200 OK with PNG image data.
6. Handle Errors
if (response.status === 401) {
const error = await response.json();
switch (error.code) {
case "missing_signature":
// Add X-Signature-256 and X-Timestamp headers
break;
case "invalid_signature":
// Check your secret matches on both servers
break;
case "expired_timestamp":
// Ensure server clocks are synchronized(NTP)
break;
case "missing_api_key":
// Add Authorization: Bearer sk_... header
break;
}
}Troubleshooting
"Invalid signature" errors
/api/v1/og (no query string)sha256=<64-char-hex>"Expired timestamp" errors
- Sync server clocks with NTP
- Check timezone settings
"Missing signature" errors
- Ensure headers are sent with exact names:
X-Signature-256,X-Timestamp - Check case-sensitivity (headers are case-insensitive in HTTP but be consistent)
Security Checklist
- ✅ Secret stored in environment variables (not in code)
- ✅ Signing happens on backend (never expose secret to frontend)
- ✅ HTTPS enabled in production
- ✅ API key stored separately from HMAC secret
- ✅ Monitoring enabled for failed signature attempts
Next Steps
For full documentation, see the HMAC Signature Guide.
Full API Reference
Related
Try it free — no signup required
Preview and test dynamic OG images in seconds.
Open the Free OG Image Tester