Webhooks

Receive real-time notifications when your V2 renders complete. Webhooks include the image URL and your custom metadata, signed with HMAC SHA-256 for security.

V2 Only
Webhooks are exclusively available in the V2 Render API. The V1 Image API does not support webhooks.

Setup

Add webhookUrl to your V2 render request body. Optionally include webhookSecret for HMAC signature verification.

Request Body
{
  "template": "blog",
  "title": "My Post",
  "webhookUrl": "https://yourapp.com/hooks/og-complete",
  "webhookSecret": "whsec_your_secret_here",
  "metadata": {
    "postId": "post_abc123",
    "userId": "user_456"
  }
}

Webhook Payload

ogmint sends a POST request to your webhook URL with the following JSON body:

Webhook POST Body
{
  "event": "render.completed",
  "data": {
    "url": "https://ogmint.app/i/xK9mQ2pL",
    "slug": "xK9mQ2pL",
    "template": "blog",
    "metadata": {
      "postId": "post_abc123",
      "userId": "user_456"
    },
    "createdAt": "2025-03-08T00:00:00.000Z"
  }
}

For cached/dedup responses, the payload includes "cached": true.

Signature Verification

When webhookSecret is provided, ogmint signs the payload with HMAC SHA-256. The signature is sent in the x-ogmint-signature header.

Node.js Verification
import crypto from "crypto";

function verifyWebhook(body: string, secret: string, signature: string) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
app.post("/hooks/og-complete", (req, res) => {
  const rawBody = JSON.stringify(req.body);
  const signature = req.headers["x-ogmint-signature"];
  
  if (!verifyWebhook(rawBody, "whsec_your_secret", signature)) {
    return res.status(401).json({ error: "Invalid signature" });
  }
  
  // Process the webhook...
  const { url, slug, metadata } = req.body.data;
  console.log("Image ready:", url);
  res.json({ ok: true });
});

SSRF Protection

ogmint blocks webhook dispatches to private/internal network addresses:

  • localhost, 127.0.0.1, ::1
  • Private ranges: 10.x.x.x, 172.16-31.x.x, 192.168.x.x
  • Cloud metadata: 169.254.169.254, metadata.google.internal
  • .local and .internal TLDs

Retry Policy

⚠️ Current Limitation
Webhook dispatch is currently fire-and-forget with a 5-second timeout. There are no automatic retries. A message queue with retry logic is planned for a future release.

Timeout

Your webhook endpoint must respond within 5 seconds. If the request times out, the webhook is considered failed. The render itself still succeeds — only the notification is lost.