Webhooks

Blobify emits webhooks when published outputs change.

Supported events:

  • content.published
  • content.unpublished
  • content.deleted
  • routing.published

Each webhook payload includes:

  • affected urls
  • summaryFields
  • publishedLocales
  • stable revalidationTags

Why tags matter

Blobify does not enumerate every page that references a changed entry.

Consumers can tag cached fetches with stable Blobify tags.

Example tags:

  • blobify:org:{orgId}
  • blobify:space:{spaceId}
  • blobify:model:{spaceId}:{model}
  • blobify:content:{spaceId}:{model}:{contentId}
  • blobify:locale:{spaceId}:{model}:{locale}
  • blobify:routing:{orgId}

If a tour appears in many pages, tag the fetches that depend on that tour.

When Blobify sends a webhook for that tour, your app can revalidate by tag and invalidate all affected pages.

Example handler

tscode
import crypto from 'crypto';
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';

type BlobifyWebhookPayload = {
  event: string;
  urls: string[];
  revalidationTags: string[];
};

function verifyBlobifySignature(rawBody: string, signatureHeader: string, secret: string) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((entry) => {
      const [key, value] = entry.split('=');
      return [key, value];
    }),
  );

  const timestamp = parts.t;
  const signature = parts.v1;
  if (!timestamp || !signature) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'utf8'),
    Buffer.from(expected, 'utf8'),
  );
}

export async function POST(req: NextRequest) {
  const rawBody = await req.text();
  const signature = req.headers.get('x-blobify-signature');

  if (!signature || !verifyBlobifySignature(rawBody, signature, process.env.BLOBIFY_WEBHOOK_SECRET!)) {
    return NextResponse.json({ ok: false, error: 'Invalid signature' }, { status: 401 });
  }

  const payload = JSON.parse(rawBody) as BlobifyWebhookPayload;

  for (const url of payload.urls) {
    revalidatePath(url);
  }

  for (const tag of payload.revalidationTags) {
    revalidateTag(tag);
  }

  return NextResponse.json({ ok: true });
}

Blobify sends these headers with each delivery:

  • X-Blobify-Signature
  • X-Blobify-Event
  • X-Blobify-Delivery

Example fetch tagging

tscode
await fetch(articleUrl, {
  next: {
    tags: [
      `blobify:model:main:article`,
      `blobify:content:main:article:${articleId}`,
      `blobify:locale:main:article:en`,
    ],
    revalidate: 300,
  },
});

Practical rule

Use both:

  • path revalidation for direct pages
  • tag revalidation for shared dependencies and referenced content

Route path

A dedicated webhook route keeps the integration isolated:

textcode
/api/blobify/webhook

Register that URL in Settings → Webhooks and use the shared secret from the webhook configuration when verifying signatures.