回调通知

当您在创建视频任务时提供了 notify_url,Scenext会在任务完成(成功或失败)时向您的服务器发送回调通知。

回调数据格式

Scenext会向您的 notify_url 发送POST请求,包含以下数据:
task_id
string
任务ID
status
string
任务状态:COMPLETEDFAILEDIN_PROGRESS
progress
array
任务进度信息列表
result
array
任务结果(仅在完成时存在)
timestamp
integer
时间戳(Unix时间戳)

签名验证

为确保安全性,每个回调请求都包含HMAC-SHA256签名,位于 X-Signature 请求头中。

验证步骤

  1. 获取请求体的JSON字符串
  2. 使用您的API密钥生成HMAC-SHA256签名
  3. 比较生成的签名与 X-Signature 头部的值

Python验证示例

import hmac
import hashlib
import json
from flask import Flask, request

app = Flask(__name__)

def verify_signature(payload, signature, api_key):
    """
    验证HMAC-SHA256签名
    """
    # 将payload转换为JSON字符串(按键排序)
    if isinstance(payload, dict):
        payload_str = json.dumps(payload, sort_keys=True)
    else:
        payload_str = str(payload)
    
    # 生成签名
    expected_signature = hmac.new(
        key=api_key.encode('utf-8'),
        msg=payload_str.encode('utf-8'),
        digestmod=hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(expected_signature, signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    # 获取签名
    signature = request.headers.get('X-Signature')
    if not signature:
        return 'Missing signature', 400
    
    # 获取请求数据
    data = request.get_json()
    
    # 验证签名
    api_key = "YOUR_API_KEY"  # 您的API密钥
    if not verify_signature(data, signature, api_key):
        return 'Invalid signature', 401
    
    # 处理回调数据
    task_id = data.get('task_id')
    status = data.get('status')
    result = data.get('result')
    
    if status == 'COMPLETED':
        # 任务完成,处理结果
        video_url = result.get('video_url')
        print(f"Task {task_id} completed. Video URL: {video_url}")
    elif status == 'FAILED':
        # 任务失败,处理错误
        error = result.get('error')
        print(f"Task {task_id} failed. Error: {error}")
    
    return 'OK', 200

Node.js验证示例

const crypto = require('crypto');
const express = require('express');
const app = express();

app.use(express.json());

function verifySignature(payload, signature, apiKey) {
    // 将payload转换为JSON字符串(按键排序)
    const payloadStr = JSON.stringify(payload, Object.keys(payload).sort());
    
    // 生成签名
    const expectedSignature = crypto
        .createHmac('sha256', apiKey)
        .update(payloadStr, 'utf8')
        .digest('hex');
    
    return crypto.timingSafeEqual(
        Buffer.from(expectedSignature, 'hex'),
        Buffer.from(signature, 'hex')
    );
}

app.post('/webhook', (req, res) => {
    const signature = req.headers['x-signature'];
    if (!signature) {
        return res.status(400).send('Missing signature');
    }
    
    const data = req.body;
    const apiKey = 'YOUR_API_KEY';  // 您的API密钥
    
    if (!verifySignature(data, signature, apiKey)) {
        return res.status(401).send('Invalid signature');
    }
    
    // 处理回调数据
    const { task_id, status, result } = data;
    
    if (status === 'COMPLETED') {
        console.log(`Task ${task_id} completed. Video URL: ${result.video_url}`);
    } else if (status === 'FAILED') {
        console.log(`Task ${task_id} failed. Error: ${result.error}`);
    }
    
    res.status(200).send('OK');
});

回调示例

任务完成回调

{
    "task_id": "12345",
    "status": "COMPLETED",
    "progress": [
        {
            "id": "frame1",
            "image": "https://oss.scenext.cn/frame_img/d988d4d9-72f0-4b76-97b0-ca974335dcf4/ed6e7bae-cb5d-4e75-a6ae-b4beced80c92/frame1.png?OSSAccessKeyId=LTAI5tDkzckmQzw7eKj4s47h&Expires=1749287113&Signature=ZofSGtAyhO3KolVaMC8ICUZJNWE%3D"
        },
        {
            "id": "frame2",
            "image": "https://oss.scenext.cn/frame_img/d988d4d9-72f0-4b76-97b0-ca974335dcf4/ed6e7bae-cb5d-4e75-a6ae-b4beced80c92/frame3.png?OSSAccessKeyId=LTAI5tDkzckmQzw7eKj4s47h&Expires=1749287134&Signature=5zB4nuv7PQkPDCzbKLMQBlb9f%2Bo%3D"
        },
        ...
    ],
    "result": [
        {
            "type": "video",
            "content": "https://oss.scenext.cn/Video/d988d4d9-72f0-4b76-97b0-ca974335dcf4/Manimed6e7bae-cb5d-4e75-a6ae-b4beced80c92.mp4?OSSAccessKeyId=LTAI5tDkzckmQzw7eKj4s47h&Expires=1749288010&Signature=CZRkEsLpWctM9MS3v7IbpRvMcKI%3D"
        }
    ],
    "timestamp": 1672531200
}

任务失败回调

{
    "task_id": "12345",
    "status": "FAILED",
    "progress": [
        {
            "id": "frame1",
            "image": "https://oss.scenext.cn/frame_img/d988d4d9-72f0-4b76-97b0-ca974335dcf4/ed6e7bae-cb5d-4e75-a6ae-b4beced80c92/frame1.png?OSSAccessKeyId=LTAI5tDkzckmQzw7eKj4s47h&Expires=1749287113&Signature=ZofSGtAyhO3KolVaMC8ICUZJNWE%3D"
        },
        {
            "id": "frame2",
            "image": "https://oss.scenext.cn/frame_img/d988d4d9-72f0-4b76-97b0-ca974335dcf4/ed6e7bae-cb5d-4e75-a6ae-b4beced80c92/frame3.png?OSSAccessKeyId=LTAI5tDkzckmQzw7eKj4s47h&Expires=1749287134&Signature=5zB4nuv7PQkPDCzbKLMQBlb9f%2Bo%3D"
        },
        ...
    ],
    "result": [
        {
            "type": "text",
            "content": "任务执行失败"
        }
    ],
    "timestamp": 1672531200
}

提示

  • 请务必验证回调签名以确保安全性
  • 回调通知无需返回响应,该回调通知只会发送一次