Lambda Webhooks
說明如何在 Remotion Lambda 中設定 Webhook 通知,包含 Webhook 配置、Payload 格式、簽名驗證與錯誤處理。
Lambda Webhooks
在 AWS Lambda 上渲染時,Remotion 可以傳送 webhook 通知,讓你知道渲染何時結束——無論是成功完成或發生錯誤。
若要了解如何在觸發渲染時啟用 webhook,請參閱 renderMediaOnLambda() 文件中的 webhook 部分。
設定
你需要建立一個接受 POST 請求的 API 端點。請確保該端點可從 AWS 存取。
本地開發提示:若你的 webhook 端點運行在本地端(
localhost),你需要使用反向代理工具建立公開 URL,例如:
- tunnelmole(開源)
- ngrok(商業工具)
執行這些工具後,會產生一個公開 URL,並將流量轉發至你本地的服務。
請求格式
請求標頭
每個 webhook 請求都包含以下標頭:
{
"Content-Type": "application/json",
"X-Remotion-Mode": "production | demo",
"X-Remotion-Signature": "sha512=HASHED_SIGNATURE | NO_SECRET_PROVIDED",
"X-Remotion-Status": "success | timeout | error"
}X-Remotion-Mode:用於辨別請求來自生產環境或測試工具X-Remotion-Signature:用於驗證請求的真實性(需設定 webhook 密鑰)X-Remotion-Status:渲染的最終狀態
請求主體型別定義
type StaticWebhookPayload = {
renderId: string;
expectedBucketOwner: string;
bucketName: string;
customData: Record<string, unknown> | null;
};
// 渲染錯誤時
type WebhookErrorPayload = StaticWebhookPayload & {
type: 'error';
errors: {
message: string;
name: string;
stack: string;
}[];
};
// 渲染成功時
type WebhookSuccessPayload = StaticWebhookPayload & {
type: 'success';
lambdaErrors: EnhancedErrorInfo[];
outputUrl: string | undefined;
outputFile: string | undefined;
timeToFinish: number | undefined;
costs: AfterRenderCost;
};
// 渲染逾時時
type WebhookTimeoutPayload = StaticWebhookPayload & {
type: 'timeout';
};
type WebhookPayload =
| WebhookErrorPayload
| WebhookSuccessPayload
| WebhookTimeoutPayload;customData 欄位
你可以在 customData 中傳入任何 JSON 可序列化的物件,方便將自訂資料帶入 webhook 端點。
重要限制:customData 序列化後必須小於 1KB(1024 位元組),否則會拋出錯誤。若需要傳遞較大的資料,請將其存入 inputProps,並透過呼叫 getRenderProgress() 讀取 progress.renderMetadata.inputProps 來取得。
非致命錯誤
一次成功的渲染過程仍可能包含非致命的 lambdaErrors,其結構如下:
{
"s3Location": "string",
"explanation": "string | null",
"type": "renderer | browser | stitcher",
"message": "string",
"name": "string",
"stack": "string",
"frame": "number | null",
"chunk": "number | null",
"isFatal": false,
"attempt": "number",
"willRetry": "boolean",
"totalAttempts": "number"
}errors 陣列則包含渲染過程中任何致命錯誤的訊息與堆疊追蹤。
驗證 Webhook 簽名
若在 CLI 參數中提供了 webhook 密鑰,Remotion 會對所有 webhook 請求進行簽名。
警告:若未提供密鑰,
X-Remotion-Signature將被設為NO_SECRET_PROVIDED。在這種情況下,無法驗證 webhook 請求的真實性與資料完整性。若要驗證傳入的 webhook,必須提供 webhook 密鑰。
Remotion 使用 HMAC 搭配 SHA-512 演算法 對 webhook 請求進行密碼學簽名。
觸發渲染時設定 Webhook
import { renderMediaOnLambda } from '@remotion/lambda/client';
await renderMediaOnLambda({
region: 'us-east-1',
functionName: 'remotion-render-bds9aab',
serveUrl: 'https://remotionlambda-example.s3.amazonaws.com/sites/my-site/index.html',
composition: 'MyVideo',
inputProps: { title: '範例影片' },
codec: 'h264',
webhook: {
url: 'https://your-api.example.com/webhook',
secret: process.env.WEBHOOK_SECRET!,
customData: {
userId: '123',
orderId: 'abc456',
},
},
});在 Node.js 中驗證簽名
import { validateWebhookSignature } from '@remotion/lambda/client';
// Express / Next.js API 路由範例
export async function POST(request: Request) {
const body = await request.text();
const signature = request.headers.get('X-Remotion-Signature') ?? '';
try {
validateWebhookSignature({
secret: process.env.WEBHOOK_SECRET!,
body,
signatureHeader: signature,
});
} catch (err) {
// 簽名驗證失敗,拒絕請求
return new Response('Unauthorized', { status: 401 });
}
const payload = JSON.parse(body) as WebhookPayload;
if (payload.type === 'success') {
console.log('渲染成功!', payload.outputUrl);
// 處理成功邏輯...
} else if (payload.type === 'error') {
console.error('渲染失敗', payload.errors);
// 處理錯誤邏輯...
} else if (payload.type === 'timeout') {
console.warn('渲染逾時', payload.renderId);
// 處理逾時邏輯...
}
return new Response('OK', { status: 200 });
}Next.js API 路由完整範例
// app/api/remotion-webhook/route.ts
import { validateWebhookSignature, WebhookPayload } from '@remotion/lambda/client';
export async function POST(request: Request) {
const body = await request.text();
const signature = request.headers.get('X-Remotion-Signature') ?? '';
const status = request.headers.get('X-Remotion-Status');
// 驗證 webhook 簽名
try {
validateWebhookSignature({
secret: process.env.WEBHOOK_SECRET!,
body,
signatureHeader: signature,
});
} catch {
return Response.json({ error: 'Invalid signature' }, { status: 401 });
}
const payload: WebhookPayload = JSON.parse(body);
switch (payload.type) {
case 'success':
// 更新資料庫,通知使用者
await updateRenderStatus(payload.renderId, 'completed', payload.outputUrl);
break;
case 'error':
// 記錄錯誤,通知管理員
await updateRenderStatus(payload.renderId, 'failed');
console.error('渲染錯誤:', payload.errors);
break;
case 'timeout':
// 標記為逾時,可能需要重新嘗試
await updateRenderStatus(payload.renderId, 'timeout');
break;
}
return Response.json({ received: true });
}
async function updateRenderStatus(
renderId: string,
status: string,
outputUrl?: string
) {
// 你的資料庫更新邏輯
}