Skip to main content
The Sanity plugin handles Sanity’s GROQ-powered document webhooks. Create one with webhooks.create, or under API > Webhooks at sanity.io/manage, point its URL at your Corsair HTTP handler, and give it a secret. Each Sanity webhook has its own secret. Store the same value with corsair.sanity.keys.set_webhook_signature(secret). Corsair keeps one secret per tenant, so all of one tenant’s webhooks must share it, and different tenants should use different secrets. sanity({ webhookSecret }) covers accounts with no stored secret, and a stored one takes precedence. Use that option only in a single-tenant app. Every tenant without a stored secret falls back to it, and then a delivery signed for one tenant also verifies under another’s tenantId. Only signed deliveries are accepted. If no secret is set, or a delivery fails verification, processWebhook reports a failure and no hook runs after it. The handler below then answers 500, and Sanity retries twice, 30 seconds apart. Signatures more than 15 minutes from now are refused. Deliveries can repeat, so deduplicate by idempotencyKey. On Corsair’s cloud, Hub routes each delivery by its sanity-project-id header to the account that stored that project id. In your own handler, processWebhook takes the tenant from ?tenantId= and uses default without it. In a multi-tenant app, put ?tenantId=<tenant> on each tenant’s webhook URL. The handler below forwards the query to processWebhook. The signature covers the body but not the URL or the headers, so give every tenant’s webhook its own secret. A delivery replayed under another tenantId then fails that tenant’s check. A before hook runs ahead of the signature check, so it can receive an unsigned or forged delivery. Keep side effects in after.
New to Corsair? See webhooks and hooks.

Webhook map

  • documents
    • changed (documents.changed)

HTTP handler setup

app/api/webhook/route.ts

Events

Documents

Changed

documents.changed A GROQ-powered webhook fired: a document matching its filter was created, updated or deleted. The body is the webhook’s projection; the operation, document id and dataset come from its headers. Only signed deliveries are accepted. Payload: any
webhookHooks example