Localess

Webhooks

Receive real-time HTTP notifications when content, translations, or assets change in Localess — and trigger downstream systems automatically.

Localess can send an HTTP POST request to any URL you configure whenever a significant event occurs — a content publish, a translation update, an asset upload. Use webhooks to keep downstream systems in sync without polling the API.

Common uses:

  • Trigger a build or deploy when content is published (Vercel, Netlify, GitHub Actions)
  • Invalidate ISR or CDN caches when a content document goes live
  • Send a Slack notification when a translation batch is published
  • Kick off search re-indexing after new content is published

Setting Up a Webhook

  1. Navigate to Settings → Webhooks in the Localess admin UI.
  2. Click + Add Webhook.
  3. Fill in the form:
FieldRequiredDescription
NameA label for this webhook (e.g. Vercel Production Deploy)
URLThe HTTPS endpoint that will receive the POST request
SecretA shared secret used to sign the payload. Strongly recommended — see Verifying Signatures
EventsOne or more event types that trigger this webhook
  1. Click Save. Localess immediately stores the webhook and starts delivering events.

Events

Localess fires webhooks on the following events:

EventTrigger
content.publishedA content document is promoted from Draft to Published
content.deletedA content document is deleted
translations.publishedThe Publish action is taken in the Translations module
assets.uploadedOne or more asset files are uploaded
assets.deletedAn asset file is deleted

Select only the events your endpoint needs. Unneeded events are never delivered to that endpoint, which keeps your handler logic simple.


Payload Format

Every webhook request is an HTTP POST with a JSON body and the content type application/json.

Request headers

HeaderDescription
Content-Typeapplication/json
X-Localess-EventThe event name, e.g. content.published
X-Localess-DeliveryA unique UUID for this delivery attempt
X-Localess-SignatureHMAC-SHA256 signature of the raw body using your secret. Present only when a secret is configured
X-Localess-TimestampUnix timestamp (seconds) of when the event was generated

Example payload — content.published

{
  "event": "content.published",
  "deliveryId": "e4a2c891-3f10-4b2d-9c5a-1d8f63b2a471",
  "timestamp": 1748131200,
  "spaceId": "abc123",
  "data": {
    "documentId": "doc_7xKp9mNq",
    "name": "How to Get Started",
    "slug": "how-to-get-started",
    "fullSlug": "blog/how-to-get-started",
    "locale": "en",
    "schema": "article"
  }
}

Example payload — translations.published

{
  "event": "translations.published",
  "deliveryId": "b1c3d592-8e47-4f9a-bc12-2e7a94f1d830",
  "timestamp": 1748131440,
  "spaceId": "abc123",
  "data": {
    "locales": ["en", "fr", "de"]
  }
}

Example payload — assets.uploaded

{
  "event": "assets.uploaded",
  "deliveryId": "f9d2a104-5c63-4e8b-a731-3b9c72e8f502",
  "timestamp": 1748131600,
  "spaceId": "abc123",
  "data": {
    "assets": [
      {
        "assetId": "asset_3rNm7qLx",
        "name": "hero-banner.jpg",
        "uri": "/spaces/abc123/assets/hero-banner.jpg",
        "mimeType": "image/jpeg",
        "size": 284931
      }
    ]
  }
}

Verifying Signatures

When you set a secret on a webhook, Localess signs every request body with HMAC-SHA256 using that secret. Verify the signature before processing the payload to confirm the request came from Localess and was not tampered with in transit.

The signature is in the X-Localess-Signature header in the format sha256=<hex-digest>.

Verification example — Node.js

import crypto from 'crypto';

function verifyLocalessWebhook(
  rawBody: string | Buffer,
  signature: string,
  secret: string
): boolean {
  const expected = `sha256=${crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex')}`;
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// In a Next.js route handler:
export async function POST(request: Request) {
  const rawBody = await request.text();
  const signature = request.headers.get('X-Localess-Signature') ?? '';

  if (!verifyLocalessWebhook(rawBody, signature, process.env.LOCALESS_WEBHOOK_SECRET!)) {
    return new Response('Unauthorized', { status: 401 });
  }

  const payload = JSON.parse(rawBody);
  // handle payload...
  return new Response('OK', { status: 200 });
}

Always use timingSafeEqual — standard string comparison is vulnerable to timing attacks.


Retry Logic

If your endpoint does not respond with an HTTP 2xx status within 10 seconds, Localess marks the delivery as failed and retries with exponential backoff:

AttemptDelay
1st retry30 seconds
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry8 hours

After 5 failed attempts the delivery is abandoned. The delivery log in Settings → Webhooks → [webhook name] → Deliveries records every attempt, the HTTP status received, and the response body, so you can investigate failures.

Make your endpoint idempotent — use the X-Localess-Delivery header as an idempotency key so that retried deliveries do not cause duplicate actions.


Integration Examples

Trigger a Vercel deploy

Vercel provides a Deploy Hook URL in your project settings. Point a Localess content.published webhook at it directly — no code required.

https://api.vercel.com/v1/integrations/deploy/prj_xxxx/yyyyyyyy

Send a Slack notification

// Slack incoming webhook handler
export async function POST(request: Request) {
  const payload = await request.json();

  if (payload.event === 'content.published') {
    await fetch(process.env.SLACK_WEBHOOK_URL!, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        text: `📝 Content published: *${payload.data.name}* (${payload.data.fullSlug})`,
      }),
    });
  }

  return new Response('OK', { status: 200 });
}

Trigger Next.js ISR revalidation on publish

// app/api/localess-webhook/route.ts
import { revalidatePath } from 'next/cache';

export async function POST(request: Request) {
  const rawBody = await request.text();

  if (!verifyLocalessWebhook(rawBody, request.headers.get('X-Localess-Signature') ?? '', process.env.LOCALESS_WEBHOOK_SECRET!)) {
    return new Response('Unauthorized', { status: 401 });
  }

  const payload = JSON.parse(rawBody);

  if (payload.event === 'content.published') {
    // Revalidate the specific page that changed
    revalidatePath(`/${payload.data.fullSlug}`);
  }

  if (payload.event === 'translations.published') {
    // Revalidate all pages that use translations
    revalidatePath('/', 'layout');
  }

  return new Response('OK', { status: 200 });
}

Trigger search re-indexing

export async function POST(request: Request) {
  const payload = await request.json();

  if (payload.event === 'content.published') {
    // Fetch the published document and push to your search index
    const content = await localessClient.getContentBySlug(payload.data.fullSlug, {
      locale: payload.data.locale,
    });

    await searchClient.upsert({
      id: payload.data.documentId,
      title: content.data.title,
      body: content.data.body,
      url: `/${payload.data.fullSlug}`,
    });
  }

  return new Response('OK', { status: 200 });
}

Delivery Logs

Navigate to Settings → Webhooks → [webhook name] → Deliveries to inspect the history of every event sent to that endpoint. Each delivery record shows:

  • The event type and delivery ID
  • The HTTP status code returned by your endpoint
  • The timestamp and duration
  • The full request payload and response body

Use this view to debug failed deliveries or verify that your endpoint is receiving events correctly.

On this page