Migrate legacy webhook behavior to GROQ-powered webhooks

To recreate the payload shape of legacy webhooks, follow these steps. This does not restore dataset-level batching — GROQ-powered webhooks fire once per changed document, so each request carries exactly one ID. Batch on your receiving endpoint if you need dataset-level grouping.

  • In the project's management interface at manage.sanity.io, create a webhook set to trigger on create, update, and delete.
  • Leave the Filter field empty.
  • Add the following to the Projection field:
{
  "projectId": sanity::projectId(),
  "dataset": sanity::dataset(),
  "ids": {
    "created": select(before() == null && after() != null => [_id], []),
    "deleted": select(before() != null && after() == null => [_id], []),
    "updated": select(before() != null && after() != null => [_id], []),
    "all": [
      _id
    ]
  }
}

The transaction ID is not available to the projection, but every webhook request carries it in the sanity-transaction-id header. By default this webhook does not fire for drafts. or versions. documents — enable the drafts and versions settings if you need those, and note that version support requires webhook API version v2025-02-19 or later.

You can also create a webhook from a template that has these settings.

Was this page helpful?

sanity.ioAll Systems Operational© Sanity 2026
Change site theme