Back to skills
SkillHub ClubShip Full StackFull StackTestingIntegration

replicate-webhooks

Receive and verify Replicate webhooks. Use when setting up Replicate webhook handlers, debugging signature verification, or handling prediction events like start, output, logs, or completed.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
63
Hot score
92
Updated
March 20, 2026
Overall rating
C3.0
Composite score
3.0
Best-practice grade
A85.2

Install command

npx @skill-hub/cli install hookdeck-webhook-skills-replicate-webhooks

Repository

hookdeck/webhook-skills

Skill path: skills/replicate-webhooks

Receive and verify Replicate webhooks. Use when setting up Replicate webhook handlers, debugging signature verification, or handling prediction events like start, output, logs, or completed.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack, Testing, Integration.

Target audience: everyone.

License: MIT.

Original source

Catalog source: SkillHub Club.

Repository owner: hookdeck.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install replicate-webhooks into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/hookdeck/webhook-skills before adding replicate-webhooks to shared team environments
  • Use replicate-webhooks for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: replicate-webhooks
description: >
  Receive and verify Replicate webhooks. Use when setting up Replicate webhook
  handlers, debugging signature verification, or handling prediction events
  like start, output, logs, or completed.
license: MIT
metadata:
  author: hookdeck
  version: "0.1.0"
  repository: https://github.com/hookdeck/webhook-skills
---

# 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

```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 });
  }
);
```

## Common Prediction Statuses

| Status | Description | Common Use Cases |
|--------|-------------|------------------|
| `starting` | Prediction is initializing | Show loading state in UI |
| `processing` | Model is running | Display progress, show logs if available |
| `succeeded` | Prediction completed successfully | Process final output, update UI |
| `failed` | Prediction encountered an error | Show error message to user |
| `canceled` | Prediction was canceled | Clean up resources, notify user |

## Environment Variables

```bash
# Your webhook signing secret from Replicate
REPLICATE_WEBHOOK_SECRET=whsec_your_secret_here
```

## Local Development

For local webhook testing, install the Hookdeck CLI:

```bash
# Install via npm
npm install -g hookdeck-cli

# Or via 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.

## Reference Materials

- [What are Replicate webhooks](references/overview.md) — Event types and payload structure
- [Setting up webhooks](references/setup.md) — Dashboard configuration and signing secret
- [Signature verification](references/verification.md) — Verification algorithm and common issues

## Resources for Implementation

### Framework Examples
- [Express implementation](examples/express/) — Node.js with Express
- [Next.js implementation](examples/nextjs/) — React framework with API routes
- [FastAPI implementation](examples/fastapi/) — Python async framework

### Documentation
- [Official Replicate webhook docs](https://replicate.com/docs/topics/webhooks)
- [Webhook setup guide](https://replicate.com/docs/topics/webhooks/setup-webhook)
- [Webhook verification guide](https://replicate.com/docs/topics/webhooks/verify-webhook)

## Recommended: webhook-handler-patterns

Enhance your webhook implementation with these patterns:

- [Handler sequence](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/handler-sequence.md)
- [Idempotency](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/idempotency.md)
- [Error handling](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/error-handling.md)
- [Retry logic](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/retry-logic.md)

## Related Skills

- [stripe-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/stripe-webhooks) - Stripe payment webhooks
- [github-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/github-webhooks) - GitHub repository events
- [shopify-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/shopify-webhooks) - Shopify store events
- [clerk-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/clerk-webhooks) - Clerk authentication events
- [webhook-handler-patterns](https://github.com/hookdeck/webhook-skills/tree/main/skills/webhook-handler-patterns) - Idempotency, error handling, retry logic
- [hookdeck-event-gateway](https://github.com/hookdeck/webhook-skills/tree/main/skills/hookdeck-event-gateway) - Webhook infrastructure that replaces your queue — guaranteed delivery, automatic retries, replay, rate limiting, and observability for your webhook handlers

---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/overview.md

```markdown
# Replicate Webhooks Overview

## What Are Replicate Webhooks?

Replicate uses webhooks to notify your application about prediction status changes in real-time. Instead of polling for prediction results, you can receive instant notifications when predictions start, generate output, produce logs, or complete.

## Common Event Types

| Event | Triggered When | Common Use Cases |
|-------|----------------|------------------|
| `start` | Prediction begins processing | Update UI to show "processing" state, notify users |
| `output` | Prediction generates output | Stream partial results, display progress |
| `logs` | Log output is generated | Display progress messages, debug information |
| `completed` | Prediction reaches terminal state (succeeded/failed/canceled) | Process final results, handle errors, update database |

## Event Payload Structure

All Replicate webhook events follow this structure:

```json
{
  "type": "start|output|logs|completed",
  "data": {
    "id": "prediction_id",
    "status": "starting|processing|succeeded|failed|canceled",
    "input": {
      // Your original input parameters
    },
    "output": null, // or prediction results
    "logs": "", // cumulative log output
    "error": null, // error message if failed
    "created_at": "2024-01-01T00:00:00.000Z",
    "started_at": "2024-01-01T00:00:01.000Z",
    "completed_at": null, // or completion timestamp
    "urls": {
      "get": "https://api.replicate.com/v1/predictions/...",
      "cancel": "https://api.replicate.com/v1/predictions/.../cancel"
    },
    "metrics": {
      "predict_time": 0.5 // time in seconds
    }
  }
}
```

## Webhook Events Filter

You can control which events trigger webhook requests using `webhook_events_filter`:

- **Default behavior**: Sends requests for new outputs and prediction completion
- **Custom filtering**: Specify exact events you want to receive
- **Rate limiting**: Events are throttled to max once per 500ms (except `start` and `completed`)

Example configuration:
```javascript
const prediction = await replicate.run(model, {
  input: { /* ... */ },
  webhook: "https://example.com/webhooks/replicate",
  webhook_events_filter: ["start", "completed"] // Only receive start and completed events
});
```

## Prediction Status Flow

1. **starting** → Prediction created, waiting for resources
2. **processing** → Model is actively running
3. **succeeded** → Completed successfully with output
4. **failed** → Error occurred during processing
5. **canceled** → Prediction was manually canceled

## Full Event Reference

For the complete list of webhook details and configuration options, see [Replicate's webhook documentation](https://replicate.com/docs/topics/webhooks).
```

### references/setup.md

```markdown
# Setting Up Replicate Webhooks

## Prerequisites

- Replicate account with API access
- Your application's webhook endpoint URL
- API token from Replicate dashboard

## Get Your Webhook Signing Secret

When you configure webhooks, Replicate will provide a signing secret in the format:

```
whsec_base64encodedkey
```

Store this secret securely in your environment variables - you'll need it to verify webhook signatures.

## Register Your Webhook Endpoint

Webhooks are configured per prediction when you create them via the API:

```javascript
const replicate = new Replicate({
  auth: process.env.REPLICATE_API_TOKEN,
});

const prediction = await replicate.run(
  "stability-ai/stable-diffusion:...",
  {
    input: {
      prompt: "a painting of a sunset"
    },
    webhook: "https://your-app.com/webhooks/replicate",
    webhook_events_filter: ["start", "output", "logs", "completed"]
  }
);
```

## Webhook Configuration Options

- **webhook**: Your endpoint URL (HTTPS required for production)
- **webhook_events_filter**: Array of events to receive
  - `start` - When prediction begins
  - `output` - When output is generated
  - `logs` - When logs are produced
  - `completed` - When prediction finishes (success/fail/cancel)

## Adding Custom Metadata

You can pass custom data via query parameters:

```javascript
webhook: "https://your-app.com/webhooks/replicate?userId=123&jobId=456"
```

## Local Development with Hookdeck CLI

For local testing, use the Hookdeck CLI instead of ngrok:

```bash
# Install Hookdeck CLI
npm install -g hookdeck-cli

# Create a tunnel to your local server
hookdeck listen 3000 --path /webhooks/replicate
```

This provides:
- A public URL for your webhook endpoint
- Request inspection in the web UI
- Automatic retries and error handling
- No account required for basic usage

Example with custom source name:
```bash
hookdeck listen 3000 \
  --path /webhooks/replicate \
  --source replicate-webhooks
```

## Test Your Webhook

Create a test prediction with your webhook URL:

```javascript
const testPrediction = await replicate.run(
  "stability-ai/stable-diffusion:...",
  {
    input: {
      prompt: "test image"
    },
    webhook: "https://your-hookdeck-url.hookdeck.com",
    webhook_events_filter: ["completed"]
  }
);
```

You should receive a webhook notification when the prediction completes.

## Production Considerations

1. **Use HTTPS**: Required for production webhooks
2. **Verify signatures**: Always verify the webhook signature
3. **Handle retries**: Replicate may retry failed webhook deliveries
4. **Process async**: Handle webhooks quickly and process heavy work asynchronously
5. **Monitor failures**: Log and monitor webhook processing errors
```

### references/verification.md

```markdown
# Replicate Signature Verification

## How It Works

Replicate uses a custom signature scheme to ensure webhook requests are authentic:

1. **Headers sent with each request:**
   - `webhook-id`: Unique identifier for this webhook event
   - `webhook-timestamp`: Unix timestamp when the webhook was sent
   - `webhook-signature`: HMAC-SHA256 signature(s) in base64 format

2. **Signing process:**
   - Concatenate: `${webhook_id}.${webhook_timestamp}.${raw_body}`
   - HMAC-SHA256 hash using the secret key
   - Base64 encode the result

3. **Secret format:**
   - Starts with `whsec_` prefix
   - Followed by base64-encoded key
   - Example: `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw`

## Implementation

### Manual Verification (Recommended)

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

function verifyWebhookSignature(body, headers, secret) {
  const webhookId = headers['webhook-id'];
  const webhookTimestamp = headers['webhook-timestamp'];
  const webhookSignature = headers['webhook-signature'];

  if (!webhookId || !webhookTimestamp || !webhookSignature) {
    throw new Error('Missing required webhook headers');
  }

  // Extract the key from the secret (remove 'whsec_' prefix)
  const key = Buffer.from(secret.split('_')[1], 'base64');

  // Create the signed content
  const signedContent = `${webhookId}.${webhookTimestamp}.${body}`;

  // Calculate expected signature
  const expectedSignature = crypto
    .createHmac('sha256', key)
    .update(signedContent)
    .digest('base64');

  // Parse signatures (can be multiple, space-separated)
  const signatures = webhookSignature.split(' ').map(sig => {
    const parts = sig.split(',');
    return parts.length > 1 ? parts[1] : sig;
  });

  // Verify at least one signature matches
  const isValid = signatures.some(sig => {
    try {
      return crypto.timingSafeEqual(
        Buffer.from(sig),
        Buffer.from(expectedSignature)
      );
    } catch {
      return false; // Different lengths
    }
  });

  // Verify timestamp is recent (prevent replay attacks)
  const timestamp = parseInt(webhookTimestamp, 10);
  const currentTime = Math.floor(Date.now() / 1000);
  if (currentTime - timestamp > 300) { // 5 minutes
    throw new Error('Timestamp too old');
  }

  return isValid;
}
```

### Using with Express

```javascript
app.post('/webhooks/replicate',
  express.raw({ type: 'application/json' }), // CRITICAL: Raw body required
  (req, res) => {
    try {
      const isValid = verifyWebhookSignature(
        req.body,
        req.headers,
        process.env.REPLICATE_WEBHOOK_SECRET
      );

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

      const event = JSON.parse(req.body.toString());
      // Process event...

      res.status(200).json({ received: true });
    } catch (err) {
      console.error('Webhook error:', err);
      res.status(400).json({ error: err.message });
    }
  }
);
```

## Common Gotchas

### 1. Raw Body Parsing
**Problem**: Using `express.json()` middleware parses the body before verification.

**Solution**: Use `express.raw()` for webhook endpoints:
```javascript
app.post('/webhooks/replicate',
  express.raw({ type: 'application/json' }),
  handler
);
```

### 2. Secret Format
**Problem**: Using the secret incorrectly (forgetting to remove prefix or decode base64).

**Solution**: Extract the key properly:
```javascript
// Correct: Remove 'whsec_' and decode base64
const key = Buffer.from(secret.split('_')[1], 'base64');

// Wrong: Using the full secret as-is
const key = secret; // Don't do this!
```

### 3. Multiple Signatures
**Problem**: Only checking the first signature when multiple are present.

**Solution**: Parse and check all signatures:
```javascript
const signatures = webhookSignature.split(' ').map(sig => {
  const parts = sig.split(',');
  return parts.length > 1 ? parts[1] : sig;
});
```

### 4. Header Name Casing
**Problem**: Headers might be lowercase in some frameworks.

**Solution**: Access headers consistently:
```javascript
// Express normalizes to lowercase
const webhookId = req.headers['webhook-id'];

// For other frameworks, you might need:
const webhookId = req.headers['Webhook-Id'] || req.headers['webhook-id'];
```

## Debugging Verification Failures

1. **Log the raw values:**
   ```javascript
   console.log('Headers:', {
     id: headers['webhook-id'],
     timestamp: headers['webhook-timestamp'],
     signature: headers['webhook-signature']
   });
   console.log('Body length:', body.length);
   console.log('Secret prefix:', secret.substring(0, 10));
   ```

2. **Common error messages and fixes:**
   - "Missing required webhook headers" → Check header names and casing
   - "Invalid signature" → Verify raw body parsing and secret format
   - "Timestamp too old" → Check server time sync or increase tolerance
   - "Different lengths" → Signature encoding mismatch

3. **Test with a known good request:**
   - Use Replicate's test webhooks to verify your implementation
   - Compare your calculated signature with the one sent

## Security Best Practices

1. **Always verify signatures** - Never trust webhook data without verification
2. **Use timing-safe comparison** - Prevents timing attacks
3. **Check timestamp freshness** - Prevents replay attacks
4. **Store secrets securely** - Use environment variables, not code
5. **Return generic errors** - Don't leak information about why verification failed
```

replicate-webhooks | SkillHub