Gareth Wilson Gareth Wilson

Guide to Replicate Webhooks: Features and Best Practices

Published


Replicate webhooks notify your application as a prediction runs: it starts, produces output, emits logs, and completes. If you're running AI models on Replicate, webhooks are how these async jobs report progress and results without polling.

This guide covers how Replicate webhooks work, the prediction events you'll handle, how to verify the Standard Webhooks signature, and the best practices for production.

What are Replicate webhooks?

Replicate webhooks are JSON POSTs configured per prediction. They follow the Standard Webhooks format, with three headers: webhook-id, webhook-timestamp, and webhook-signature. The signing secret starts with whsec_ and its remainder is base64. The signature is an HMAC-SHA256 (base64) over <webhook-id>.<webhook-timestamp>.<raw_body>, and the webhook-signature header can carry multiple space-separated values (each prefixed v1,) during secret rotation; any one matching is valid. Reject deliveries older than 5 minutes.

Replicate webhook features

FeatureDetails
ConfigurationPer prediction via the API: webhook URL + webhook_events_filter
Signature headerswebhook-id, webhook-timestamp, webhook-signature
Signature schemeBase64 HMAC-SHA256 over id.timestamp.body, whsec_ base64 secret
Replay window5 minutes; multiple signatures during rotation, any match is valid
ThrottlingEvents throttled to max once per 500ms (except start and completed)
SDKManual verification (the Replicate SDK registers webhooks but doesn't verify)

Common events

You choose which prediction lifecycle events to receive via webhook_events_filter when you create the prediction:

EventFires when
startThe prediction begins processing
outputThe prediction generates output
logsLog output is generated
completedThe prediction reaches a terminal state (succeeded, failed, or canceled)

The payload envelope is { type, data }; the data.status field carries the prediction status (starting, processing, succeeded, failed, canceled).

Setting up Replicate webhooks

Replicate webhooks are configured per prediction, not in a dashboard. Pass a webhook URL and a webhook_events_filter array when you create the prediction (for example ["start", "completed"]). Replicate provides a signing secret in the format whsec_<base64>, store it as REPLICATE_WEBHOOK_SECRET. You can attach custom metadata via query parameters on the webhook URL (?userId=123).

Securing Replicate webhooks

Reconstruct <webhook-id>.<webhook-timestamp>.<raw_body>, strip the whsec_ prefix and base64-decode the secret, compute a base64 HMAC-SHA256, and compare against each signature in the header (any match passes). Then confirm the timestamp is recent. Verify against the raw body before parsing.

const crypto = require("crypto");

const SECRET = process.env.REPLICATE_WEBHOOK_SECRET;

function verify(rawBody, headers) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  const header = headers["webhook-signature"];
  if (!id || !timestamp || !header) return false;

  const key = Buffer.from(SECRET.split("_")[1], "base64"); // strip whsec_, base64-decode
  const signed = `${id}.${timestamp}.${rawBody.toString()}`;
  const expected = crypto.createHmac("sha256", key).update(signed).digest("base64");

  // The header may carry multiple space-separated "v1,<sig>" values (rotation)
  const provided = header.split(" ").map((s) => (s.includes(",") ? s.split(",")[1] : s));
  const match = provided.some((sig) => {
    const a = Buffer.from(sig);
    const b = Buffer.from(expected);
    return a.length === b.length && crypto.timingSafeEqual(a, b);
  });

  if (Math.floor(Date.now() / 1000) - parseInt(timestamp, 10) > 300) return false; // 5 min
  return match;
}

app.post("/webhooks/replicate", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers)) return res.sendStatus(401);
  res.sendStatus(200); // acknowledge fast
  processQueue.add(JSON.parse(req.body.toString())); // dedupe on webhook-id, async
});

The same verification in Python:

import base64
import hashlib
import hmac
import os
import time

SECRET = os.environ["REPLICATE_WEBHOOK_SECRET"]

def verify(raw_body: bytes, webhook_id: str, webhook_timestamp: str, webhook_signature: str) -> bool:
    key = base64.b64decode(SECRET.split("_")[1])  # strip whsec_, base64-decode
    signed = f"{webhook_id}.{webhook_timestamp}.".encode() + raw_body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    provided = [p.split(",")[1] if "," in p else p for p in webhook_signature.split()]
    if not any(hmac.compare_digest(sig, expected) for sig in provided):
        return False
    return int(time.time()) - int(webhook_timestamp) <= 300  # 5-minute window

Replicate webhook limitations and pain points

Multiple signatures during rotation

The Problem: During a secret rotation, webhook-signature carries more than one space-separated signature. Checking only the first can fail intermittently right after rotating.

Why It Happens: Standard Webhooks sends one signature per active key during the overlap.

Workarounds:

  • Split on spaces and accept if any signature matches.

How Hookdeck Can Help: Hookdeck verifies the Standard Webhooks signature at the edge, handling rotation for you.

The secret is base64 after the prefix

The Problem: The whsec_ secret must have its prefix stripped and the remainder base64-decoded before use as the HMAC key. Using it whole never matches.

Why It Happens: Standard Webhooks secrets are whsec_ + base64 key material.

Workarounds:

  • Strip whsec_, base64-decode, then HMAC.

How Hookdeck Can Help: Hookdeck handles the secret format at the edge.

Per-prediction configuration, and two vocabularies

The Problem: Webhooks are set per prediction (not a global endpoint), and the lifecycle events (start/output/logs/completed) differ from the prediction statuses (starting/processing/succeeded/failed/canceled). Conflating them leads to handlers that never fire.

Why It Happens: Replicate configures webhooks at prediction creation and reports both an event type and a status.

Workarounds:

  • Set webhook_events_filter per prediction, and read data.status for the outcome.

How Hookdeck Can Help: Hookdeck's filters can route on the event type and status you actually receive.

Throttling and duplicates

The Problem: Intermediate events are throttled to once per 500ms (except start and completed), and retries can duplicate.

Why It Happens: Replicate throttles high-frequency events and delivers at-least-once.

Workarounds:

  • Don't assume every intermediate event arrives; dedupe on webhook-id.

How Hookdeck Can Help: Hookdeck deduplicates and durably queues events at the edge. See our guide to webhook idempotency.

Best practices

Verify base64 HMAC-SHA256 over id.timestamp.body

Reconstruct the signed content, decode the whsec_ secret, and compare against each signature in constant time.

Handle rotation and the replay window

Accept any matching signature, and reject deliveries older than 5 minutes.

Read data.status, not just the event type

Branch on the prediction status for the outcome, and dedupe on webhook-id.

Acknowledge fast, process asynchronously

Return 200 quickly and defer work to a queue. See why to process webhooks asynchronously.

Make Replicate webhooks production-ready

Hookdeck verifies the Standard Webhooks signature, deduplicates, and durably queues every prediction event

Conclusion

Replicate webhooks follow the Standard Webhooks format: webhook-id / webhook-timestamp / webhook-signature headers, a whsec_ base64 secret, base64 HMAC-SHA256 over id.timestamp.body, a 5-minute window, and multiple signatures during rotation. Configure events per prediction with webhook_events_filter, verify over the raw body, read data.status, and dedupe on webhook-id.

Hookdeck verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique events.

Get started with Hookdeck for free and handle Replicate webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.