Reading time: 5 min read

Processing Sitecore webhook events with Trigger.dev

How to handle long-running background jobs triggered by Sitecore webhooks, with retries, observability, and no timeout issues.

Portrait photo of Roberto Barbedo, article author

The problem with serverless webhook handlers

Sitecore webhooks are a great way to react to content changes. A product gets published, a page is updated — Sitecore fires a webhook and something on the other side handles it.

Most teams connect webhooks to a simple API route on their headless website, likely a Vercel serverless function. This works fine for simple tasks. But when you try to do something heavier — like syncing products to a search index, or running a pipeline that queries a large database — you may start hitting timeout limits or partial failures with limited visibility into what went wrong.

What is Trigger.dev

Trigger.dev is a platform for running background jobs from your application. You write a job in TypeScript, trigger it from your webhook handler, and Trigger.dev handles the execution outside your normal request cycle.

Trigger.dev Runs dashboard in Development environment showing two completed hello-world jobs, with columns for Task, Version, Status, Started time, Duration, Compute, and Machine

Beyond removing timeout constraints, it also provides:

  • Automatic retries — if the job fails, it retries with configurable delay
  • Run history — every execution is logged and stored
  • Step-level observability — see exactly which step failed and why
  • Replay — rerun a failed job with the same payload directly from the dashboard
  • Scheduling — trigger jobs on a cron, not just on demand

Conditional waiting — the job can check whether an item is actually published, or whether the change is already live on the site, before doing any work. If it is not ready yet, the job can wait and check again — without blocking anything or consuming a running thread

That last point is worth noting. Sitecore fires the webhook at the moment of publish, but your CDN or delivery layer may not reflect that change immediately. You can add a validation step that polls your live site or delivery API until the item is actually available, then proceed.

Example: syncing products to a search index

In this example we use Algolia, but the same approach applies to any third-party index like Elasticsearch or Azure AI Search.

Imagine you have a product catalog in Sitecore. Every time a product is published or unpublished, Sitecore fires a webhook. Your system needs to fetch the full product data and push the update to Algolia to keep search results accurate.

Depending on how your data is structured, fetching the full product may involve multiple queries — related categories, variants, pricing rules. This can take longer than a standard serverless function allows, and if it fails midway there is no built-in way to retry just that product.

How it works

The setup lives entirely inside your existing Next.js project. You create a TypeScript file that defines the background job — what it should do, how many times to retry, and what to do on each attempt. This file sits alongside the rest of your application code.

// file: /src/trigger/example.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
import { logger, task, wait } from '@trigger.dev/sdk/v3';
const MAX_PUBLISH_RETRIES = 5;
export const indexItemTask = task({
  id: 'algolia-index-item',
  maxDuration: 300,
  run: async (payload: any, { ctx }) => {
    logger.log('Indexing item received', { payload, ctx });
    let retries = 0;
    while (itemNotPublished(payload) && retries < MAX_PUBLISH_RETRIES) {
      retries++;
      logger.warn(`Item not published yet, retrying in 5s... (${retries}/${MAX_PUBLISH_RETRIES})`, {
        itemId: payload.id,
      });
      await wait.for({ seconds: 5 });
    }
    if (retries === MAX_PUBLISH_RETRIES) {
      logger.error('Item never published after max retries, aborting.', { itemId: payload.id });
      return {
        message: 'Indexing aborted: item never became published',
        itemId: payload.id,
      };
    }
    const algoliaRecord = buildAlgoliaRecord(payload);
    logger.log('Algolia record prepared', { algoliaRecord });
    await indexInAlgolia(algoliaRecord);
    logger.log('Item successfully indexed', { itemId: payload.id });
    return {
      message: 'Item indexed successfully',
      itemId: payload.id,
      indexedAt: new Date().toISOString(),
    };
  },
});

When you deploy the project, Trigger.dev picks up those job definitions automatically. Your project is effectively deployed to two places at once: Vercel runs your API routes as usual, and Trigger.dev runs your background jobs. Both are fed from the same codebase and the same deployment.

Your Sitecore webhook points to an API route in your Next.js project, same as before. That route receives the webhook payload, triggers the background job with the relevant data, and immediately returns a 200 to Sitecore. The job then runs on Trigger.dev's infrastructure — waiting for the item to go live, fetching the full product data, and pushing the update to Algolia.

// file: /api/webhook-handler.ts
import type { indexItemTask } from '../../../src/trigger/example';
import { tasks } from '@trigger.dev/sdk';
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }
  const authHeader = req.headers['authorization'];
  const expectedToken = process.env.WEBHOOK_SECRET;
  if (expectedToken && authHeader !== `Bearer ${expectedToken}`) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  const payload = req.body;
  // Trigger the Algolia indexing task asynchronously
  const handle = await tasks.trigger<typeof indexItemTask>('algolia-index-item', payload);
  return res.status(200).json({
    ok: true,
    runId: handle.id,
    message: `Indexing task queued for item ${payload.id}`,
  });
}

Here is the flow:

Sitecore Webhook → Next.js API Route → Trigger.dev Job → Validate Item is Live → Fetch Product Data → Index

The job handles both publish and unpublish events. If the item was unpublished, it gets removed from the index. If it was published, the full product data is fetched and saved.

What you can see in the dashboard

Once set up, every job execution is tracked. You can see:

  • When the job was triggered
  • How long each run took
  • Which step failed if something went wrong
  • The payload that was sent, so you can replay it

Trigger.dev run detail view showing a hello-world job triggered by a Sitecore publish:end webhook event, displaying the full JSON payload including item ID, publish options, source database name, and target language versions

Getting started

Adding Trigger.dev to an existing Next.js project is mostly installing their SDK and connecting your project to their cloud or self-hosted instance. Their CLI runs alongside your dev server during local development, so you can trigger and test jobs without deploying anything first.

For Sitecore, your webhook configuration does not change. You point it at your existing endpoint, your endpoint calls Trigger.dev, and the processing happens outside the request cycle.