Managing video content manually (from uploading and encoding to publishing and syncing) can be slow, error-prone, and hard to scale. Every manual step adds friction, slows production, and increases the risk of mistakes.
Sanity Webhooks offer a solution by enabling real-time automation across your video pipeline. By connecting content changes in Sanity to backend services or serverless functions, you can automatically trigger workflows like video transcoding, thumbnail generation, CDN uploads, and more for faster, reliable, and scalable video publishing.
Create a Webhook in Sanity
A webhook in Sanity lets you automatically trigger external processes whenever content changes, like publishing a new document or deleting a record. Creating a webhook allows Sanity to notify another system in real time, so that updates in your content studio are immediately reflected across your applications.
Step 1: Visit https://www.sanity.io/manage and select your project.
Step 2: Navigate to Settings → API Settings.
Step 3: Scroll to the Webhooks section and click on + Create webhook.
Step 4: Fill in the necessary fields. Refer to the following table to know how to fill in the fields.
| Field | Description | Mandatory/Optional |
| Name | Provide an identifying label for the webhook. | Mandatory |
| Description | Explain what this webhook does and what it is used for. | Optional |
| URL | Enter the endpoint URL that should receive webhook messages (format: http[s]://hostname[:port]/path). | Mandatory |
| Dataset | Select which dataset(s) this webhook should apply to. By default, * (all datasets) are used. | Optional |
| Trigger on | Decide whether this webhook should fire on Create, Update, and/or Delete actions. | Optional |
| Filter | Use a GROQ filter to limit when the webhook triggers (e.g., _type == 'post'). | Optional |
| Projection | Customize payload fields with GROQ projections (e.g., {_id, name}). | Optional |
| Status | Toggle the checkbox → Enable webhook → to activate or deactivate it. | Optional |
| HTTP method | Select how the webhook delivers requests (default is POST). | Optional |
| HTTP headers | Provide any additional headers (Name/Value pairs) needed by the endpoint. | Optional |
| API version | Select which API version to use for GROQ (e.g., v2021-03-25). | Optional |
| Drafts | Enable if you want changes to Video Workflow in draft state (unpublished) to trigger the webhook. By default, webhooks only fire for published changes. | Optional |
| Versions | Enable → Trigger webhook when versions are modified → if you want changes to all relevant versions of the Video Workflow to trigger the webhook. Note: This option only appears if you"ve enabled Content Lake versioning, which is not enabled by default in all projects. | Optional |
| Secret | Add a secret key to verify events sent from Sanity. | Optional |
Develop a Receiver Endpoint
Build a receiver endpoint that accepts webhook POST requests from Sanity; this endpoint is the control plane of your pipeline. It verifies the webhook"s signature, validates & interprets the payload, decides which workflow to run (upload, transcode, thumbnail, notify), calls your video provider using your credentials, and writes status back to Sanity.
Example: Express.js Endpoint that Receives a Webhook from Sanity
import express from "express";import crypto from "crypto";import axios from "axios";
const app = express();app.use(express.json());
app.post("/sanity-webhook", async (req, res) => {// Verify signatureconst signature = req.headers["x-sanity-signature"];const expected = crypto.createHmac("sha256", process.env.SANITY_SECRET).update(JSON.stringify(req.body)).digest("hex");
if (signature !== expected) {return res.status(401).send("Invalid signature");}
const videoDoc = req.body;const assetRef = videoDoc.file?.asset?._ref;
if (!assetRef) {return res.status(400).send("No video file found");}
// Get asset URL from Sanity CDNconst assetUrl = `https://cdn.sanity.io/files/${process.env.SANITY_PROJECT_ID}/${process.env.SANITY_DATASET}/${assetRef.split("-")[1]}.${assetRef.split("-")[2]}`;
// Send video to Cincopatry {const response = await axios.post("https://api.cincopa.com/v2.0/upload", {api_token: process.env.CINCOPA_API_TOKEN,url: assetUrl,title: videoDoc.title});
console.log("Cincopa upload started:", response.data);res.status(200).send("Video sent to Cincopa");} catch (error) {console.error("Cincopa upload failed:", error.response?.data || error.message);res.status(500).send("Error sending video to Cincopa");}});
app.listen(3000, () => console.log("Webhook listener running on port 3000"));Automate Video Workflow Steps
Automating video workflow steps ensures that tasks like thumbnail generation, metadata updates, and cache invalidation run without manual intervention. This is necessary to keep video publishing consistent, reduce errors, and scale as the volume of media grows.
Start Video Transcoding Or Encoding
After a video is uploaded to Sanity, it may need to be converted into different resolutions or formats (e.g., MP4, HLS) for compatibility across devices and networks. To automate this:
Step 1: Integrate with a video transcoding service such as AWS Elemental MediaConvert or use FFmpeg for local processing.
Step 2: Resolve the file asset to a fetchable input for the transcoder. Webhook payloads contain a reference (e.g., a Sanity file asset _ref), but your transcoder needs a byte source. Convert the reference to a usable location (such as the Sanity CDN URL or a signed/temporary URL). Or first copy the file to the destination storage your transcoder expects, then pass that path as the job input.
Example:
const startTranscoding = async (assetUrl) => {await mediaConvert.createJob({Role: "arn:aws:iam::123456789012:role/MediaConvert_Default_Role",Settings: {OutputGroups: [ /* HLS, MP4, etc. */ ],Inputs: [{ FileInput: assetUrl }]}}).promise();};Trigger this function within your webhook handler after verifying the incoming data.
Update Thumbnails or Metadata in Your Database
Once a video is uploaded or transcoded, generate thumbnails or update metadata such as title, duration, or dimensions. To automate this:
Step 1: Use a processing tool like FFmpeg or a platform API (e.g., Mux or Cincopa) to extract metadata or generate thumbnails.
Step 2: Update your Sanity document using the Sanity client or push the metadata to your own database.
Example:
await client.patch(videoDoc._id).set({duration: '120s',thumbnailUrl: 'https://cdn.example.com/thumbs/video123.jpg'}).commit();Send Notifications to Users or Other Services
Notifying users when their video is processed improves user experience and keeps your team informed of status changes. To automate this:
Step 1: Use services like SendGrid (for email), Twilio (for SMS), or Slack (for internal team alerts).
Step 2: Send a message after a successful video upload or encoding event.
Example:
await axios.post(process.env.SLACK_WEBHOOK_URL, {text: `???? Video "${videoDoc.title}" has been uploaded and processed.`});This message can be adjusted based on the context of the event.
Trigger Deployment or Cache Invalidation in Connected Platforms
After updating video metadata or publishing new content, you may need to trigger a front-end deployment or invalidate cache to reflect the latest changes. To automate this:
Step 1: Use deployment hooks provided by services like Vercel or Netlify.
Step 2: Invalidate caches using services like Cloudflare.
Example:
await axios.post(process.env.NETLIFY_BUILD_HOOK_URL);Example:
await axios.post(`https://api.cloudflare.com/client/v4/zones/${process.env.CF_ZONE_ID}/purge_cache`,{ purge_everything: true },{headers: {'Authorization': `Bearer ${process.env.CF_API_TOKEN}`,'Content-Type': 'application/json'}});Use these triggers in your webhook receiver based on your content update conditions.
Monitor and Handle Webhook Events
Monitoring your webhook events and handling failures gracefully ensures a debuggable and reliable automation pipeline. Below are methods to track and respond to webhook activity.
Log Webhook Receptions and Outcomes
Keeping a log of each webhook event and its result provides a traceable audit trail and helps with debugging issues. To implement logging:
Step 1: Store logs in a database (e.g., MongoDB, PostgreSQL) or use a logging service like Logtail, Sentry, or Datadog.
Step 2: Include important details such as document ID, event type, timestamp, and outcome.
Example:
await db.collection('webhook_logs').insertOne({receivedAt: new Date(),docId: req.body._id,eventType: req.headers['x-sanity-event'],status: 'success',message: 'Video successfully sent to Cincopa'});Alternatively, use file-based or cloud logging with tools like Winston or CloudWatch.
Implement Retry Mechanisms or Error Handling if Workflows Fail
Failures during webhook handling (e.g., due to network issues or API errors) can lead to broken workflows. It's essential to retry failed operations and handle errors effectively. To implement retries:
Step 1: Use libraries like axios-retry or custom retry logic with exponential backoff.
Step 2: Respond with HTTP 5xx status codes to enable automatic webhook retries by Sanity.
Example (Retry Logic):
import axiosRetry from 'axios-retry';axiosRetry(axios, { retries: 3, retryDelay: axiosRetry.exponentialDelay });To handle persistent failures:
Step 1: Push failed jobs to a queue (e.g., BullMQ, AWS SQS) for later processing.
Step 2: Send alerts via Slack or email when a failure is detected.
Example (Slack Alert on Failure):
await axios.post(process.env.SLACK_ALERT_HOOK, {text: `???? Video processing failed for document ID: ${videoDoc._id}`});Sanity will automatically retry failed webhook deliveries up to three times if your server returns a 5xx response. Ensure your server implements proper error handling and response codes to take advantage of this feature.

