Onderwerp

Snelstart — Genereer je eerste OG-afbeelding

Met deze FastOG-snelstart zet je binnen enkele minuten je eerste dynamische Open Graph-afbeelding live. Aanmelden is gratis — kies daarna een Dynamic API-plan voor je renderquotum. Je maakt je API-sleutel en HMAC-geheim op aanvraag aan in het dashboard. Elke render is schoon — geen watermerk op welk plan dan ook, gratis tester inbegrepen. Probeer de gratis OG-afbeeldingstester om de pipeline in actie te zien en onderteken daarna je eigen URL's met de onderstaande stappen.

Hoe het werkt — Architectuur

FastOG is gebouwd voor on-demand OG-afbeeldingen: jouw website ondertekent een URL op het moment van pagina-rendering, de crawler haalt die op, FastOG rendert de PNG eenmalig, en Cloudflare cached die zodat elk later verzoek direct en gratis is.

sequenceDiagram
    participant You as You share a link
    participant Crawler as Social crawler
    participant Site as Your website
    participant FastOG as FastOG API
    participant Edge as Edge cache

    You->>Crawler: share a link to your post
    Crawler->>Site: GET the page without JavaScript
    Site->>Site: sign the og:image URL at render time
    Site-->>Crawler: HTML with signed og:image URL
    Crawler->>Edge: fetch the og:image URL
    Edge->>FastOG: cache miss, first time
    FastOG->>FastOG: verify the HMAC covers the exact params
    FastOG-->>Edge: render PNG, counts 1 toward monthly quota, cached for 1 year
    Edge-->>Crawler: PNG
    Crawler-->>You: image shown in the post preview
    Note over Edge, Crawler: identical params and URL mean a cache hit with no render

De drie regels waardoor het "gewoon werkt"

  1. Onderteken de inhoud, niet de klok. De handtekening dekt elke parameter (titel, template, …). Identieke parameters produceren altijd de identieke URL — dat is wat de edge-cache laat werken: zelfde inhoud → zelfde URL → cache-hit voor altijd.
  2. De afbeelding is permanent na de eerste render. De handtekening bewaakt alleen de eerste render. Zodra een afbeelding is gerenderd, bewaart FastOG's edge die een jaar lang, gesleuteld op de inhoudsparameters — latere verzoeken met dezelfde parameters worden vanaf de edge bediend en raken de renderer nooit (tellen niet mee tegen je quotum). Een ongeldige handtekening op gecachte inhoud retourneert nog steeds de gecachte PNG (het antwoord meldt welke laag heeft geantwoord: X-FastOG-Worker-Cache: HIT of R2); alleen een cache-miss krijgt een 401.
  3. De enige faalmodus. Een gemanipuleerde URL (iemand wijzigt title/template) krijgt 401 bij FastOG bij een cache-miss — die kan dus nooit worden gebruikt om je quotum te verbruiken. Legitieme URLs verlopen nooit: ze blijven geldig totdat je het HMAC-geheim van de sleutel roteert.

1. Haal je API-sleutel en HMAC-geheim op

  1. Ga naar FastOG-dashboard → API-sleutels

  2. Klik op "Nieuwe API-sleutel aanmaken"

  3. Geef het een naam (bijv. "Productiewebsite")

  4. Kopieer beide waarden (worden slechts één keer getoond!):

    • API-sleutel: sk_... (32 tekens na het voorvoegsel)
    • HMAC-geheim: hex-tekenreeks van 64 tekens
  5. Sla ze op in je omgeving:

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. Implementeer de URL-ondertekeningsfunctie

Voor volledige tutorials met meerdere frameworks en patronen, zie:

Node.js / Next.js

javascript
// lib/fastog-signer.js
import crypto from "crypto";

// RFC-3986 encode (encodeURIComponent leaves !'()* unencoded).
const enc = (s) =>
    encodeURIComponent(s).replace(
        /[!'()*]/g,
        (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
    );

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 all = { ...params, key: apiKey };

    // Canonical: every param (except s), sorted, RFC-3986 encoded, joined with "&".
    const pairs = [];
    for (const [k, v] of Object.entries(all)) {
        for (const vv of Array.isArray(v) ? v : [v])
            pairs.push(`${enc(k)}=${enc(vv)}`);
    }
    pairs.sort();
    const canonical = pairs.join("&");

    // Content-signed: message = path + canonical query. No timestamp.
    const signature = crypto
        .createHmac("sha256", hmacSecret)
        .update(`GET/api/v1/og\n${canonical}`)
        .digest("hex");

    const url = new URL(baseUrl);
    for (const [k, v] of Object.entries(all)) {
        for (const vv of Array.isArray(v) ? v : [v])
            url.searchParams.append(k, vv);
    }
    url.searchParams.set("s", signature);
    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 = config('services.fastog.api_key');
    $hmacSecret = config('services.fastog.hmac_secret');

    $all = array_merge($params, ['key' => $apiKey]);

    // Canonical: every param except s, keys sorted (array values sorted too),
    // RFC-3986 encoded, joined with '&'. Content-signed — no timestamp.
    $pairs = [];
    foreach ($all as $k => $v) {
        $values = is_array($v) ? $v : [$v];
        sort($values, SORT_STRING);
        foreach ($values as $vv) {
            $pairs[] = rawurlencode((string) $k) . '=' . rawurlencode((string) $vv);
        }
    }
    sort($pairs, SORT_STRING);
    $canonical = implode('&', $pairs);

    $signature = hash_hmac('sha256', 'GET/api/v1/og' . "\n" . $canonical, $hmacSecret);

    return $baseUrl . '?' . http_build_query(array_merge($all, ['s' => $signature]));
}

Python (Django/FastAPI)

python
# utils/fastog.py
import hmac
import hashlib
import os
from urllib.parse import quote, urlencode

def _enc(s):
    return quote(str(s), safe='-._~')  # RFC-3986: only unreserved chars

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']

    all_params = {**params, 'key': api_key}

    # Canonical: every param except s, sorted, RFC-3986 encoded, joined with '&'.
    pairs = []
    for k, v in all_params.items():
        for vv in sorted(v if isinstance(v, list) else [v]):
            pairs.append(f"{_enc(k)}={_enc(vv)}")
    canonical = '&'.join(sorted(pairs))

    # Content-signed: message = path + canonical query. No timestamp.
    signature = hmac.new(
        hmac_secret.encode(),
        f"GET/api/v1/og\n{canonical}".encode(),
        hashlib.sha256,
    ).hexdigest()

    return f"{base_url}?{urlencode({**all_params, 's': signature})}"

3. Gebruik in je 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 je implementatie

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>"

Verwacht:

  • ✅ Geldige handtekening → Geeft OG-afbeelding terug
  • ❌ Gewijzigde parameter (bijv. title aangepast) → {"code": "invalid_signature"}
  • ❌ Ongeldige handtekening → {"code": "invalid_signature"}

5. Beveiligingschecklist

  • [ ] HMAC-geheim opgeslagen in omgevingsvariabelen (niet in code)
  • [ ] HTTPS gebruiken voor alle verzoeken
  • [ ] Geheim nooit blootstellen in client-side JavaScript
  • [ ] Mislukte handtekeningpogingen monitoren

6. Fouten afhandelen

javascript
if (response.status === 401) {
    const error = await response.json();

    switch (error.code) {
        case "missing_signature":
            // URL is missing `key` or `s` — re-generate it with your signer.
            break;
        case "invalid_signature":
            // A param changed after signing, or the secret doesn't match.
            // Re-sign the URL after any content change; verify the secret on both sides.
            break;
    }
}

Volgende stappen

  1. Implementeer ondertekening in de backend van je website (zie de tutorials)
  2. Test met de FastOG API
  3. Controleer logs op mislukte handtekeningpogingen
  4. Stel waarschuwingen in voor herhaalde fouten

Voor de volledige documentatie, zie de HMAC-handtekeninggids.

Volledige API-referentie

Probeer het gratis — geen aanmelding vereist

Bekijk en test dynamische OG-afbeeldingen in seconden.

Open de gratis OG-afbeeldingstester

A BeCaSimulations site