Signature Verification

To ensure the security and integrity of callback notifications, Scenext uses the HMAC-SHA256 algorithm to sign all callback requests.

Signature Algorithm

Generation Process

  1. Convert callback data to JSON string (sorted by key names)
  2. Use your API key as the secret key
  3. Generate signature using HMAC-SHA256 algorithm
  4. Convert signature to hexadecimal string

Python Implementation

import hmac
import hashlib
import json

def generate_signature(payload, api_key):
    """
    Generate HMAC-SHA256 signature
    :param payload: Data to be sent
    :param api_key: API key for signing
    :return: Hexadecimal signature
    """
    # Convert payload to JSON string
    if isinstance(payload, dict):
        payload_str = json.dumps(payload, sort_keys=True)
    else:
        payload_str = str(payload)
    
    # Generate signature using HMAC-SHA256
    signature = hmac.new(
        key=api_key.encode('utf-8'),
        msg=payload_str.encode('utf-8'),
        digestmod=hashlib.sha256
    ).hexdigest()
    
    return signature

def verify_signature(payload, signature, api_key):
    """
    Verify HMAC-SHA256 signature
    """
    expected_signature = generate_signature(payload, api_key)
    return hmac.compare_digest(expected_signature, signature)

JavaScript Implementation

const crypto = require('crypto');

function generateSignature(payload, apiKey) {
    // Convert payload to JSON string (sorted by keys)
    const payloadStr = JSON.stringify(payload, Object.keys(payload).sort());
    
    // Generate signature
    const signature = crypto
        .createHmac('sha256', apiKey)
        .update(payloadStr, 'utf8')
        .digest('hex');
    
    return signature;
}

function verifySignature(payload, signature, apiKey) {
    const expectedSignature = generateSignature(payload, apiKey);
    
    return crypto.timingSafeEqual(
        Buffer.from(expectedSignature, 'hex'),
        Buffer.from(signature, 'hex')
    );
}

PHP Implementation

<?php
function generateSignature($payload, $apiKey) {
    // Convert payload to JSON string (sorted by keys)
    if (is_array($payload)) {
        ksort($payload);
        $payloadStr = json_encode($payload);
    } else {
        $payloadStr = (string)$payload;
    }
    
    // Generate signature
    return hash_hmac('sha256', $payloadStr, $apiKey);
}

function verifySignature($payload, $signature, $apiKey) {
    $expectedSignature = generateSignature($payload, $apiKey);
    return hash_equals($expectedSignature, $signature);
}
?>

Complete Verification Examples

Flask (Python) Server

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

app = Flask(__name__)

API_KEY = "your_api_key_here"  # Your API key

def verify_signature(payload, signature):
    """Verify signature"""
    payload_str = json.dumps(payload, sort_keys=True)
    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():
    # Get signature
    signature = request.headers.get('X-Signature')
    if not signature:
        return jsonify({'error': 'Missing signature'}), 400
    
    # Get request data
    try:
        data = request.get_json()
    except Exception:
        return jsonify({'error': 'Invalid JSON'}), 400
    
    # Verify signature
    if not verify_signature(data, signature):
        return jsonify({'error': 'Invalid signature'}), 401
    
    # Process callback data
    task_id = data.get('task_id')
    status = data.get('status')
    
    print(f"Received webhook for task {task_id} with status {status}")
    
    # Process based on status
    if status == 'COMPLETED':
        # Task completion handling
        result = data.get('result', {})
        video_url = result.get('video_url')
        print(f"Video completed: {video_url}")
        
    elif status == 'FAILED':
        # Task failure handling
        result = data.get('result', {})
        error = result.get('error')
        print(f"Video generation failed: {error}")
    
    return jsonify({'message': 'OK'}), 200

if __name__ == '__main__':
    app.run(debug=True)

Express (Node.js) Server

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

const app = express();
const API_KEY = 'your_api_key_here';  // Your API key

app.use(express.json());

function verifySignature(payload, signature) {
    const payloadStr = JSON.stringify(payload, Object.keys(payload).sort());
    const expectedSignature = crypto
        .createHmac('sha256', API_KEY)
        .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).json({ error: 'Missing signature' });
    }
    
    const data = req.body;
    
    if (!verifySignature(data, signature)) {
        return res.status(401).json({ error: 'Invalid signature' });
    }
    
    // Process callback data
    const { task_id, status, result } = data;
    
    console.log(`Received webhook for task ${task_id} with status ${status}`);
    
    if (status === 'COMPLETED') {
        console.log(`Video completed: ${result.video_url}`);
    } else if (status === 'FAILED') {
        console.log(`Video generation failed: ${result.error}`);
    }
    
    res.status(200).json({ message: 'OK' });
});

app.listen(3000, () => {
    console.log('Webhook server listening on port 3000');
});

Security Best Practices

Important Tips

FAQ

  1. Check if the API key is correct
  2. Ensure JSON serialization sorts keys by name
  3. Verify that request data hasn’t been modified
  4. Check that character encoding is UTF-8
We strongly recommend not skipping signature verification. This would expose your system to security risks, including but not limited to: forged callback requests, data tampering, etc.
Timestamp verification is not required but recommended. You can check if the timestamp is within a reasonable range (e.g., within 5 minutes) to prevent replay attacks.