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 render

The three rules that make it "just work"

  • Sign at render time, not ahead of time. Every time a crawler (or visitor) requests your page, mint a _fresh_ signed URL with t = now(). The crawler fetches it seconds later → valid → renders → cached. This is why the 5-minute timestamp never bites in normal use.
  • The image is permanent after the first render. The 5-minute window only gates the _first_ render. Once an image is rendered, Cloudflare caches it for a year keyed on the content params — later requests with any (even expired) 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).
  • The one failure mode. If an image was _never_ rendered and its first fetch comes more than 5 minutes after signing, FastOG returns 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

  • Go to FastOG Dashboard → API Keys
  • Click "Create New API Key"
  • Give it a name (e.g., "Production Website")
  • Copy both values (shown only once!):
  • - API Key: sk_... (32 chars after prefix)

    - HMAC Secret: 64-character hex string

  • Store them in your environment:
  • env
    # 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/og

    2. Implement URL Signing Function

    For complete tutorials with multiple frameworks and patterns, see:

    Node.js / Next.js

    javascript
    // 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)

    php
    // 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)

    python
    # 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

    tsx
    // 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

    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

    django
    {# 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

    bash
    # 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)
    Timestamp expired:
    • Generate timestamp immediately before making request
    • Maximum drift: 5 minutes
    • Use NTP to sync server clock
    API key not found:
    • 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

    bash
    # 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

    javascript
    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

  • Check secret matches - Copy/paste the exact same secret to both servers
  • Check timestamp - Server clocks must be within 5 minutes (use NTP)
  • Check path - Must be exactly /api/v1/og (no query string)
  • Check format - Header must be 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

  • Implement signing in your website backend
  • Test with the FastOG API
  • Monitor logs for failed signature attempts
  • Set up alerting for repeated failures
  • 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