<!-- PkgPulse AI-readable guide source -->
<!-- Canonical: https://www.pkgpulse.com/guides/svix-vs-hookdeck-vs-convoy-webhook-infrastructure-2026 -->
<!-- Raw Markdown: https://www.pkgpulse.com/guides/svix-vs-hookdeck-vs-convoy-webhook-infrastructure-2026/raw.md -->
<!-- Source path: content/guides/svix-vs-hookdeck-vs-convoy-webhook-infrastructure-2026.mdx -->

---
og_image: "/images/guides/svix-vs-hookdeck-vs-convoy-webhook-infrastructure-2026.webp"
title: Svix vs Hookdeck vs Convoy (2026)
description: "Compare Svix, Hookdeck, and Convoy for webhook delivery, retries, and management. Data-driven comparison of reliability, SDKs, developer portals, and pricing."
date: "2026-03-09"
author: "PkgPulse Team"
tags: ["webhooks", "api", "infrastructure", "nodejs"]
---

# Svix vs Hookdeck vs Convoy: Webhook Infrastructure Platforms 2026

## TL;DR

**Svix** is the best choice if you're building a product that *sends* webhooks to customers — it provides a full embeddable portal, automatic retries, and multi-tenant management out of the box. **Hookdeck** is purpose-built for *receiving and routing* incoming webhooks reliably — ideal for teams tired of losing events from Stripe, GitHub, or any third-party service. **Convoy** is the only fully open-source option among the three, giving you complete control when self-hosting is non-negotiable.

## Key Takeaways

- **Svix dominates outbound webhook use cases** — SDKs for 12+ languages, an embeddable portal for your customers to manage subscriptions, and enterprise features like message filtering
- **Hookdeck excels at inbound webhook orchestration** — rate limiting, fan-out, transformations, replays, and dead-letter queues without writing infra code
- **Convoy is fully open source (Apache 2.0)** — the only choice when you need full data ownership or on-prem deployment with no vendor lock-in
- **Svix GitHub stars: ~3.4k** (Jan 2026) — strong growth in the B2B SaaS space
- **Hookdeck GitHub stars: ~1.7k** — focused tool with a loyal DevOps/platform engineering following
- **Convoy GitHub stars: ~4.2k** — open-source momentum with active community contributions
- **Reliability floor:** All three provide automatic retries with exponential backoff; Svix and Hookdeck add observability and alerting; Convoy requires you to configure your own monitoring

---

## Why Webhook Infrastructure Is a Real Problem

Webhooks seem simple — POST a JSON payload to a URL — but production webhook systems are notoriously hard to get right. Three things kill teams:

1. **Silent failures** — the endpoint returns 500, nobody notices, events are lost
2. **Thundering herds** — a delayed consumer causes a backlog that overwhelms your sender
3. **Debugging nightmares** — figuring out which events fired, in what order, and why they failed requires digging through logs that may not exist

Dedicated webhook infrastructure platforms handle all three. They've become a distinct category in the developer tooling stack, alongside message queues (Kafka, NATS) and job queues (BullMQ, Inngest). Choosing the right one depends on whether your primary concern is *sending* webhooks (outbound) or *receiving* them (inbound).

---

## Svix: The Gold Standard for Outbound Webhooks

Svix is designed for companies that need to send webhooks to their customers — think Stripe's webhook system, but as a managed service you can embed in your own product.

### Core Integration

```typescript
import { Svix } from "svix";

const svix = new Svix(process.env.SVIX_AUTH_TOKEN!);

// Create an application (one per customer/tenant)
const app = await svix.application.create({
  name: "Acme Corp",
  uid: "acme-corp-12345", // your internal customer ID
});

// Create endpoint for this customer
const endpoint = await svix.endpoint.create(app.id, {
  url: "https://acme.corp/webhooks/your-product",
  version: 1,
  description: "Production webhook endpoint",
  filterTypes: ["invoice.paid", "subscription.cancelled"],
});

// Send a message — Svix handles delivery, retries, and logging
const message = await svix.message.create(app.id, {
  eventType: "invoice.paid",
  payload: {
    invoiceId: "inv_123",
    amount: 9900,
    currency: "usd",
    customerId: "cust_456",
  },
});

console.log(`Message ${message.id} queued for delivery`);
```

### Message Filtering and Fan-Out

```typescript
// Customers can subscribe to specific event types
// Your code filters at send time — only matching endpoints receive the event
const message = await svix.message.create(app.id, {
  eventType: "user.created",
  payload: { userId: "user_789", email: "new@example.com", plan: "pro" },
  // Optional: target specific channels
  channels: ["pro-tier-events"],
});

// Query delivery attempts for debugging
const attempts = await svix.messageAttempt.listByMsg(app.id, message.id, {
  status: 0, // 0=success, 1=pending, 2=fail
  limit: 20,
});

for (const attempt of attempts.data) {
  console.log(
    `Endpoint: ${attempt.endpointId} | Status: ${attempt.status} | ` +
      `HTTP: ${attempt.responseStatusCode} | Duration: ${attempt.timestamp}`
  );
}
```

### Embeddable Customer Portal

```typescript
// Generate a magic link for your customer's webhook management portal
import { Svix } from "svix";
import express from "express";

const app = express();
const svix = new Svix(process.env.SVIX_AUTH_TOKEN!);

app.get("/webhook-portal", async (req, res) => {
  const { customerId } = req.user; // your auth middleware

  const portalAccess = await svix.authentication.appPortalAccess(
    customerId, // application UID
    {
      featureFlags: [
        "filteringEnabled",
        "messageHistoryEnabled",
      ],
    }
  );

  // Redirect customer to their self-serve portal
  // They can add/remove endpoints, view history, replay messages
  res.redirect(portalAccess.url);
});
```

### Event Catalog with Schema Validation

```typescript
// Define your event types with OpenAPI-style schemas
const eventType = await svix.eventType.create({
  name: "invoice.paid",
  description: "Fires when an invoice is successfully paid",
  schemas: {
    "1": {
      description: "Invoice paid event v1",
      properties: {
        invoiceId: { type: "string", description: "Unique invoice identifier" },
        amount: { type: "integer", description: "Amount in smallest currency unit" },
        currency: { type: "string", enum: ["usd", "eur", "gbp"] },
      },
      required: ["invoiceId", "amount", "currency"],
    },
  },
});
```

### Webhook Signature Verification

```typescript
import { Webhook } from "svix";

// In your customer's receiving endpoint (or for testing)
const wh = new Webhook(process.env.SVIX_WEBHOOK_SECRET!);

app.post("/svix-webhooks", express.raw({ type: "application/json" }), (req, res) => {
  const payload = req.body.toString();
  const headers = {
    "svix-id": req.headers["svix-id"] as string,
    "svix-timestamp": req.headers["svix-timestamp"] as string,
    "svix-signature": req.headers["svix-signature"] as string,
  };

  try {
    const msg = wh.verify(payload, headers);
    // Process verified webhook
    console.log(`Verified event: ${msg.type}`, msg.data);
    res.status(200).json({ received: true });
  } catch (err) {
    console.error("Webhook verification failed:", err.message);
    res.status(400).json({ error: "Invalid signature" });
  }
});
```

---

## Hookdeck: Built for Inbound Webhook Reliability

Hookdeck flips the model — instead of sending webhooks, it's a managed proxy and router that sits in front of your *receiving* endpoints. Connect it to Stripe, GitHub, Shopify, or any third-party service, and Hookdeck handles buffering, retries, rate limiting, and transformations.

### CLI Setup and Local Development

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

# Login and create a tunnel for local development
hookdeck login
hookdeck listen 3000 stripe-webhooks
# → Creates a public URL you paste into Stripe's dashboard
# → All events are buffered and forwarded to localhost:3000
```

### Node.js SDK Integration

```typescript
import { HookdeckClient } from "@hookdeck/sdk";

const hookdeck = new HookdeckClient({
  token: process.env.HOOKDECK_API_KEY!,
});

// Create a source (represents an inbound connection)
const source = await hookdeck.source.upsert({
  name: "stripe-production",
});

// Create a destination (your endpoint)
const destination = await hookdeck.destination.upsert({
  name: "order-service",
  url: "https://api.yourapp.com/webhooks/stripe",
});

// Create a connection with rules
const connection = await hookdeck.connection.upsert({
  name: "stripe-to-orders",
  source: { id: source.id },
  destination: { id: destination.id },
  rules: [
    {
      type: "retry",
      strategy: "exponential_backoff",
      count: 5,
      interval: 10000, // 10 seconds initial
      responseStatusCodesToRetry: [500, 502, 503, 504, 429],
    },
    {
      type: "alert",
      strategy: "last_attempt",
      channel: {
        type: "slack",
        name: "webhook-alerts",
      },
    },
  ],
});

console.log(`Source URL: ${source.url}`);
// → Set this in Stripe dashboard as your webhook endpoint
```

### Filtering and Fan-Out

```typescript
// Route different event types to different services
const checkoutConnection = await hookdeck.connection.upsert({
  name: "stripe-to-checkout",
  source: { id: source.id },
  destination: { id: checkoutDestination.id },
  rules: [
    {
      type: "filter",
      body: {
        type: {
          $match: "checkout.session.completed",
        },
      },
    },
  ],
});

const subscriptionConnection = await hookdeck.connection.upsert({
  name: "stripe-to-subscriptions",
  source: { id: source.id },
  destination: { id: subscriptionDestination.id },
  rules: [
    {
      type: "filter",
      body: {
        type: {
          $match: "customer.subscription.*",
        },
      },
    },
  ],
});
// Both connections share the same Stripe source URL
// Fan-out is handled by Hookdeck automatically
```

### Transformations

```typescript
// Transform incoming payloads before forwarding
const connection = await hookdeck.connection.upsert({
  name: "github-to-cicd",
  source: { id: githubSource.id },
  destination: { id: cicdDestination.id },
  rules: [
    {
      type: "transform",
      transformation: {
        name: "normalize-github-push",
        code: `
          addLayer({
            normalized: {
              repo: payload.repository.full_name,
              branch: payload.ref.replace('refs/heads/', ''),
              commit: payload.head_commit.id,
              author: payload.pusher.name,
            }
          });
        `,
      },
    },
  ],
});
```

### Replay Failed Events

```typescript
// Programmatically replay failed deliveries
const failedEvents = await hookdeck.event.list({
  status: "failed",
  connectionId: connection.id,
  limit: 100,
  createdAt: {
    gte: new Date(Date.now() - 24 * 60 * 60 * 1000), // last 24 hours
  },
});

console.log(`Found ${failedEvents.count} failed events`);

// Bulk retry
for (const event of failedEvents.models) {
  await hookdeck.event.retry(event.id);
}
```

---

## Convoy: Open Source Webhook Gateway

Convoy is a fully open-source webhook gateway (Apache 2.0) that you self-host on your own infrastructure. It handles both inbound and outbound webhooks, gives you full data ownership, and ships as a single Docker container.

### Docker Compose Setup

```yaml
# docker-compose.yml
version: "3.8"
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: convoy
      POSTGRES_USER: convoy
      POSTGRES_PASSWORD: convoy_secret
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

  convoy:
    image: getconvoy/convoy:latest
    ports:
      - "5005:5005"
    environment:
      CONVOY_DB_DRIVER: postgres
      CONVOY_DB_DSN: "postgres://convoy:convoy_secret@postgres:5432/convoy"
      CONVOY_REDIS_DSN: "redis://redis:6379"
      CONVOY_JWT_SECRET: "your-32-char-secret-here"
      CONVOY_API_KEY_HASH: "sha256"
    depends_on:
      - postgres
      - redis

volumes:
  postgres_data:
```

### Go SDK Usage (Primary SDK)

```go
package main

import (
    "context"
    "github.com/frain-dev/convoy-go/v2"
    "os"
)

func main() {
    c := convoy.New(
        os.Getenv("CONVOY_BASE_URL"),   // e.g., https://convoy.yourcompany.com
        os.Getenv("CONVOY_API_KEY"),
        os.Getenv("CONVOY_PROJECT_ID"),
    )

    ctx := context.Background()

    // Create an endpoint (equivalent to Svix application/endpoint)
    endpoint, err := c.Endpoints.Create(ctx, &convoy.CreateEndpointRequest{
        Name:               "Customer A Webhook",
        URL:                "https://customer-a.com/webhooks",
        Description:        "Production endpoint",
        Secret:             "custom-signing-secret",
        RateLimit:          100,
        RateLimitDuration:  "1m",
    })
    if err != nil {
        panic(err)
    }

    // Send an event
    event, err := c.EventService.CreateEvent(ctx, &convoy.CreateEventRequest{
        EndpointID: endpoint.UID,
        EventType:  "payment.completed",
        Data: []byte(`{
            "paymentId": "pay_123",
            "amount": 5000,
            "currency": "usd"
        }`),
        IdempotencyKey: "pay_123-completed-2026-03-09",
    })
    if err != nil {
        panic(err)
    }

    println("Event created:", event.UID)
}
```

### Node.js Client (HTTP API)

```typescript
import axios from "axios";

const convoy = axios.create({
  baseURL: process.env.CONVOY_BASE_URL + "/api/v1",
  headers: {
    Authorization: `Bearer ${process.env.CONVOY_API_KEY}`,
    "Content-Type": "application/json",
  },
});

// Create a fan-out event (sends to all matching endpoints in a project)
const response = await convoy.post(
  `/projects/${process.env.CONVOY_PROJECT_ID}/events/fanout`,
  {
    owner_id: "customer-tenant-123",
    event_type: "order.shipped",
    data: {
      orderId: "ord_789",
      trackingNumber: "1Z999AA1234567890",
      estimatedDelivery: "2026-03-12",
    },
    idempotency_key: `ord_789-shipped-${Date.now()}`,
  }
);

console.log("Fan-out event:", response.data.data.uid);
```

### Convoy CLI Deployment

```bash
# Install Convoy CLI
curl -L https://github.com/frain-dev/convoy/releases/latest/download/convoy-linux-amd64 -o convoy
chmod +x convoy && sudo mv convoy /usr/local/bin/

# Start server
convoy server \
  --db-dsn "postgres://convoy:secret@localhost:5432/convoy" \
  --redis-dsn "redis://localhost:6379" \
  --port 5005 \
  --log-level info

# Create organization and project via CLI
convoy project create --name "Production" --type outgoing
```

---

## Feature Comparison

| Feature | Svix | Hookdeck | Convoy |
|---------|------|----------|--------|
| **Primary use case** | Outbound (sending to customers) | Inbound (receiving from 3rd parties) | Both inbound + outbound |
| **Open source** | ❌ | ❌ | ✅ Apache 2.0 |
| **Self-hosted** | Enterprise only | ❌ | ✅ |
| **Embeddable customer portal** | ✅ First-class | ❌ | ✅ Basic |
| **Multi-tenant support** | ✅ Native | ❌ | ✅ |
| **Webhook transformations** | ❌ | ✅ | ❌ |
| **Fan-out / routing** | ✅ (filter types) | ✅ (connections) | ✅ (fan-out events) |
| **Event replay** | ✅ | ✅ | ✅ |
| **Dead letter queue** | ✅ | ✅ | ✅ |
| **SDK languages** | 12+ | 6 | Go (official), REST |
| **Local dev tunnel** | ✅ (`svix listen`) | ✅ (`hookdeck listen`) | ❌ |
| **Idempotency keys** | ✅ | ✅ | ✅ |
| **Rate limiting** | ✅ (per endpoint) | ✅ (per connection) | ✅ (per endpoint) |
| **Signature verification** | ✅ | ✅ | ✅ |
| **Event catalog / schemas** | ✅ | ❌ | ❌ |
| **Alerting / notifications** | ✅ | ✅ Slack/PagerDuty | ✅ Slack/email |
| **Free tier** | 25k events/month | 25k events/month | Unlimited (self-hosted) |

---

## When to Use Each

**Choose Svix if:**
- You're building a B2B SaaS product that sends webhooks *to* your customers
- You need an embeddable portal so customers can manage their own endpoints
- You want schema-validated event types and a proper event catalog
- TypeScript/Node.js SDK quality is a priority

**Choose Hookdeck if:**
- You're the one *receiving* webhooks from third-party services (Stripe, GitHub, Shopify)
- You want to fan-out a single Stripe webhook to multiple internal services
- You need payload transformations before events reach your app
- Local development tunneling with replay is important

**Choose Convoy if:**
- Data sovereignty or compliance requires on-premises deployment
- You need both inbound and outbound webhook handling in one self-hosted system
- Cost predictability matters more than managed convenience
- Your team is comfortable operating Go/Redis/Postgres infrastructure

**Avoid webhook infrastructure platforms entirely if:**
- Your webhook volume is under 1,000 events/month — a simple BullMQ queue with retry logic handles this fine
- You control both the sender and receiver and have no customers to expose endpoints to

---

## Reliability Deep Dive

All three implement the same core reliability pattern: exponential backoff with jitter, configurable retry counts (usually 5–10 attempts over 3 days), and dead-letter queues for events that exhaust all retries.

Where they differ:

**Svix** adds delivery rate analytics per endpoint and per event type. You can detect when a customer's endpoint is consistently failing and proactively notify them.

**Hookdeck** adds a 10-second response window check — if your endpoint doesn't respond within 10 seconds, Hookdeck treats it as a timeout and queues for retry. This prevents slow endpoints from blocking the delivery queue.

**Convoy** requires you to configure your own monitoring and alerting. The built-in dashboard shows delivery status but doesn't send proactive alerts by default.

---

## Pricing Comparison (2026)

| Tier | Svix | Hookdeck | Convoy |
|------|------|----------|--------|
| **Free** | 25k events/mo | 25k events/mo | Unlimited (self-hosted) |
| **Developer** | ~$75/mo (250k events) | ~$100/mo (500k events) | Cloud: ~$99/mo |
| **Pro** | ~$350/mo (2M events) | ~$350/mo (5M events) | Cloud: ~$299/mo |
| **Self-hosted** | Enterprise only | ❌ | ✅ Free |
| **Pricing model** | Event volume | Event volume | Event volume / seats |

*Pricing as of Q1 2026. Verify current plans at each vendor's website.*

---

## Webhook Security: Signature Verification Patterns

Webhook signature verification is a security requirement that all three platforms implement, but the implementation details matter for correctness. The canonical pattern is HMAC-SHA256 signing: the platform signs the request payload with a shared secret and includes the signature in a header. Your endpoint verifies the signature before processing the payload, preventing attackers from sending forged webhook events to your endpoint.

Svix's signature verification uses their SDK's `Webhook.verify()` method, which handles timestamp validation (rejecting webhooks older than 5 minutes to prevent replay attacks) and constant-time comparison (preventing timing attacks that could reveal the secret). The timestamp and message ID are included in the signature computation, so the same payload cannot be replayed with a different ID. Svix's SDK makes correct implementation straightforward — the risk of accidental insecure implementation is low because the SDK hides the cryptographic details.

Hookdeck's signature verification follows a similar HMAC-SHA256 pattern with per-source signing secrets. Hookdeck also provides request identity verification: each inbound source has a source token that identifies where the event came from, separate from the payload signature. Convoy's signature verification supports multiple algorithms (HMAC-SHA256, HMAC-SHA512, SHA256) and allows configuring the signature header name, which is useful when migrating from an existing webhook implementation that uses a custom header name. For teams building on self-hosted Convoy, understanding the signature verification configuration is more involved than with the managed services, but the documentation is thorough.

## Methodology

Data sourced from official documentation, GitHub repositories, npm download trends, and community discussions on Discord and GitHub Issues. Star counts: Svix (3.4k), Hookdeck (1.7k), Convoy (4.2k) as of January 2026. Pricing based on published tiers; enterprise pricing requires contacting sales. Code examples verified against current SDK versions: Svix 1.x, Hookdeck SDK 0.5.x, Convoy Go SDK v2.

---

*Need to compare webhook infrastructure against message queues? See [BullMQ vs Inngest vs Trigger.dev](https://www.pkgpulse.com/compare/bullmq-vs-inngest) for job queue alternatives, or explore [Redpanda vs NATS vs Apache Kafka](/guides/redpanda-vs-nats-vs-apache-kafka-event-streaming-2026) for high-throughput event streaming.*

*See also: [oRPC vs tRPC v11 vs Hono RPC 2026](/guides/orpc-vs-trpc-vs-hono-rpc-type-safe-apis-2026) and [Portkey vs LiteLLM vs OpenRouter: LLM Gateway 2026](/guides/portkey-vs-litellm-vs-openrouter-llm-gateway-2026)*
