mcpField
R
replicate-webhooks

replicate-webhooks

github.com/hookdeckDevelopment95198 installsUpdated today
Repository

About replicate-webhooks

A skill for developers building Replicate webhook handlers, covering prediction lifecycle events, signature verification, and payloads, with framework implementation references.

Key features

  1. 01Handle prediction status events
  2. 02Verify webhook signatures
  3. 03Review event payload structure
  4. 04Express, Next.js, and FastAPI implementation references
  5. 05Test with a local tunnel

Use cases

  1. 01Handle prediction completion or failure notifications
  2. 02Update an interface or process output by prediction status

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/hookdeck/webhook-skills --skill replicate-webhooks

After installing, type/replicate-webhooksin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

TODO.md
examples/express/
.env.example
jest.config.js
package.json
README.md
examples/express/src/
index.js
examples/express/test/
webhook.test.js
examples/fastapi/
.env.example
main.py
README.md
requirements.txt
test_webhook.py
examples/nextjs/
.env.example
package.json
README.md
vitest.config.ts
examples/nextjs/app/webhooks/replicate/
route.ts
examples/nextjs/test/
webhook.test.ts
references/
overview.md
setup.md
verification.md

Details

From the project README

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)

Essential Code (USE THIS)

Express Webhook Handler
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 });
  }
);

Common Prediction Statuses

StatusDescriptionCommon Use Cases
startingPrediction is initializingShow loading state in UI
processingModel is runningDisplay progress, show logs if available
succeededPrediction completed successfullyProcess final output, update UI
failedPrediction encountered an errorShow error message to user
canceledPrediction was canceledClean up resources, notify user

Environment Variables

# Your webhook signing secret from Replicate
REPLICATE_WEBHOOK_SECRET=whsec_your_secret_here

Local Development

For local webhook testing, install the Hookdeck CLI:

Then start the tunnel:

npx hookdeck-cli listen 3000 replicate --path /webhooks/replicate

No account required. Provides local tunnel + web UI for inspecting requests.

Reference Materials

Resources for Implementation

Framework Examples
Documentation

Recommended: webhook-handler-patterns

Enhance your webhook implementation with these patterns:

Related Skills

Comments

Markdown supported