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 SettingsAPI 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.

FieldDescriptionMandatory/Optional
NameProvide an identifying label for the webhook.Mandatory
DescriptionExplain what this webhook does and what it is used for.Optional
URLEnter the endpoint URL that should receive webhook messages (format: http[s]://hostname[:port]/path).Mandatory
DatasetSelect which dataset(s) this webhook should apply to. By default, * (all datasets) are used.Optional
Trigger onDecide whether this webhook should fire on Create, Update, and/or Delete actions.Optional
FilterUse a GROQ filter to limit when the webhook triggers (e.g., _type == 'post').Optional
ProjectionCustomize payload fields with GROQ projections (e.g., {_id, name}).Optional
StatusToggle the checkbox → Enable webhook → to activate or deactivate it.Optional
HTTP methodSelect how the webhook delivers requests (default is POST).Optional
HTTP headersProvide any additional headers (Name/Value pairs) needed by the endpoint.Optional
API versionSelect which API version to use for GROQ (e.g., v2021-03-25).Optional
DraftsEnable if you want changes to Video Workflow in draft state (unpublished) to trigger the webhook. By default, webhooks only fire for published changes.Optional
VersionsEnable → 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
SecretAdd 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

code
import express from "express";
code
import crypto from "crypto";
code
import axios from "axios";
code

code
const app = express();
code
app.use(express.json());
code

code
app.post("/sanity-webhook", async (req, res) => {
code
// Verify signature
code
const signature = req.headers["x-sanity-signature"];
code
const expected = crypto
code
.createHmac("sha256", process.env.SANITY_SECRET)
code
.update(JSON.stringify(req.body))
code
.digest("hex");
code

code
if (signature !== expected) {
code
return res.status(401).send("Invalid signature");
code
}
code

code
const videoDoc = req.body;
code
const assetRef = videoDoc.file?.asset?._ref;
code

code
if (!assetRef) {
code
return res.status(400).send("No video file found");
code
}
code

code
// Get asset URL from Sanity CDN
code
const assetUrl = `https://cdn.sanity.io/files/${process.env.SANITY_PROJECT_ID}/${process.env.SANITY_DATASET}/${assetRef.split("-")[1]}.${assetRef.split("-")[2]}`;
code

code
// Send video to Cincopa
code
try {
code
const response = await axios.post("https://api.cincopa.com/v2.0/upload", {
code
api_token: process.env.CINCOPA_API_TOKEN,
code
url: assetUrl,
code
title: videoDoc.title
code
});
code

code
console.log("Cincopa upload started:", response.data);
code
res.status(200).send("Video sent to Cincopa");
code
} catch (error) {
code
console.error("Cincopa upload failed:", error.response?.data || error.message);
code
res.status(500).send("Error sending video to Cincopa");
code
}
code
});
code

code
app.listen(3000, () => console.log("Webhook listener running on port 3000"));
Banner

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:

code
const startTranscoding = async (assetUrl) => {
code
await mediaConvert.createJob({
code
Role: "arn:aws:iam::123456789012:role/MediaConvert_Default_Role",
code
Settings: {
code
OutputGroups: [ /* HLS, MP4, etc. */ ],
code
Inputs: [{ FileInput: assetUrl }]
code
}
code
}).promise();
code
};

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:

code
await client.patch(videoDoc._id)
code
.set({
code
duration: '120s',
code
thumbnailUrl: 'https://cdn.example.com/thumbs/video123.jpg'
code
})
code
.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:

code
await axios.post(process.env.SLACK_WEBHOOK_URL, {
code
text: `???? Video "${videoDoc.title}" has been uploaded and processed.`
code
});

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:

code
await axios.post(process.env.NETLIFY_BUILD_HOOK_URL);

Example:

code
await axios.post(
code
`https://api.cloudflare.com/client/v4/zones/${process.env.CF_ZONE_ID}/purge_cache`,
code
{ purge_everything: true },
code
{
code
headers: {
code
'Authorization': `Bearer ${process.env.CF_API_TOKEN}`,
code
'Content-Type': 'application/json'
code
}
code
}
code
);

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:

code
await db.collection('webhook_logs').insertOne({
code
receivedAt: new Date(),
code
docId: req.body._id,
code
eventType: req.headers['x-sanity-event'],
code
status: 'success',
code
message: 'Video successfully sent to Cincopa'
code
});
code

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):

code
import axiosRetry from 'axios-retry';
code
axiosRetry(axios, { retries: 3, retryDelay: axiosRetry.exponentialDelay });
code

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):

code
await axios.post(process.env.SLACK_ALERT_HOOK, {
code
text: `???? Video processing failed for document ID: ${videoDoc._id}`
code
});

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.