Skip to main content

Webhooks

在 AWS Lambda 上渲染时,Remotion 可以发送 Webhooks 来通知您渲染结束的情况,无论是成功还是失败。本页面描述了 Webhook 负载和如何设置 Webhook API 端点。

请参考 renderMediaOnLambda() 文档以了解如何启用 Webhooks 触发渲染

设置

您需要设置一个带有 POST 请求处理程序的 API 端点。确保端点可访问并接受来自 AWS 的请求。

info

如果您在本地机器上运行 Webhook 端点(即在 localhost 上),您需要使用诸如 tunnelmole 这样的工具来设置一个公共反向代理,或者使用 ngrok 这样的流行闭源隧道工具。运行任一工具都将生成一个公共 URL,该 URL 将转发到您本地服务。

响应

每个 Webhook 都具有以下标头:

json
{
"Content-Type": "application/json",
"X-Remotion-Mode": "production" | "demo",
"X-Remotion-Signature": "sha512=HASHED_SIGNATURE" | "NO_SECRET_PROVIDED",
"X-Remotion-Status": "success" | "timeout" | "error",
}
json
{
"Content-Type": "application/json",
"X-Remotion-Mode": "production" | "demo",
"X-Remotion-Signature": "sha512=HASHED_SIGNATURE" | "NO_SECRET_PROVIDED",
"X-Remotion-Status": "success" | "timeout" | "error",
}

您可以使用这些标头来验证请求的真实性,检查渲染过程的状态以及检查 Webhook 是否是从部署到 AWS 的生产代码调用的,还是从下面的工具或您自己的测试套件调用的演示应用。

请求正文具有以下结构:

ts
type WebhookPayload =
| {
type: 'error';
errors: {
message: string;
name: string;
stack: string;
}[];
renderId: string;
expectedBucketOwner: string;
bucketName: string;
customData: Record<string, unkown>;
}
| {
type: 'success';
lambdaErrors: EnhancedErrorInfo[];
outputUrl: string | undefined;
outputFile: string | undefined;
timeToFinish: number | undefined;
renderId: string;
expectedBucketOwner: string;
bucketName: string;
customData: Record<string, unkown>;
// Available from v3.3.11
costs: {
estimatedCost: number;
estimatedDisplayCost: string;
currency: string;
disclaimer: string;
};
}
| {
type: 'timeout';
renderId: string;
expectedBucketOwner: string;
bucketName: string;
customData: Record<string, unkown>;
};
ts
type WebhookPayload =
| {
type: 'error';
errors: {
message: string;
name: string;
stack: string;
}[];
renderId: string;
expectedBucketOwner: string;
bucketName: string;
customData: Record<string, unkown>;
}
| {
type: 'success';
lambdaErrors: EnhancedErrorInfo[];
outputUrl: string | undefined;
outputFile: string | undefined;
timeToFinish: number | undefined;
renderId: string;
expectedBucketOwner: string;
bucketName: string;
customData: Record<string, unkown>;
// Available from v3.3.11
costs: {
estimatedCost: number;
estimatedDisplayCost: string;
currency: string;
disclaimer: string;
};
}
| {
type: 'timeout';
renderId: string;
expectedBucketOwner: string;
bucketName: string;
customData: Record<string, unkown>;
};

字段 renderIdbucketName 将会被返回,就像 renderMediaOnLambda() 本身返回的一样

您可以使用字段 customData 来设置一个 JSON 可序列化对象,这对于将自定义数据传递给 Webhook 端点非常有用。当序列化后,customData 字段的大小必须小于 1KB(1024 字节),否则会抛出错误。将较大的数据存储在 inputProps 中,并通过调用 getRenderProgress() 并读取 progress.renderMetadata.inputProps 来检索它。

如果渲染过程超时,响应体将不包含任何其他字段。

只有在渲染成功时才会返回 outputUrloutputFiletimeToFinish 键。请注意,成功的渲染过程可能仍然存在非致命的 lambdaErrors

json
{
"s3Location": "string",
"explanation": "string" | null,
"type": "renderer" | "browser" | "stitcher",
"message": "string",
"name": "string",
"stack": "string",
"frame": "number"| null,
"chunk": "number"| null,
"isFatal": "boolean",
"attempt": "number",
"willRetry": "boolean",
"totalAttempts": "number",
"tmpDir": {
"files": [{
"filename": "string",
"size": "number",
}],
"total": "number"
} | null,
}
json
{
"s3Location": "string",
"explanation": "string" | null,
"type": "renderer" | "browser" | "stitcher",
"message": "string",
"name": "string",
"stack": "string",
"frame": "number"| null,
"chunk": "number"| null,
"isFatal": "boolean",
"attempt": "number",
"willRetry": "boolean",
"totalAttempts": "number",
"tmpDir": {
"files": [{
"filename": "string",
"size": "number",
}],
"total": "number"
} | null,
}

errors 数组将包含在渲染过程中发生的任何 致命 错误的错误消息和堆栈跟踪。

验证 Webhooks

如果您在 CLI 参数中提供了一个 webhook 密钥,Remotion 将对所有 webhook 请求进行签名。

warning

如果您没有提供密钥,X-Remotion-Signature 将被设置为 NO_SECRET_PROVIDED。无法验证使用 NO_SECRET_PROVIDED 签名发送的 webhook 请求的真实性和数据完整性。如果您想要验证传入的 webhooks,必须提供一个 webhook 密钥。

Remotion 使用 HMACSHA-512 算法 对其发送的 webhook 请求进行加密签名。这使您能够验证传入 webhook 请求的真实性和数据完整性。

为了验证一个 webhook 请求,您需要使用您提供的 webhook 密钥和请求体创建一个 SHA-512 HMAC 签名的十六进制摘要。如果它与 X-Remotion-Signature 头部匹配,则该请求确实是由 Remotion 发送的,其请求体是完整的。

如果不匹配,要么数据完整性受到损害且请求体不完整,要么该请求不是由 Remotion 发送的。

这是 Remotion 计算签名的方式:

javascript
import * as Crypto from "crypto";
function calculateSignature(payload: string, secret?: string) {
if (!secret) {
return "NO_SECRET_PROVIDED";
}
const hmac = Crypto.createHmac("sha512", secret);
const signature = "sha512=" + hmac.update(payload).digest("hex");
return signature;
}
javascript
import * as Crypto from "crypto";
function calculateSignature(payload: string, secret?: string) {
if (!secret) {
return "NO_SECRET_PROVIDED";
}
const hmac = Crypto.createHmac("sha512", secret);
const signature = "sha512=" + hmac.update(payload).digest("hex");
return signature;
}

在您的 webhook 端点中,payload 参数是请求体,secret 参数是您的 webhook 密钥。

您可以使用 validateWebhookSignature() 函数来验证签名是否有效,而无需自行验证签名。

示例 webhook 端点(Express)

您可以使用任何 Web 框架和语言来设置您的 webhook 端点。以下示例是使用 Express 框架编写的 JavaScript 示例。

server.js
javascript
import express from "express";
import bodyParser from "body-parser";
import * as Crypto from "crypto";
import {
validateWebhookSignature,
WebhookPayload,
} from "@remotion/lambda/client";
const router = express();
// You'll need to add a JSON parser middleware globally or
// for the webhook route in order to get access to the request
// body.
const jsonParser = bodyParser.json();
// Enable testing through the tool below
const ENABLE_TESTING = true;
// Express API endpoint
router.post("/my-remotion-webhook-endpoint", jsonParser, (req, res) => {
if (ENABLE_TESTING) {
res.setHeader("Access-Control-Allow-Origin", "https://www.remotion.dev");
res.setHeader("Access-Control-Allow-Methods", "OPTIONS,POST");
res.setHeader(
"Access-Control-Allow-Headers",
"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, X-Remotion-Status, X-Remotion-Signature, X-Remotion-Mode"
);
}
if (req.method === "OPTIONS") {
res.status(200).end();
return;
}
validateWebhookSignature({
signatureHeader: req.header("X-Remotion-Signature"),
body: req.body,
secret: process.env.WEBHOOK_SECRET as string
});
const status = req.header("X-Remotion-Status"); // success, timeout, error
const mode = req.header("X-Remotion-Mode"); // demo or production
const payload = JSON.parse(req.body) as WebhookPayload;
if (payload.type === "success") {
// ...
} else if (payload.type === "timeout") {
// ...
}
});
server.js
javascript
import express from "express";
import bodyParser from "body-parser";
import * as Crypto from "crypto";
import {
validateWebhookSignature,
WebhookPayload,
} from "@remotion/lambda/client";
const router = express();
// You'll need to add a JSON parser middleware globally or
// for the webhook route in order to get access to the request
// body.
const jsonParser = bodyParser.json();
// Enable testing through the tool below
const ENABLE_TESTING = true;
// Express API endpoint
router.post("/my-remotion-webhook-endpoint", jsonParser, (req, res) => {
if (ENABLE_TESTING) {
res.setHeader("Access-Control-Allow-Origin", "https://www.remotion.dev");
res.setHeader("Access-Control-Allow-Methods", "OPTIONS,POST");
res.setHeader(
"Access-Control-Allow-Headers",
"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, X-Remotion-Status, X-Remotion-Signature, X-Remotion-Mode"
);
}
if (req.method === "OPTIONS") {
res.status(200).end();
return;
}
validateWebhookSignature({
signatureHeader: req.header("X-Remotion-Signature"),
body: req.body,
secret: process.env.WEBHOOK_SECRET as string
});
const status = req.header("X-Remotion-Status"); // success, timeout, error
const mode = req.header("X-Remotion-Mode"); // demo or production
const payload = JSON.parse(req.body) as WebhookPayload;
if (payload.type === "success") {
// ...
} else if (payload.type === "timeout") {
// ...
}
});

示例 Webhook 端点 (Next.JS 应用路由器)

同样,在 Next.JS 中为应用路由器提供一个示例端点。

由于此端点将在 AWS Lambda 函数中独立执行,您需要从 @remotion/lambda/client 导入 Remotion 函数。

app/api/webhook.ts
tsx
import {
validateWebhookSignature,
WebhookPayload,
} from '@remotion/lambda/client';
 
// Enable testing through the tool below
// You may disable it in production
const ENABLE_TESTING = true;
 
export const POST = async (req: Request, res: Response) => {
let headers = {};
 
if (ENABLE_TESTING) {
const testingheaders = {
'Access-Control-Allow-Origin': 'https://www.remotion.dev',
'Access-Control-Allow-Headers':
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, X-Remotion-Status, X-Remotion-Signature, X-Remotion-Mode',
'Access-Control-Allow-Methods': 'OPTIONS,POST',
};
headers = {...headers, ...testingheaders};
}
 
if (req.method === 'OPTIONS') {
return new Response(null, {
headers,
});
}
 
// Parse the body properly
const body = await req.json();
 
validateWebhookSignature({
secret: process.env.WEBHOOK_SECRET as string,
body: body,
signatureHeader: req.headers.get('X-Remotion-Signature') as string,
});
 
const payload = body as WebhookPayload;
 
if (payload.type === 'success') {
//...
} else if (payload.type === 'timeout') {
//...
}
 
return new Response(JSON.stringify({success: true}));
};
 
export const OPTIONS = POST;
app/api/webhook.ts
tsx
import {
validateWebhookSignature,
WebhookPayload,
} from '@remotion/lambda/client';
 
// Enable testing through the tool below
// You may disable it in production
const ENABLE_TESTING = true;
 
export const POST = async (req: Request, res: Response) => {
let headers = {};
 
if (ENABLE_TESTING) {
const testingheaders = {
'Access-Control-Allow-Origin': 'https://www.remotion.dev',
'Access-Control-Allow-Headers':
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, X-Remotion-Status, X-Remotion-Signature, X-Remotion-Mode',
'Access-Control-Allow-Methods': 'OPTIONS,POST',
};
headers = {...headers, ...testingheaders};
}
 
if (req.method === 'OPTIONS') {
return new Response(null, {
headers,
});
}
 
// Parse the body properly
const body = await req.json();
 
validateWebhookSignature({
secret: process.env.WEBHOOK_SECRET as string,
body: body,
signatureHeader: req.headers.get('X-Remotion-Signature') as string,
});
 
const payload = body as WebhookPayload;
 
if (payload.type === 'success') {
//...
} else if (payload.type === 'timeout') {
//...
}
 
return new Response(JSON.stringify({success: true}));
};
 
export const OPTIONS = POST;

示例 Webhook 端点 (Next.JS 页面路由器)

与上面相同的端点,但使用页面路由器。

pages/api/webhook.ts
tsx
import {
validateWebhookSignature,
WebhookPayload,
} from '@remotion/lambda/client';
 
// Enable testing through the tool below
// You may disable it in production
const ENABLE_TESTING = true;
 
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (ENABLE_TESTING) {
res.setHeader('Access-Control-Allow-Origin', 'https://www.remotion.dev');
res.setHeader('Access-Control-Allow-Methods', 'OPTIONS,POST');
res.setHeader(
'Access-Control-Allow-Headers',
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, X-Remotion-Status, X-Remotion-Signature, X-Remotion-Mode',
);
}
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
 
validateWebhookSignature({
secret: process.env.WEBHOOK_SECRET as string,
body: req.body,
signatureHeader: req.headers['x-remotion-signature'] as string,
});
 
// If code reaches this path, the webhook is authentic.
const payload = req.body as WebhookPayload;
if (payload.type === 'success') {
// ...
} else if (payload.type === 'timeout') {
// ...
}
 
res.status(200).json({
success: true,
});
}
pages/api/webhook.ts
tsx
import {
validateWebhookSignature,
WebhookPayload,
} from '@remotion/lambda/client';
 
// Enable testing through the tool below
// You may disable it in production
const ENABLE_TESTING = true;
 
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (ENABLE_TESTING) {
res.setHeader('Access-Control-Allow-Origin', 'https://www.remotion.dev');
res.setHeader('Access-Control-Allow-Methods', 'OPTIONS,POST');
res.setHeader(
'Access-Control-Allow-Headers',
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, X-Remotion-Status, X-Remotion-Signature, X-Remotion-Mode',
);
}
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
 
validateWebhookSignature({
secret: process.env.WEBHOOK_SECRET as string,
body: req.body,
signatureHeader: req.headers['x-remotion-signature'] as string,
});
 
// If code reaches this path, the webhook is authentic.
const payload = req.body as WebhookPayload;
if (payload.type === 'success') {
// ...
} else if (payload.type === 'timeout') {
// ...
}
 
res.status(200).json({
success: true,
});
}

测试您的 Webhook 端点

您可以使用此工具验证您的 Webhook 端点是否正常工作。该工具将发送一个适当的演示负载并将响应记录到屏幕上。此工具发送的所有请求都将设置 "X-Remotion-Mode" 标头为 "demo"

info

此工具直接从您的浏览器发送演示 Webhook 请求,这具有以下含义:

  • CORS 要求
    • 确保您的 API 端点配置为接受来自 remotion.dev 的请求,方法是设置 "Access-Control-Allow-Origin": "https://www.remotion.dev"。这对于此工具工作是必要的,但对于您的生产 Webhook 端点则不是必需的。
    • 您必须设置 "Access-Control-Allow-Headers": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, X-Remotion-Status, X-Remotion-Signature, X-Remotion-Mode"
    • 您必须设置 "Access-Control-Allow-Methods": "OPTIONS,POST"
    • 阅读 DevTools 中的错误消息以调试潜在的 CORS 问题。
  • 您可以使用在 localhost 上监听的服务器,而无需使用反向代理。
info
Your webhook URL:
Your webhook secret:

另请参阅