replicate-webhooks

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Replicate Webhooks

Replicate Webhooks

When to Use This Skill

何时使用此技能

  • Setting up Replicate webhook handlers
  • Debugging signature verification failures
  • Understanding Replicate event types and payloads
  • Handling prediction lifecycle events (start, output, logs, completed)
  • 设置Replicate webhook处理器
  • 调试签名验证失败问题
  • 了解Replicate事件类型和负载
  • 处理预测生命周期事件(启动、输出、日志、完成)

Essential Code (USE THIS)

核心代码(请使用此代码)

Express Webhook Handler

Express Webhook处理器

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

const app = express();

// CRITICAL: Use express.raw() for webhook endpoint - Replicate needs raw body
app.post('/webhooks/replicate',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    // Get webhook headers
    const webhookId = req.headers['webhook-id'];
    const webhookTimestamp = req.headers['webhook-timestamp'];
    const webhookSignature = req.headers['webhook-signature'];

    // Verify we have required headers
    if (!webhookId || !webhookTimestamp || !webhookSignature) {
      return res.status(400).json({ error: 'Missing required webhook headers' });
    }

    // Manual signature verification (recommended approach)
    const secret = process.env.REPLICATE_WEBHOOK_SECRET; // whsec_xxxxx from Replicate
    const signedContent = `${webhookId}.${webhookTimestamp}.${req.body}`;

    try {
      // Extract base64 secret after 'whsec_' prefix
      const secretBytes = Buffer.from(secret.split('_')[1], 'base64');
      const expectedSignature = crypto
        .createHmac('sha256', secretBytes)
        .update(signedContent)
        .digest('base64');

      // Replicate can send multiple signatures, check each one
      const signatures = webhookSignature.split(' ').map(sig => {
        const parts = sig.split(',');
        return parts.length > 1 ? parts[1] : sig;
      });

      const isValid = signatures.some(sig => {
        try {
          return crypto.timingSafeEqual(
            Buffer.from(sig),
            Buffer.from(expectedSignature)
          );
        } catch {
          return false; // Different lengths = invalid
        }
      });

      if (!isValid) {
        return res.status(400).json({ error: 'Invalid signature' });
      }

      // Check timestamp to prevent replay attacks (5-minute window)
      const timestamp = parseInt(webhookTimestamp, 10);
      const currentTime = Math.floor(Date.now() / 1000);
      if (currentTime - timestamp > 300) {
        return res.status(400).json({ error: 'Timestamp too old' });
      }
    } catch (err) {
      console.error('Signature verification error:', err);
      return res.status(400).json({ error: 'Invalid signature' });
    }

    // Parse the verified webhook body
    const prediction = JSON.parse(req.body.toString());

    // Handle the prediction based on its status
    console.log('Prediction webhook received:', {
      id: prediction.id,
      status: prediction.status,
      version: prediction.version
    });

    switch (prediction.status) {
      case 'starting':
        console.log('Prediction starting:', prediction.id);
        break;
      case 'processing':
        console.log('Prediction processing:', prediction.id);
        if (prediction.logs) {
          console.log('Logs:', prediction.logs);
        }
        break;
      case 'succeeded':
        console.log('Prediction completed successfully:', prediction.id);
        console.log('Output:', prediction.output);
        break;
      case 'failed':
        console.log('Prediction failed:', prediction.id);
        console.log('Error:', prediction.error);
        break;
      case 'canceled':
        console.log('Prediction canceled:', prediction.id);
        break;
      default:
        console.log('Unknown status:', prediction.status);
    }

    res.status(200).json({ received: true });
  }
);
javascript
const express = require('express');
const crypto = require('crypto');

const app = express();

// 关键:对webhook端点使用express.raw() - Replicate需要原始请求体
app.post('/webhooks/replicate',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    // 获取webhook头部信息
    const webhookId = req.headers['webhook-id'];
    const webhookTimestamp = req.headers['webhook-timestamp'];
    const webhookSignature = req.headers['webhook-signature'];

    // 验证是否存在必要的头部
    if (!webhookId || !webhookTimestamp || !webhookSignature) {
      return res.status(400).json({ error: '缺少必要的webhook头部' });
    }

    // 手动签名验证(推荐方式)
    const secret = process.env.REPLICATE_WEBHOOK_SECRET; // 来自Replicate的whsec_xxxxx
    const signedContent = `${webhookId}.${webhookTimestamp}.${req.body}`;

    try {
      // 提取'whsec_'前缀后的base64密钥
      const secretBytes = Buffer.from(secret.split('_')[1], 'base64');
      const expectedSignature = crypto
        .createHmac('sha256', secretBytes)
        .update(signedContent)
        .digest('base64');

      // Replicate可能发送多个签名,逐一检查
      const signatures = webhookSignature.split(' ').map(sig => {
        const parts = sig.split(',');
        return parts.length > 1 ? parts[1] : sig;
      });

      const isValid = signatures.some(sig => {
        try {
          return crypto.timingSafeEqual(
            Buffer.from(sig),
            Buffer.from(expectedSignature)
          );
        } catch {
          return false; // 长度不同 = 无效
        }
      });

      if (!isValid) {
        return res.status(400).json({ error: '无效签名' });
      }

      // 检查时间戳以防止重放攻击(5分钟窗口)
      const timestamp = parseInt(webhookTimestamp, 10);
      const currentTime = Math.floor(Date.now() / 1000);
      if (currentTime - timestamp > 300) {
        return res.status(400).json({ error: '时间戳过期' });
      }
    } catch (err) {
      console.error('签名验证错误:', err);
      return res.status(400).json({ error: '无效签名' });
    }

    // 解析已验证的webhook请求体
    const prediction = JSON.parse(req.body.toString());

    // 根据预测状态进行处理
    console.log('收到预测webhook:', {
      id: prediction.id,
      status: prediction.status,
      version: prediction.version
    });

    switch (prediction.status) {
      case 'starting':
        console.log('预测启动中:', prediction.id);
        break;
      case 'processing':
        console.log('预测处理中:', prediction.id);
        if (prediction.logs) {
          console.log('日志:', prediction.logs);
        }
        break;
      case 'succeeded':
        console.log('预测成功完成:', prediction.id);
        console.log('输出:', prediction.output);
        break;
      case 'failed':
        console.log('预测失败:', prediction.id);
        console.log('错误:', prediction.error);
        break;
      case 'canceled':
        console.log('预测已取消:', prediction.id);
        break;
      default:
        console.log('未知状态:', prediction.status);
    }

    res.status(200).json({ received: true });
  }
);

Common Prediction Statuses

常见预测状态

StatusDescriptionCommon Use Cases
starting
Prediction is initializingShow loading state in UI
processing
Model is runningDisplay progress, show logs if available
succeeded
Prediction completed successfullyProcess final output, update UI
failed
Prediction encountered an errorShow error message to user
canceled
Prediction was canceledClean up resources, notify user
状态描述常见用例
starting
预测正在初始化在UI中显示加载状态
processing
模型正在运行显示进度,如有可用日志则展示
succeeded
预测成功完成处理最终输出,更新UI
failed
预测遇到错误向用户显示错误信息
canceled
预测已取消清理资源,通知用户

Environment Variables

环境变量

bash
undefined
bash
undefined

Your webhook signing secret from Replicate

来自Replicate的webhook签名密钥

REPLICATE_WEBHOOK_SECRET=whsec_your_secret_here
undefined
REPLICATE_WEBHOOK_SECRET=whsec_your_secret_here
undefined

Local Development

本地开发

For local webhook testing, install the Hookdeck CLI:
bash
undefined
如需进行本地webhook测试,请安装Hookdeck CLI:
bash
undefined

Install via npm

通过npm安装

npm install -g hookdeck-cli
npm install -g hookdeck-cli

Or via Homebrew

或通过Homebrew安装

brew install hookdeck/hookdeck/hookdeck

Then start the tunnel:

```bash
hookdeck listen 3000 --path /webhooks/replicate
No account required. Provides local tunnel + web UI for inspecting requests.
brew install hookdeck/hookdeck/hookdeck

然后启动隧道:

```bash
hookdeck listen 3000 --path /webhooks/replicate
无需账号。提供本地隧道+Web UI用于检查请求。

Reference Materials

参考资料

  • What are Replicate webhooks — Event types and payload structure
  • Setting up webhooks — Dashboard configuration and signing secret
  • Signature verification — Verification algorithm and common issues
  • 什么是Replicate webhook — 事件类型和负载结构
  • 设置webhook — 仪表盘配置和签名密钥
  • 签名验证 — 验证算法和常见问题

Resources for Implementation

实现资源

Framework Examples

框架示例

  • Express implementation — Node.js with Express
  • Next.js implementation — React framework with API routes
  • FastAPI implementation — Python async framework
  • Express实现 — Node.js + Express
  • Next.js实现 — React框架 + API路由
  • FastAPI实现 — Python异步框架

Documentation

文档

Recommended: webhook-handler-patterns

推荐:webhook-handler-patterns

Enhance your webhook implementation with these patterns:
使用以下模式增强你的webhook实现:

Related Skills

相关技能