diff --git a/apps/web/app/compare/ngrok/page.tsx b/apps/web/app/compare/ngrok/page.tsx index 001a4d88..84c28676 100644 --- a/apps/web/app/compare/ngrok/page.tsx +++ b/apps/web/app/compare/ngrok/page.tsx @@ -9,10 +9,15 @@ const FAQ_ITEMS = [ answer: "webhooks.cc is a strong ngrok alternative if your primary use case is webhook testing. It captures, inspects, and replays webhooks with a TypeScript SDK for CI assertions and an MCP server for AI agents. Other alternatives include LocalTunnel (free open-source tunnel), Smee.io (GitHub's webhook proxy), and Hookdeck (production webhook infrastructure).", }, + { + question: "ngrok vs Hookdeck: which is better for webhook testing?", + answer: + "ngrok is a general-purpose tunnel that exposes local ports over HTTP/TCP and inspects traffic only while the agent runs. Hookdeck is production webhook infrastructure with an event gateway, retries, and hookdeck listen for local forwarding. If your goal is testing webhooks in development and CI, webhooks.cc combines both strengths: a permanent capture URL, whk tunnel to localhost, 32-provider signature verification, a TypeScript SDK for CI assertions, and an MCP server.", + }, { question: "Can I replace ngrok with webhooks.cc?", answer: - "For webhook testing, yes. webhooks.cc captures incoming webhooks, stores request history, and forwards to localhost via the CLI tunnel. If you use ngrok only to receive webhooks during development, webhooks.cc covers that workflow and adds inspection, replay, SDK assertions, and MCP support. If you use ngrok for general-purpose tunneling (exposing web apps, TCP services, databases), ngrok remains the better fit — webhooks.cc is purpose-built for webhooks.", + "For webhook testing, yes. webhooks.cc captures incoming webhooks, stores request history, and forwards to localhost via the CLI tunnel. If you use ngrok only to receive webhooks during development, webhooks.cc covers that workflow and adds inspection, replay, SDK assertions, and MCP support. If you use ngrok for general-purpose tunneling (exposing web apps, TCP services, databases), ngrok remains the better fit because webhooks.cc is purpose-built for webhooks.", }, { question: "How does the webhooks.cc CLI tunnel compare to ngrok's tunnel?", @@ -22,7 +27,7 @@ const FAQ_ITEMS = [ { question: "Is webhooks.cc free?", answer: - "Yes. Every individual feature is included on both tiers — webhook capture, inspection, replay, mock responses, CLI tunnel, TypeScript SDK, and MCP server. The paid plan increases rate limits and retention, and Teams (invite members, share endpoints) is billed separately at $12/seat/mo. ngrok also has a free tier, but some features (like custom domains and IP restrictions) require paid plans.", + "Yes. Every individual feature is included on both tiers: webhook capture, inspection, replay, mock responses, CLI tunnel, TypeScript SDK, and MCP server. The paid plan increases rate limits and retention, and Teams (invite members, share endpoints) is billed separately at $12/seat/mo. ngrok also has a free tier, but some features (like custom domains and IP restrictions) require paid plans.", }, { question: "Does ngrok have a webhook testing SDK?", @@ -54,11 +59,11 @@ const ROWS = [ "Local inspector (localhost:4040), dashboard Traffic Inspector", ], ["CLI tunnel to localhost", "Yes (whk tunnel)", "Yes (ngrok http)"], - ["Mock responses", "Yes — configurable per endpoint", "No"], + ["Mock responses", "Yes, configurable per endpoint", "No"], ["Request replay", "Yes, to any URL", "Yes, to the local upstream"], - ["TypeScript SDK", "Yes — @webhooks-cc/sdk", "No webhook-testing SDK"], + ["TypeScript SDK", "Yes (@webhooks-cc/sdk)", "No webhook-testing SDK"], ["CI test assertions", "Yes (waitFor + matchers)", "No"], - ["MCP server for AI agents", "Yes — @webhooks-cc/mcp", "No"], + ["MCP server for AI agents", "Yes (@webhooks-cc/mcp)", "No"], ["TCP / non-HTTP tunneling", "No (HTTP webhooks only)", "Yes"], ["Custom domains", "No", "Yes (paid)"], ["Edge computing / traffic policy", "No", "Yes (paid)"], @@ -68,13 +73,14 @@ const ROWS = [ ] as const; export const metadata = createPageMetadata({ - title: "ngrok Alternative for Webhook Testing (2026)", + title: "ngrok Alternative: vs Hookdeck & Webhook.site (2026)", description: - "ngrok alternative built for webhook testing. Capture, inspect, and replay webhooks with SDK assertions, CLI tunnel, and MCP for AI agents.", + "Compare ngrok vs webhooks.cc, Hookdeck, and Webhook.site for webhook testing: stored request history, CLI tunnel to localhost, replay, CI SDK, and MCP server.", path: "/compare/ngrok", keywords: [ "ngrok alternative", "ngrok alternatives", + "ngrok vs hookdeck", "best ngrok alternative", "ngrok alternative for webhooks", "ngrok webhook testing", @@ -104,7 +110,7 @@ export default function CompareNgrokPage() {

webhooks.cc vs ngrok

If you use ngrok mainly for webhook development, webhooks.cc is a purpose-built - alternative that captures, inspects, replays, and forwards webhooks to localhost — with a + alternative that captures, inspects, replays, and forwards webhooks to localhost with a TypeScript SDK, CLI tunnel, and MCP server included. ngrok is a general-purpose tunnel for exposing any local service to the internet. Different tools, different focus.

@@ -149,7 +155,7 @@ export default function CompareNgrokPage() { ngrok tunnels traffic to your local server and shows it in a local inspector at localhost:4040 while the agent runs, with replay to the same upstream; its dashboard Traffic Inspector keeps traffic for longer. webhooks.cc captures every request - automatically — headers, body, query params, IP, timing — and makes it searchable, + automatically (headers, body, query params, IP, timing) and makes it searchable, exportable, and replayable from the dashboard, CLI, or SDK.

@@ -158,7 +164,7 @@ export default function CompareNgrokPage() {

Testing and CI integration

webhooks.cc provides a TypeScript SDK with{" "} - waitFor() — create + waitFor(): create an endpoint, trigger your integration, and assert on the captured webhook in your test suite. This works in CI without opening any tunnels. ngrok is designed for runtime tunneling, not test-time assertions. @@ -169,11 +175,11 @@ export default function CompareNgrokPage() {

Scope and complexity

ngrok supports TCP tunnels, custom domains, traffic policies, IP restrictions, and - edge computing — features that go well beyond webhooks. If you need those, ngrok is - the right tool. If your goal is to test and debug webhook integrations, webhooks.cc - does that with less setup and more webhook-specific tooling. Team collaboration on - webhooks.cc costs $12/seat/month, while ngrok's pay-as-you-go plan starts at - $20/month with 3 seats included and $5 per additional seat, plus metered usage. + edge computing, which go well beyond webhooks. If you need those, ngrok is the right + tool. If your goal is to test and debug webhook integrations, webhooks.cc does that + with less setup and more webhook-specific tooling. Team collaboration on webhooks.cc + costs $12/seat/month, while ngrok's pay-as-you-go plan starts at $20/month with 3 + seats included and $5 per additional seat, plus metered usage.

@@ -236,6 +242,13 @@ export default function CompareNgrokPage() { vs Hookdeck {" · "} + + vs Stripe CLI + + {" · "} All comparisons diff --git a/apps/web/app/compare/page.tsx b/apps/web/app/compare/page.tsx index 04920539..e5dfdc62 100644 --- a/apps/web/app/compare/page.tsx +++ b/apps/web/app/compare/page.tsx @@ -6,13 +6,16 @@ import { FAQAccordion } from "@/components/landing/faq-accordion"; export const metadata = createPageMetadata({ title: "Webhook Testing Tools Compared: Features & Pricing (2026)", description: - "One feature checklist for webhooks.cc, Webhook.site, RequestBin, Beeceptor, Hookdeck, ngrok, Smee.io and LocalTunnel: history, replay, mocks, CLI tunnel, CI assertions, MCP and team pricing.", + "One feature checklist for webhooks.cc, Webhook.site, RequestBin, Beeceptor, Hookdeck, ngrok, Svix, Stripe CLI, Smee.io and LocalTunnel: history, replay, CLI, CI SDK and MCP.", path: "/compare", keywords: [ "webhook testing tool comparison", "webhook testing alternatives", "webhook.site alternative", "ngrok alternative", + "ngrok vs hookdeck", + "svix alternative", + "stripe cli alternative", "requestbin alternative", "beeceptor alternative", "hookdeck alternative", @@ -35,6 +38,24 @@ const COMPARISONS = [ summary: "ngrok is a general-purpose tunnel. webhooks.cc is a webhook testing platform with capture, replay, SDK, and MCP built in.", }, + { + href: "/compare/hookdeck", + title: "webhooks.cc vs Hookdeck", + summary: + "Hookdeck is production webhook infrastructure (retries, routing). webhooks.cc is a development testing tool. They complement each other.", + }, + { + href: "/compare/svix", + title: "webhooks.cc vs Svix", + summary: + "Svix is production webhook infrastructure (outbound delivery, Svix Ingest, and Svix Play). webhooks.cc tests inbound webhooks and emails across 32+ signed providers with SDK assertions and MCP.", + }, + { + href: "/compare/stripe-cli", + title: "webhooks.cc vs Stripe CLI", + summary: + "Stripe CLI forwards Stripe events to localhost. webhooks.cc brings that CLI tunnel workflow, persistent history, and CI SDK signing to 32+ webhook providers and inbound email.", + }, { href: "/compare/requestbin", title: "webhooks.cc vs RequestBin (Pipedream)", @@ -47,12 +68,6 @@ const COMPARISONS = [ summary: "Beeceptor excels at API mocking. webhooks.cc excels at capturing and testing real webhook payloads with SDK and MCP.", }, - { - href: "/compare/hookdeck", - title: "webhooks.cc vs Hookdeck", - summary: - "Hookdeck is production webhook infrastructure (retries, routing). webhooks.cc is a development testing tool. They complement each other.", - }, { href: "/compare/smee", title: "webhooks.cc vs Smee.io", @@ -63,7 +78,7 @@ const COMPARISONS = [ href: "/compare/localtunnel", title: "webhooks.cc vs LocalTunnel", summary: - "LocalTunnel is a free, simple tunnel. webhooks.cc tunnels too — and also captures, inspects, replays, and tests every request.", + "LocalTunnel is a free, simple tunnel. webhooks.cc tunnels too, and also captures, inspects, replays, and tests every request.", }, ] as const; @@ -74,6 +89,8 @@ const TOOLS = [ { name: "Beeceptor", href: "/compare/beeceptor" }, { name: "Hookdeck", href: "/compare/hookdeck" }, { name: "ngrok", href: "/compare/ngrok" }, + { name: "Svix", href: "/compare/svix" }, + { name: "Stripe CLI", href: "/compare/stripe-cli" }, { name: "Smee.io", href: "/compare/smee" }, { name: "LocalTunnel", href: "/compare/localtunnel" }, ] as const; @@ -89,6 +106,8 @@ const MATRIX: readonly (readonly [string, ...string[]])[] = [ "Request log", "Event log", "Local inspector while running", + "Svix Play / Cloud logs", + "In Stripe Dashboard only", "No", "No", ], @@ -100,14 +119,52 @@ const MATRIX: readonly (readonly [string, ...string[]])[] = [ "No", "Redelivery", "To local upstream", + "Resend deliveries", + "stripe events resend", + "No", + "No", + ], + [ + "Mock responses", + "Yes", + "Paid", + "Via workflow code", + "Yes, rule-based", + "No", + "No", + "Static in Play", + "No", + "No", + "No", + ], + [ + "CLI tunnel to localhost", + "Yes", + "Yes", + "No", + "No", + "Yes", + "Yes", + "Yes (Play)", + "Stripe only", + "Yes", + "Yes", + ], + ["SDK assertions in CI", "Yes", "No", "No", "No", "No", "No", "No", "No", "No", "No"], + [ + "MCP server", + "Yes", + "No", + "No", + "No", + "Operations only", + "No", + "App Portal MCP", + "Stripe API ops", "No", "No", ], - ["Mock responses", "Yes", "Paid", "Via workflow code", "Yes, rule-based", "No", "No", "No", "No"], - ["CLI tunnel to localhost", "Yes", "Yes", "No", "No", "Yes", "Yes", "Yes", "Yes"], - ["SDK assertions in CI", "Yes", "No", "No", "No", "No", "No", "No", "No"], - ["MCP server", "Yes", "No", "No", "No", "Operations only", "No", "No", "No"], - ["Open source", "Yes", "No", "No", "No", "Partially", "Agent only", "Yes", "Yes"], + ["Open source", "Yes", "No", "No", "No", "Partially", "Agent only", "Yes", "Yes", "Yes", "Yes"], [ "Team plan", "$12/seat/mo, pooled quota", @@ -116,6 +173,8 @@ const MATRIX: readonly (readonly [string, ...string[]])[] = [ "Team+ $25/mo", "Team+ $39/mo", "$20/mo incl. 3 seats", + "Cloud plans, from $20/mo", + "Via Stripe account", "None", "None", ], @@ -131,7 +190,7 @@ const FAQ_ITEMS: FAQItem[] = [ { question: "Which webhook tools have a CLI tunnel to localhost and a free tier?", answer: - "webhooks.cc (whk tunnel, free plan with 50 requests a day and every feature), Hookdeck (hookdeck listen, free tier with limited events), ngrok (free tier), and the free, open-source Smee.io and LocalTunnel. Only webhooks.cc and Hookdeck also keep a stored history of what arrived.", + "webhooks.cc (whk tunnel, free plan with 50 requests a day and every feature), Hookdeck (hookdeck listen, free tier with limited events), ngrok (free tier), Stripe CLI (stripe listen, for Stripe events only), and the free, open-source Smee.io and LocalTunnel. Only webhooks.cc and Hookdeck also keep a stored cross-provider history of what arrived.", }, { question: "What should I compare when choosing a paid webhook testing tool?", diff --git a/apps/web/app/compare/stripe-cli/page.tsx b/apps/web/app/compare/stripe-cli/page.tsx new file mode 100644 index 00000000..1057017d --- /dev/null +++ b/apps/web/app/compare/stripe-cli/page.tsx @@ -0,0 +1,289 @@ +import Link from "next/link"; +import { createPageMetadata } from "@/lib/seo"; +import { JsonLd, breadcrumbSchema, faqSchema } from "@/lib/schemas"; +import { ComparisonCTA } from "@/components/compare/comparison-cta"; + +const FAQ_ITEMS = [ + { + question: + "Are there any webhook tools with a CLI that forwards to localhost like the Stripe CLI does?", + answer: + "Yes. The webhooks.cc CLI (whk tunnel ) works like stripe listen --forward-to, but for any HTTP webhook provider. Every HTTP request sent to https://go.webhooks.cc/w/{slug} is captured in a searchable history, verified, and forwarded to your local server. For inbound emails sent to {slug}@mailhooks.cc, you can configure endpoint forwarding to post each captured email as signed JSON to your server or test locally with the SDK and dashboard.", + }, + { + question: "Can webhooks.cc replace stripe listen for testing Stripe webhooks?", + answer: + "You can use either or both. stripe listen streams events directly from your Stripe account over WebSockets. webhooks.cc gives you a permanent HTTPS URL you can register in Stripe test mode (so deliveries are stored even when your laptop is closed), sends signed Stripe templates (checkout.session.completed, invoice.paid, customer.subscription.updated) via the SDK or dashboard without needing the Stripe CLI installed in CI, and forwards live deliveries to localhost with whk tunnel.", + }, + { + question: "How do I test Stripe webhooks in CI/CD without running stripe listen?", + answer: + "Use sendTo() from @webhooks-cc/sdk in your Vitest, Jest, or Playwright test. Call client.sendTo('http://localhost:3000/api/webhooks/stripe', { provider: 'stripe', template: 'checkout.session.completed', secret: process.env.STRIPE_WEBHOOK_SECRET }). The SDK generates a valid t=timestamp,v1=hash stripe-signature header that passes stripe.webhooks.constructEvent() with no background CLI process required.", + }, + { + question: "What providers does webhooks.cc support besides Stripe?", + answer: + "webhooks.cc captures webhooks from any HTTP sender and includes built-in signature verification and signed payload templates for 32+ providers, including GitHub, Shopify, Clerk, Resend, Linear, Slack, Twilio, Discord, Paddle, Lemon Squeezy, Coinbase Commerce, Square, DocuSign, Adyen, and PayPal.", + }, + { + question: "Does the Stripe CLI store webhook history or support team sharing?", + answer: + "Stripe logs webhook events in the Stripe Dashboard for your Stripe account, while stripe listen prints events to your local terminal session. webhooks.cc provides a unified, searchable inbox across all your webhook and email providers, with one-click replay to any URL and shared team endpoints ($12/seat/mo).", + }, +]; + +const ROWS = [ + [ + "Supported providers", + "All HTTP webhooks + 32 signed providers (Stripe, GitHub, Shopify, Clerk, etc.)", + "Stripe only", + ], + [ + "Localhost CLI forwarding", + "Yes (whk tunnel 3000 for HTTP webhooks)", + "Yes (stripe listen --forward-to localhost:3000)", + ], + [ + "Inbound email capture & forwarding", + "Yes ({slug}@mailhooks.cc + HTTP forwarding as signed JSON)", + "No", + ], + [ + "Captures when CLI is offline", + "Yes (hosted endpoint stores every delivery)", + "Only in Stripe Dashboard Event logs", + ], + [ + "Signed test events in CI without CLI daemon", + "Yes (client.sendTo() with valid stripe-signature)", + "Requires stripe trigger + running stripe listen", + ], + [ + "Cross-provider request search & diff", + "Yes (full-text search + side-by-side payload diff)", + "No", + ], + [ + "Replay to any URL", + "Yes (dashboard, CLI, SDK, or MCP)", + "stripe events resend (to Stripe URL)", + ], + ["Mock custom status codes & bodies", "Yes (configurable per endpoint)", "No"], + ["MCP server for AI coding agents", "Yes (@webhooks-cc/mcp, 38 tools)", "Stripe MCP (API ops)"], + ["Open source", "Yes (AGPL + MIT)", "Yes (Apache 2.0 CLI)"], +] as const; + +export const metadata = createPageMetadata({ + title: "Stripe CLI Alternative: Multi-Provider Webhook & Email CLI", + description: + "Compare webhooks.cc (whk CLI) vs Stripe CLI (stripe listen): forward webhooks to localhost across 32+ providers, capture inbound emails, and test in CI with SDK.", + path: "/compare/stripe-cli", + keywords: [ + "stripe cli webhook test", + "stripe listen alternative", + "stripe cli alternative", + "forward webhooks to localhost cli", + "email webhook cli localhost", + "test stripe webhooks in ci", + "webhooks.cc vs stripe cli", + ], +}); + +export default function CompareStripeCliPage() { + return ( +
+ + + +
+

+ Comparison · Updated October 2026 +

+

webhooks.cc vs Stripe CLI

+

+ The Stripe CLI ( + stripe listen and{" "} + stripe trigger) set the + standard for local Stripe webhook testing. webhooks.cc brings that same workflow to every + webhook and email provider in your stack: a fast Go CLI tunnel ( + whk tunnel), a hosted + request and email inbox that captures deliveries even when your terminal is closed, and a + TypeScript SDK that sends genuine{" "} + stripe-signature{" "} + payloads in CI without a background daemon. +

+ + {/* Feature table */} +

Feature comparison

+
+ + + + + + + + + + {ROWS.map(([label, left, right]) => ( + + + + + + ))} + +
+ Feature + + webhooks.cc (whk CLI + SDK) + + Stripe CLI (stripe listen) +
{label}{left}{right}
+
+ + + + {/* Key differences */} +

Key differences

+
+
+

+ One platform for Stripe, GitHub, Clerk, Shopify, and inbound email +

+

+ Modern apps rarely receive webhooks from Stripe alone; they also handle Clerk or + WorkOS auth hooks, GitHub or Vercel deploy hooks, and Resend or Postmark email events. + Instead of juggling separate tunnels and provider CLIs,{" "} + whk tunnel 3000{" "} + forwards any provider's HTTP webhooks to your local server with automatic + signature verification, while inbound emails sent to{" "} + + {"{slug}"}@mailhooks.cc + {" "} + are captured with OTP extraction and can be forwarded to your server as signed JSON or + asserted in tests via the SDK. +

+
+ +
+

+ Deterministic CI tests without a background CLI daemon +

+

+ Running{" "} + stripe listen{" "} + inside GitHub Actions requires Stripe API keys, a background WebSocket process, and + network round-trips. With{" "} + @webhooks-cc/sdk, + your Vitest or Playwright test calls{" "} + + { + "client.sendTo(url, { provider: 'stripe', template: 'checkout.session.completed', secret })" + } + {" "} + to post a payload signed with the exact{" "} + t=...,v1=...{" "} + HMAC-SHA256 format that{" "} + + stripe.webhooks.constructEvent() + {" "} + expects. +

+
+ +
+

+ Persistent capture when your laptop is closed +

+

+ When you stop{" "} + stripe listen, + local forwarding stops. A webhooks.cc endpoint URL stays live 24/7 for your staging or + sandbox environment, captures every delivery with headers and timing, and lets any + teammate inspect or replay a payload to their own{" "} + localhost with one + click. +

+
+
+ + {/* When to choose */} +
+
+

Choose webhooks.cc when you need

+
    +
  • A Stripe-CLI-style workflow across 32+ webhook providers and email
  • +
  • Signed Stripe, GitHub, Shopify, and Clerk payloads in CI without a tunnel
  • +
  • A permanent URL that captures webhooks while your terminal is closed
  • +
  • Shared staging endpoints for your team ($12/seat/mo)
  • +
  • MCP tools so Claude Code or Cursor can trigger and inspect webhooks
  • +
+
+
+

Choose Stripe CLI when you need

+
    +
  • Direct WebSocket streaming from your Stripe account with zero URL setup
  • +
  • Triggering real test-mode objects inside Stripe's API via stripe trigger
  • +
  • Stripe-only local development where no other webhook providers are used
  • +
  • Managing Stripe resources and logs from the terminal
  • +
+
+
+ + {/* FAQ */} +

Frequently asked questions

+
+ {FAQ_ITEMS.map((item) => ( +
+

{item.question}

+

{item.answer}

+
+ ))} +
+ + {/* More comparisons */} +

+ See also:{" "} + + Stripe Webhook Tester + + {" · "} + + vs ngrok + + {" · "} + + vs Hookdeck + + {" · "} + + vs Svix + + {" · "} + + All comparisons + +

+ + {/* CTA */} + +
+
+ ); +} diff --git a/apps/web/app/compare/svix/page.tsx b/apps/web/app/compare/svix/page.tsx new file mode 100644 index 00000000..fb62c50d --- /dev/null +++ b/apps/web/app/compare/svix/page.tsx @@ -0,0 +1,260 @@ +import Link from "next/link"; +import { createPageMetadata } from "@/lib/seo"; +import { JsonLd, breadcrumbSchema, faqSchema } from "@/lib/schemas"; +import { ComparisonCTA } from "@/components/compare/comparison-cta"; + +const FAQ_ITEMS = [ + { + question: "What is the difference between webhooks.cc and Svix?", + answer: + "Svix is webhook infrastructure: its core product dispatches outbound webhooks to your customers with retries and an embeddable App Portal, alongside Svix Ingest for managed incoming webhook queuing and Svix Play for basic browser inspection. webhooks.cc is a developer testing platform built for local development and CI/CD: it gives you endpoints that capture webhooks and inbound emails, generates signed test payloads for 32+ providers, forwards HTTP webhooks to localhost via whk tunnel, and provides a TypeScript SDK with waitFor() assertions and a 38-tool MCP server.", + }, + { + question: "webhooks.cc vs Svix Play and Svix Ingest: how do they compare for webhook testing?", + answer: + "Svix Play is Svix's free browser-based request bin for viewing incoming HTTP requests, while Svix Ingest is production ingestion infrastructure that receives third-party webhooks (such as Stripe or GitHub) and forwards them into your backend. webhooks.cc focuses on developer and CI testing: it combines an instant capture URL with CLI tunneling to localhost (whk tunnel), 32-provider signature generation and verification, inbound email capture (@mailhooks.cc), a TypeScript SDK with waitFor() matchers for Vitest/Jest/Playwright, and a 38-tool MCP server for AI coding agents.", + }, + { + question: "Can webhooks.cc verify Svix and Standard Webhooks signatures?", + answer: + "Yes. Both the webhooks.cc dashboard and @webhooks-cc/sdk verify Standard Webhooks (webhook-id, webhook-timestamp, webhook-signature) and Svix headers (svix-id, svix-timestamp, svix-signature) used by providers like Clerk and Resend, alongside 30 other provider signing schemes including Stripe, GitHub, Shopify, Slack, Discord Ed25519, and PayPal RSA-SHA256.", + }, + { + question: "Can I use webhooks.cc and Svix together?", + answer: + "Yes. Teams that use Svix to send outbound webhooks to customers often point test destinations in Svix at a webhooks.cc endpoint so their CI suite can assert that outbound events are delivered, properly signed with svix-signature, and formatted as expected.", + }, + { + question: "How does pricing compare between webhooks.cc and Svix?", + answer: + "webhooks.cc includes every individual feature on its Free tier (50 requests/day), costs $8/month for Pro (100,000 requests/month), and bills Teams at $12/seat/month with pooled quota. Svix offers open-source self-hosting and Svix Play for basic inspection, while hosted Svix Cloud plans start at $20/month (Basic) and $490/month (Professional) after the free developer tier.", + }, +]; + +const ROWS = [ + [ + "Primary focus", + "Inbound webhook & email testing platform", + "Outbound webhook delivery + Svix Ingest & Svix Play", + ], + [ + "Webhook capture & history", + "Yes, searchable history + full headers/body", + "Svix Play for basic bin; delivery logs in Svix Cloud / Ingest", + ], + [ + "Provider signature verification", + "32 providers (Stripe, GitHub, Svix, Clerk, Discord, etc.)", + "Standard Webhooks / Svix + major providers in Svix Ingest", + ], + [ + "Signed test payload templates", + "34 providers (send signed events in 1 click or SDK)", + "Standard Webhooks test events", + ], + ["CLI tunnel to localhost", "Yes (whk tunnel)", "Yes (svix listen for Play endpoints)"], + ["Mock responses", "Yes, configurable status, headers & body", "Static response in Svix Play"], + ["Request replay", "Yes, to any URL from dashboard, CLI, or SDK", "Resend delivery attempts"], + ["CI test assertions (waitFor)", "Yes (@webhooks-cc/sdk matchers)", "No inbound assertion SDK"], + ["Inbound email capture", "Yes ({slug}@mailhooks.cc + OTP extraction)", "No"], + [ + "MCP server for AI agents", + "Yes (@webhooks-cc/mcp, 38 testing tools)", + "App Portal MCP (inspect deliveries & replay)", + ], + ["Outbound customer portal", "No", "Yes (core Svix feature)"], + ["Open source", "Yes (AGPL + MIT)", "Yes (MIT server + libraries)"], + ["Team collaboration", "$12/seat/mo, pooled quota", "Included on Svix Cloud tiers"], +] as const; + +export const metadata = createPageMetadata({ + title: "Svix Alternative for Webhook Testing: vs Svix Play (2026)", + description: + "Compare webhooks.cc vs Svix, Svix Ingest, and Svix Play: webhook capture, 32-provider signature verification, CLI tunnel to localhost, CI SDK, and MCP.", + path: "/compare/svix", + keywords: [ + "svix alternative", + "svix vs webhooks.cc", + "svix play alternative", + "svix ingest alternative", + "svix webhook testing", + "standard webhooks tester", + "webhook testing tools", + ], +}); + +export default function CompareSvixPage() { + return ( +
+ + + +
+

+ Comparison · Updated October 2026 +

+

webhooks.cc vs Svix

+

+ Svix is production webhook infrastructure: its core platform sends outbound webhooks to + your customers, paired with Svix Ingest for receiving production webhooks and Svix Play as + a free browser request bin. webhooks.cc is a developer testing platform built for + capturing, verifying, replaying, and asserting on webhooks and inbound email across 32+ + providers in local development and CI/CD. +

+ + {/* Feature table */} +

Feature comparison

+
+ + + + + + + + + + {ROWS.map(([label, left, right]) => ( + + + + + + ))} + +
+ Feature + + webhooks.cc + + Svix / Svix Ingest / Svix Play +
{label}{left}{right}
+
+ + + + {/* Key differences */} +

Key differences

+
+
+

+ Developer testing platform vs production webhook infrastructure +

+

+ Svix is built first for production webhook delivery: dispatching your product's + outbound webhooks to customer endpoints (with an embeddable App Portal) and ingesting + production events via Svix Ingest. webhooks.cc is built specifically for local + development, staging, and CI testing: it generates properly signed test payloads and + verifies signatures for 32 providers out of the box (including Svix's own{" "} + svix-signature and{" "} + webhook-signature{" "} + headers). +

+
+ +
+

CI/CD assertions vs manual browser bins

+

+ Svix Play gives you a browser bin for quick manual checks, similar to RequestBin. + webhooks.cc adds{" "} + @webhooks-cc/sdk{" "} + with withEndpoint() + , waitFor(), and{" "} + sendTo() so your + Vitest, Jest, or Playwright suite can verify webhook handlers deterministically in CI + without running a tunnel. +

+
+ +
+

AI agent MCP server and email capture

+

+ Every webhooks.cc endpoint also receives email at{" "} + + {"{slug}"}@mailhooks.cc + {" "} + with automatic OTP and magic-link extraction, and the{" "} + @webhooks-cc/mcp{" "} + server exposes 38 testing tools to Claude Code, Cursor, and Codex (creating endpoints, + sending signed provider templates, waiting for deliveries, and replaying to + localhost). Svix offers an App Portal MCP focused on inspecting and replaying messages + on existing Svix endpoints, and does not capture inbound email. +

+
+
+ + {/* When to choose */} +
+
+

Choose webhooks.cc when you need

+
    +
  • Capture, inspect, and replay inbound webhooks from 32+ providers
  • +
  • Send signed Stripe, GitHub, Shopify, Clerk, or Svix test templates
  • +
  • Automated webhook and signup-email assertions in CI tests
  • +
  • CLI localhost forwarding (whk tunnel) with stored history
  • +
  • MCP tools for AI coding agents (Claude Code, Cursor, Codex)
  • +
+
+
+

Choose Svix when you need

+
    +
  • An outbound webhook delivery service for your own SaaS product
  • +
  • An embeddable portal where your customers manage their endpoints
  • +
  • Managed production webhook queuing and retries (Svix Outbound / Ingest)
  • +
  • A quick unauthenticated browser bin (Svix Play) with no test SDK needed
  • +
+
+
+ + {/* FAQ */} +

Frequently asked questions

+
+ {FAQ_ITEMS.map((item) => ( +
+

{item.question}

+

{item.answer}

+
+ ))} +
+ + {/* More comparisons */} +

+ See also:{" "} + + vs Hookdeck + + {" · "} + + vs Stripe CLI + + {" · "} + + vs Webhook.site + + {" · "} + + All comparisons + +

+ + {/* CTA */} + +
+
+ ); +} diff --git a/apps/web/lib/changelog.ts b/apps/web/lib/changelog.ts index bb046256..4d35cbb9 100644 --- a/apps/web/lib/changelog.ts +++ b/apps/web/lib/changelog.ts @@ -15,13 +15,25 @@ export const TRACK_LABELS: Record = { mcp: "MCP", }; -export const APP_VERSION = "0.40.1"; +export const APP_VERSION = "0.40.2"; export const CLI_VERSION = "1.4.1"; export const SDK_VERSION = "1.15.0"; export const MCP_VERSION = "1.12.1"; export const CHANGELOG: ChangelogEntry[] = [ // ─── Web App ──────────────────────────────────────────────────────── + { + version: "0.40.2", + date: "2026-10-20", + title: "Svix and Stripe CLI Comparisons, 10 Provider Reference Tables, and Search Hygiene", + track: "web", + items: [ + "New side-by-side comparison pages for Svix (/compare/svix) and Stripe CLI (/compare/stripe-cli), plus updated ngrok and comparison hub checklists", + "Event and signature reference tables and FAQs on the Linear, DocuSign, SendGrid, Paddle, Intercom, Square, Cal.com, Coinbase Commerce, Adyen, and Checkout.com webhook pages", + "Clarified SDK verifySignature() reference vs the signature verification guide, canonicalized the comparison guide to /compare, and expanded MCP server registry and client documentation", + "Permanent redirects for /webhooks/amazon-sns and legacy comparison URLs, plus explicit X-Robots-Tag noindex headers on private routes", + ], + }, { version: "0.40.1", date: "2026-10-19", diff --git a/apps/web/lib/seo.ts b/apps/web/lib/seo.ts index c620806b..fce25f1a 100644 --- a/apps/web/lib/seo.ts +++ b/apps/web/lib/seo.ts @@ -19,7 +19,7 @@ const EMAIL_PAGES_UPDATED = new Date("2026-10-08T00:00:00.000Z"); // The Search Console pass: the public Teams page, the landing page that links // it, and comparison pages with fresh tables and the questions people (and AI // search) ask about them. -const SEO_PASS_UPDATED = new Date("2026-10-13T00:00:00.000Z"); +const SEO_PASS_UPDATED = new Date("2026-10-20T00:00:00.000Z"); export interface SitemapPageDefinition { path: string; @@ -78,6 +78,18 @@ export const PUBLIC_SITEMAP_PAGES: readonly SitemapPageDefinition[] = [ priority: 0.6, lastModified: SEO_PASS_UPDATED, }, + { + path: "/compare/svix", + changeFrequency: "monthly", + priority: 0.6, + lastModified: SEO_PASS_UPDATED, + }, + { + path: "/compare/stripe-cli", + changeFrequency: "monthly", + priority: 0.6, + lastModified: SEO_PASS_UPDATED, + }, { path: "/compare/smee", changeFrequency: "monthly", priority: 0.6 }, { path: "/compare/localtunnel", @@ -86,9 +98,9 @@ export const PUBLIC_SITEMAP_PAGES: readonly SitemapPageDefinition[] = [ lastModified: SEO_PASS_UPDATED, }, // Blog pages are dynamically generated from Supabase in app/sitemap.ts - // /api-explorer is noindexed — client-only Scalar viewer, /docs/api owns this query + // /api-explorer is noindexed - client-only Scalar viewer, /docs/api owns this query { path: "/installation", changeFrequency: "monthly", priority: 0.8 }, - // /login is noindexed — excluded from sitemap intentionally + // /login is noindexed - excluded from sitemap intentionally { path: "/changelog", changeFrequency: "weekly", priority: 0.5 }, { path: "/support", changeFrequency: "monthly", priority: 0.6 }, { path: "/privacy", changeFrequency: "yearly", priority: 0.3, lastModified: LEGAL_PAGES_UPDATED }, diff --git a/apps/web/lib/webhook-provider-pages.test.ts b/apps/web/lib/webhook-provider-pages.test.ts index 20daec59..3f9db683 100644 --- a/apps/web/lib/webhook-provider-pages.test.ts +++ b/apps/web/lib/webhook-provider-pages.test.ts @@ -76,7 +76,7 @@ describe("webhook provider pages", () => { "2026-09-29T00:00:00.000Z" ); // #446 changed the FAQ wording ("an") on vowel-initial pages on 3 October. - for (const slug of ["adyen", "airtable", "auth0", "aws-sns", "intercom", "okta"]) { + for (const slug of ["airtable", "auth0", "aws-sns", "okta"]) { expect(getWebhookProviderPage(slug)?.lastModified.toISOString(), slug).toBe( "2026-10-03T00:00:00.000Z" ); @@ -88,10 +88,20 @@ describe("webhook provider pages", () => { it("keeps search-driven copy short, dash-free and tied to real pages", () => { const withSeo = pages.filter((page) => page.seo); expect(withSeo.map((page) => page.slug).sort()).toEqual([ + "adyen", + "cal", + "checkout-com", "clerk", + "coinbase-commerce", "discord", + "docusign", + "intercom", "lemonsqueezy", + "linear", "mux", + "paddle", + "sendgrid", + "square", ]); for (const page of withSeo) { const seo = page.seo!; @@ -105,7 +115,9 @@ describe("webhook provider pages", () => { ...(seo.reference?.rows.flat() ?? []), ].join(" "); expect(copy, page.slug).not.toContain(String.fromCharCode(0x2014)); - expect(page.lastModified.toISOString(), page.slug).toBe("2026-10-13T00:00:00.000Z"); + expect(["2026-10-13T00:00:00.000Z", "2026-10-20T00:00:00.000Z"], page.slug).toContain( + page.lastModified.toISOString() + ); } }); }); diff --git a/apps/web/lib/webhook-provider-pages.ts b/apps/web/lib/webhook-provider-pages.ts index 6aeeaa33..2abcbc82 100644 --- a/apps/web/lib/webhook-provider-pages.ts +++ b/apps/web/lib/webhook-provider-pages.ts @@ -5,7 +5,7 @@ export type WebhookProviderCategory = "Payments & billing" | "Dev & deploy" | "Communication" | "Commerce & SaaS" | "Identity & data"; interface ProviderEditorial { - /** What this provider's webhooks notify you about — one factual sentence. */ + /** What this provider's webhooks notify you about - one factual sentence. */ blurb: string; /** Where webhooks are configured for this provider. */ configHint: string; @@ -55,20 +55,20 @@ const PROVIDER_EDITORIAL: Record = { "Paddle sends webhooks for transactions, subscriptions, customers, and adjustments in Paddle Billing.", configHint: "Paddle Dashboard → Developer Tools → Notifications → New destination", category: "Payments & billing", - updated: "2026-08-21", + updated: "2026-10-20", }, linear: { blurb: "Linear sends webhooks for issue, comment, project, and cycle changes in a workspace.", configHint: "Linear → Settings → API → Webhooks → New webhook", category: "Commerce & SaaS", - updated: "2026-08-21", + updated: "2026-10-20", }, sendgrid: { blurb: "SendGrid sends event webhooks for email delivery, opens, clicks, bounces, and spam reports.", configHint: "SendGrid → Settings → Mail Settings → Event Webhook", category: "Communication", - updated: "2026-08-21", + updated: "2026-10-20", }, clerk: { blurb: @@ -131,7 +131,7 @@ const PROVIDER_EDITORIAL: Record = { "Coinbase Commerce sends webhooks as crypto charges are created, pending, confirmed, or failed.", configHint: "Coinbase Commerce → Settings → Webhook subscriptions", category: "Payments & billing", - updated: "2026-08-21", + updated: "2026-10-20", }, razorpay: { blurb: @@ -145,14 +145,14 @@ const PROVIDER_EDITORIAL: Record = { "Cal.com sends webhooks when bookings are created, cancelled, rescheduled, or completed.", configHint: "Cal.com → Settings → Developer → Webhooks", category: "Commerce & SaaS", - updated: "2026-08-21", + updated: "2026-10-20", }, intercom: { blurb: "Intercom sends webhooks for conversations, contacts, and admin replies in your workspace.", configHint: "Intercom Developer Hub → your app → Webhooks", category: "Communication", - updated: "2026-10-03", + updated: "2026-10-20", }, telegram: { blurb: @@ -165,7 +165,7 @@ const PROVIDER_EDITORIAL: Record = { blurb: "Square sends webhooks for payments, refunds, orders, inventory, and catalog changes.", configHint: "Square Developer Dashboard → your app → Webhooks → Subscriptions", category: "Payments & billing", - updated: "2026-08-21", + updated: "2026-10-20", }, hubspot: { blurb: @@ -212,14 +212,14 @@ const PROVIDER_EDITORIAL: Record = { "DocuSign Connect sends webhooks as envelopes are sent, viewed, signed, completed, or declined.", configHint: "DocuSign Admin → Integrations → Connect → Add Configuration", category: "Identity & data", - updated: "2026-08-21", + updated: "2026-10-20", }, adyen: { blurb: "Adyen sends standard notification webhooks for authorisations, captures, refunds, and chargebacks.", configHint: "Adyen Customer Area → Developers → Webhooks → Create webhook", category: "Payments & billing", - updated: "2026-10-03", + updated: "2026-10-20", }, paypal: { blurb: @@ -374,7 +374,7 @@ const CAPTURE_ONLY_PROVIDERS: Record = { "Checkout.com sends webhooks for payment lifecycle events such as approvals, captures, refunds, and voids, plus dispute events.", configHint: "Dashboard → Developers → Webhooks, or the Workflows API (POST /workflows)", category: "Payments & billing", - updated: "2026-09-29", + updated: "2026-10-20", signatureHeader: "cko-signature", signatureAlgorithm: "HMAC-SHA256", signatureNote: @@ -846,6 +846,366 @@ const PROVIDER_SEO: Partial> = { }, ], }, + linear: { + title: "Linear Webhooks: Events, linear-signature & Free Tester", + description: + "Inspect Linear webhook events (Issue, Comment, Project, Cycle), verify the linear-signature HMAC-SHA256 header, send signed samples, and forward to localhost.", + reference: { + heading: "Linear webhook events and headers", + intro: + "Linear sends a JSON body with action (create, update, remove), type, data, and webhookTimestamp, plus three identifying headers on every delivery.", + columns: ["Header or resource type", "Purpose"], + codeColumn: 0, + rows: [ + ["linear-signature", "Hex HMAC-SHA256 digest of the raw request body"], + ["linear-delivery", "UUID v4 unique to each webhook delivery attempt"], + ["linear-event", "Entity type that changed (such as Issue or Comment)"], + ["Issue", "Issue created, updated (state, assignee, priority), or removed"], + ["Comment", "Comment posted, edited, or deleted on an issue"], + ["Project", "Project or ProjectUpdate created, modified, or archived"], + ["Cycle", "Cycle started, updated, or completed in a team"], + ], + source: { + label: "Linear webhooks documentation", + href: "https://linear.app/developers/webhooks", + }, + }, + faq: [ + { + question: "How do I verify Linear's linear-signature header?", + answer: + "Compute an HMAC-SHA256 hex digest over the raw request body using your webhook signing secret and compare it in constant time against the linear-signature header. Also check that payload.webhookTimestamp is within 60 seconds of the current time to prevent replay attacks.", + }, + { + question: "What is Linear's webhook retry schedule?", + answer: + "If your endpoint does not respond with HTTP 200 within 5 seconds, Linear retries the delivery up to 3 times after 1 minute, 1 hour, and 6 hours. If an endpoint fails repeatedly, Linear disables the webhook subscription automatically.", + }, + ], + }, + docusign: { + title: "DocuSign Connect Webhooks: Events, HMAC & Best Practices", + description: + "Capture DocuSign Connect webhook events (envelope-sent, envelope-completed), verify x-docusign-signature-1 HMAC headers, and follow listener best practices.", + reference: { + heading: "Common DocuSign Connect webhook events", + intro: + "DocuSign Connect JSON SIM (Connect 2.0) notifications send an event string alongside envelopeId and data. Configure these events under DocuSign Admin -> Connect.", + columns: ["Event", "When it fires"], + codeColumn: 0, + rows: [ + ["envelope-sent", "Envelope has been sent to at least one recipient"], + ["envelope-delivered", "All recipients have opened the envelope"], + ["envelope-completed", "All recipients have signed and completed the envelope"], + ["envelope-declined", "A recipient declined to sign the envelope"], + ["envelope-voided", "The sender voided the envelope"], + ["recipient-completed", "An individual recipient finished signing"], + ["recipient-authenticationfailed", "A recipient failed an identity or access-code check"], + ], + source: { + label: "DocuSign Connect webhook docs", + href: "https://developers.docusign.com/platform/webhooks/connect/", + }, + }, + faq: [ + { + question: "What are the DocuSign webhook listener best practices?", + answer: + "Acknowledge every delivery with HTTP 200 immediately before running slow tasks or downloading signed PDFs, verify the x-docusign-signature-1 base64 HMAC-SHA256 header against the raw body, deduplicate retries using envelopeId plus event timestamp, and omit Include Documents from the webhook payload so your worker fetches PDFs asynchronously via the eSignature REST API.", + }, + { + question: "How do I verify x-docusign-signature-1?", + answer: + "Enable HMAC security in your DocuSign Connect configuration and copy the HMAC key. Compute a base64-encoded HMAC-SHA256 digest of the raw UTF-8 request body using that key and compare it in constant time with x-docusign-signature-1.", + }, + ], + }, + sendgrid: { + title: "SendGrid Event Webhook: ECDSA Signatures, Events & Tester", + description: + "Test SendGrid Event Webhook deliveries (delivered, open, bounce, spamreport), verify ECDSA signatures with x-twilio-email-event-webhook-signature, and replay.", + reference: { + heading: "SendGrid Event Webhook event types", + intro: + "SendGrid batches email events into a JSON array POSTed to your Event Webhook URL. Every object in the array carries an event field and sg_event_id.", + columns: ["Event", "Meaning"], + codeColumn: 0, + rows: [ + ["processed", "Message received by SendGrid and ready for delivery"], + ["delivered", "Receiving mail server accepted the message"], + ["deferred", "Receiving server temporarily rejected the message; SendGrid will retry"], + ["bounce", "Receiving server permanently rejected the message"], + ["dropped", "SendGrid dropped the message (suppression list, invalid address)"], + ["open", "Recipient opened the HTML email (tracking pixel loaded)"], + ["click", "Recipient clicked a tracked link in the email"], + ["spamreport", "Recipient marked the email as spam"], + ], + source: { + label: "SendGrid Event Webhook reference", + href: "https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/event", + }, + }, + faq: [ + { + question: "How do I verify SendGrid's x-twilio-email-event-webhook-signature header?", + answer: + "SendGrid signs Event Webhooks with ECDSA over SHA-256 rather than HMAC. Concatenate the x-twilio-email-event-webhook-timestamp header value and the raw request body bytes, then verify the base64 signature in x-twilio-email-event-webhook-signature using the ECDSA public key from SendGrid Settings -> Mail Settings -> Event Webhook.", + }, + { + question: "Why does SendGrid send an array of events instead of a single JSON object?", + answer: + "SendGrid batches deliveries for throughput, posting a JSON array of event objects every few seconds or when the batch fills. Iterate over the array in your handler and deduplicate each item by its unique sg_event_id.", + }, + ], + }, + paddle: { + title: "Paddle Webhooks: paddle-signature & p_signature Guide", + description: + "Verify Paddle Billing paddle-signature (ts;h1 HMAC-SHA256) and Paddle Classic p_signature (RSA-SHA1 phpserialize), inspect subscription events, and test locally.", + reference: { + heading: "Paddle Billing events and Classic signature headers", + intro: + "Paddle Billing sends JSON webhooks signed with paddle-signature. Legacy Paddle Classic sends URL-encoded form POSTs with a p_signature field inside the body.", + columns: ["Event or field", "Description"], + codeColumn: 0, + rows: [ + ["paddle-signature", "Paddle Billing header: ts=;h1="], + ["p_signature", "Paddle Classic form field: base64 RSA-SHA1 over PHP-serialized fields"], + ["transaction.completed", "Paddle Billing transaction payment succeeded"], + ["transaction.payment_failed", "Paddle Billing payment attempt failed"], + ["subscription.created", "New subscription started in Paddle Billing"], + ["subscription.updated", "Subscription plan, quantity, or billing cycle changed"], + ["subscription.canceled", "Subscription canceled by customer or billing failure"], + ], + source: { + label: "Paddle webhooks documentation", + href: "https://developer.paddle.com/webhooks/overview", + }, + }, + faq: [ + { + question: "How do I verify paddle-signature in Paddle Billing?", + answer: + "Split the paddle-signature header on ';' to extract ts and h1. Build the signed payload string `${ts}:${rawBody}` using the exact raw request body, compute its hex HMAC-SHA256 digest with your endpoint's secret key (pdl_ntfset_...), and compare it to h1.", + }, + { + question: "How do I verify p_signature in Paddle Classic?", + answer: + "Parse the application/x-www-form-urlencoded body, extract and remove the p_signature field, sort the remaining keys alphabetically, stringify every value, serialize the sorted key-value map using PHP's serialize() format, and verify the base64 p_signature with RSA-SHA1 against your Paddle Classic public key.", + }, + ], + }, + intercom: { + title: "Intercom Webhooks: x-hub-signature, Topics & Free Tester", + description: + "Capture Intercom webhook topics (conversation.user.created, conversation.admin.replied), verify x-hub-signature HMAC-SHA1 with your client secret, and test.", + reference: { + heading: "Common Intercom webhook topics", + intro: + "Intercom notifications wrap every event in a notification_event object whose topic field identifies what happened in your workspace.", + columns: ["Topic", "When it fires"], + codeColumn: 0, + rows: [ + ["conversation.user.created", "A user or lead started a new conversation"], + ["conversation.user.replied", "A user or lead replied to an existing conversation"], + ["conversation.admin.replied", "A teammate or admin replied to a conversation"], + ["conversation.admin.closed", "An admin closed a conversation"], + ["contact.user.created", "A new user contact was created"], + ["ticket.created", "A new support ticket was created"], + ], + source: { + label: "Intercom webhooks documentation", + href: "https://developers.intercom.com/docs/webhooks", + }, + }, + faq: [ + { + question: "How do I verify Intercom's x-hub-signature header?", + answer: + "Intercom signs the raw JSON body with HMAC-SHA1 using your Intercom app's client_secret and sends the result as sha1= in the x-hub-signature header. Compute sha1= over the unparsed request body with your client secret and compare in constant time.", + }, + { + question: "What happens if an Intercom webhook endpoint times out?", + answer: + "Your endpoint must return a 2xx status within 5 seconds. Failed or timed-out deliveries are retried with exponential backoff, and Intercom temporarily suspends the webhook subscription if failures persist.", + }, + ], + }, + square: { + title: "Square Webhooks: HMAC-SHA256 Signatures, Events & Tester", + description: + "Test Square webhook events (payment.updated, order.created, invoice.payment_made), verify x-square-hmacsha256-signature over notificationUrl + body, and replay.", + reference: { + heading: "Common Square webhook events", + intro: + "Square webhooks carry event_id, type, merchant_id, and data. Subscribe to specific event types in the Square Developer Dashboard.", + columns: ["Event", "When it fires"], + codeColumn: 0, + rows: [ + ["payment.created", "A new payment is initialized"], + ["payment.updated", "A payment status changes (such as COMPLETED or FAILED)"], + ["order.created", "A new order is created"], + ["order.updated", "An order state, fulfillment, or line item changes"], + ["refund.updated", "A payment refund completes or fails"], + ["invoice.payment_made", "A customer pays a Square invoice"], + ], + source: { + label: "Square webhooks overview", + href: "https://developer.squareup.com/docs/webhooks/overview", + }, + }, + faq: [ + { + question: "Why does Square x-square-hmacsha256-signature verification fail?", + answer: + "Unlike providers that sign only the body, Square prepends your exact webhook notification URL to the raw UTF-8 body before hashing: base64(HMAC-SHA256(notificationUrl + rawBody, signatureKey)). If your app sits behind a proxy or trailing slash mismatch, pass the exact URL registered in the Square Developer Dashboard.", + }, + { + question: "How do I test Square webhooks on localhost?", + answer: + "Create a free webhooks.cc endpoint, paste its URL into Square Developer Dashboard -> Webhooks -> Subscriptions, and click Send Test Event or run whk tunnel to forward live sandbox deliveries to your local server.", + }, + ], + }, + cal: { + title: "Cal.com Webhooks: x-cal-signature-256, Triggers & Tester", + description: + "Capture Cal.com booking webhooks (BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED), verify the x-cal-signature-256 HMAC-SHA256 header, and test locally.", + reference: { + heading: "Cal.com webhook trigger events", + intro: + "Cal.com webhooks send triggerEvent, createdAt, and payload with booking details, attendee emails, and custom responses.", + columns: ["triggerEvent", "When it fires"], + codeColumn: 0, + rows: [ + ["BOOKING_CREATED", "An attendee schedules a new booking"], + ["BOOKING_RESCHEDULED", "An existing booking is moved to a new time"], + ["BOOKING_CANCELLED", "A booking is cancelled by attendee or host"], + ["BOOKING_REJECTED", "A host rejects a booking request that required confirmation"], + ["MEETING_ENDED", "A scheduled meeting's end time is reached"], + ["RECORDING_READY", "A Cal Video recording is ready for download"], + ], + source: { + label: "Cal.com webhooks guide", + href: "https://cal.com/docs/developing/guides/automation/webhooks", + }, + }, + faq: [ + { + question: "How do I verify Cal.com's x-cal-signature-256 header?", + answer: + "When you set a Secret on a Cal.com webhook, Cal.com computes a hex-encoded HMAC-SHA256 digest of the raw request body using that secret and sends it in the x-cal-signature-256 header. Recompute HMAC-SHA256(rawBody, secret) in hex and compare with timingSafeEqual.", + }, + ], + }, + "coinbase-commerce": { + title: "Coinbase Commerce Webhooks: x-cc-webhook-signature & Events", + description: + "Verify Coinbase Commerce x-cc-webhook-signature (HMAC-SHA256), inspect charge:created, charge:pending, and charge:confirmed payloads, and test on a free URL.", + reference: { + heading: "Coinbase Commerce webhook events", + intro: + "Coinbase Commerce wraps each notification in an event object with id, type, api_version, created_at, and data holding the charge resource.", + columns: ["Event type", "When it fires"], + codeColumn: 0, + rows: [ + ["charge:created", "A new crypto checkout charge is created"], + ["charge:pending", "A transaction is detected on-chain but not yet confirmed"], + ["charge:confirmed", "On-chain payment reached the required block confirmations"], + ["charge:failed", "Charge expired or payment failed to complete"], + ["charge:delayed", "Payment was received after the charge expiration window"], + ["charge:resolved", "An underpaid, overpaid, or delayed charge was resolved"], + ], + source: { + label: "Coinbase Commerce webhooks docs", + href: "https://docs.cdp.coinbase.com/commerce-onchain/docs/webhooks", + }, + }, + faq: [ + { + question: "How do I verify the x-cc-webhook-signature header?", + answer: + "Copy your Webhook Shared Secret from Coinbase Commerce Settings -> Notifications. Compute a lowercase hex HMAC-SHA256 digest over the raw request body using that shared secret and compare it in constant time with the x-cc-webhook-signature header.", + }, + { + question: "Should I fulfill orders on charge:pending or charge:confirmed?", + answer: + "Always wait for charge:confirmed before fulfilling an order or granting access. charge:pending only means a transaction was broadcast; it has not yet finalized on-chain.", + }, + ], + }, + adyen: { + title: "Adyen Webhooks: hmacSignature Verification, Events & Tester", + description: + "Capture Adyen standard notification webhooks (AUTHORISATION, CAPTURE, REFUND, CHARGEBACK), verify additionalData.hmacSignature (HMAC-SHA256), and test locally.", + reference: { + heading: "Common Adyen standard webhook eventCodes", + intro: + 'Adyen sends a notificationItems array where each NotificationRequestItem carries eventCode, success ("true" or "false"), pspReference, and additionalData.hmacSignature.', + columns: ["eventCode", "Meaning"], + codeColumn: 0, + rows: [ + ["AUTHORISATION", "Payment authorization succeeded or failed (check success field)"], + ["CAPTURE", "Authorized payment funds were captured"], + ["CANCELLATION", "Authorized payment was cancelled before capture"], + ["REFUND", "Captured payment was refunded to the shopper"], + ["CHARGEBACK", "Shopper disputed the charge and funds were deducted"], + ["REPORT_AVAILABLE", "A settlement or accounting report is ready to download"], + ], + source: { + label: "Adyen webhooks documentation", + href: "https://docs.adyen.com/development-resources/webhooks", + }, + }, + faq: [ + { + question: "How do I verify Adyen's hmacSignature?", + answer: + "For standard webhooks, Adyen places the signature inside notificationItem.NotificationRequestItem.additionalData.hmacSignature. Join pspReference, originalReference, merchantAccountCode, merchantReference, amount.value, amount.currency, eventCode, and success with ':' (escaping '\\' as '\\\\' and ':' as '\\:'), hex-decode your Adyen HMAC key into binary bytes, and compute a base64 HMAC-SHA256 digest.", + }, + { + question: "What response does Adyen expect from a webhook endpoint?", + answer: + "Adyen requires a 2xx HTTP status (or '[accepted]' on older configurations) within 10 seconds. Always verify hmacSignature and check whether success equals 'true' before fulfilling an AUTHORISATION event.", + }, + ], + }, + "checkout-com": { + title: "Checkout.com Webhooks: cko-signature, Events & Free Tester", + description: + "Test Checkout.com workflow webhooks (payment_approved, payment_captured, payment_refunded), verify the cko-signature HMAC-SHA256 header, and inspect payloads.", + reference: { + heading: "Common Checkout.com webhook events", + intro: + "Checkout.com Workflows send JSON notifications with id, type, created_on, and data, signed in the cko-signature header.", + columns: ["Event type", "When it fires"], + codeColumn: 0, + rows: [ + ["payment_approved", "Payment authorization was approved by the issuer"], + ["payment_declined", "Payment authorization was declined"], + ["payment_captured", "Authorized payment was captured"], + ["payment_refunded", "Captured payment was refunded"], + ["payment_voided", "Authorized payment was voided before capture"], + ["dispute_received", "Cardholder opened a chargeback dispute"], + ], + source: { + label: "Checkout.com webhooks documentation", + href: "https://www.checkout.com/docs/developer-resources/webhooks", + }, + }, + faq: [ + { + question: "How do I verify Checkout.com's cko-signature header?", + answer: + "Each webhook action in a Checkout.com Workflow has a signature key. Compute a lowercase hex HMAC-SHA256 digest of the raw request body using that signature key and compare it with the cko-signature header. You can also configure an optional Authorization header on the workflow.", + }, + { + question: "How does Checkout.com retry failed webhooks?", + answer: + "If your endpoint does not return a 2xx response within 10 seconds, Checkout.com Workflows retry the webhook with exponential backoff for up to 24 hours. Use the event id field to deduplicate repeated deliveries.", + }, + ], + }, }; export interface WebhookProviderPage { @@ -911,7 +1271,7 @@ function buildWebhookProviderPage(provider: TemplateProvider): WebhookProviderPa const sdkHeader = "signatureHeader" in meta ? (meta.signatureHeader ?? null) : null; // The web provider catalog carries hand-tuned overrides the raw SDK template - // metadata lacks — e.g. Discord verifies with the x-signature-ed25519 header + // metadata lacks - e.g. Discord verifies with the x-signature-ed25519 header // using Ed25519, neither of which is in its SDK template. Prefer the catalog // so this page never contradicts the catalog the dashboard verifies against. const catalogHeader = info?.header ? info.header : null; diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 852c2b56..82db2d2f 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -55,12 +55,18 @@ const nextConfig: NextConfig = { }, ], }, + { + // Explicit X-Robots-Tag header at the Next.js router layer in addition + // to proxy.ts and layout metadata so crawlers always see noindex. + source: "/dashboard/:path*", + headers: [{ key: "X-Robots-Tag", value: "noindex, nofollow" }], + }, ]; }, async redirects() { return [ { - // /go is retired — the landing page now IS the guest dashboard. + // /go is retired - the landing page now IS the guest dashboard. // Permanent redirect preserves the page's search rankings and backlinks. source: "/go", destination: "/", @@ -77,6 +83,24 @@ const nextConfig: NextConfig = { destination: "/sitemap.xml", permanent: true, }, + { + // Devs and LLMs guess both /webhooks/amazon-sns and /webhooks/aws-sns; + // canonical slug in webhook-provider-pages.ts is aws-sns. + source: "/webhooks/amazon-sns", + destination: "/webhooks/aws-sns", + permanent: true, + }, + { + // Legacy/guessed comparison paths seen in Search Console. + source: "/compare/webhooks-cc", + destination: "/compare", + permanent: true, + }, + { + source: "/compare/webhooks-cc-vs-webhook-site", + destination: "/compare/webhook-site", + permanent: true, + }, ]; }, }; diff --git a/apps/web/package.json b/apps/web/package.json index 51083439..639da18f 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "web", - "version": "0.40.1", + "version": "0.40.2", "private": true, "scripts": { "dev": "next dev", diff --git a/apps/web/proxy.ts b/apps/web/proxy.ts index 325585a9..91074ec9 100644 --- a/apps/web/proxy.ts +++ b/apps/web/proxy.ts @@ -30,6 +30,8 @@ function shouldNoIndexPath(pathname: string): boolean { "/auth", "/cli/verify", "/teams", + "/agent/claim", + "/blog/preview", ]; return privatePrefixes.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`)); } diff --git a/apps/web/public/robots.txt b/apps/web/public/robots.txt index aba92046..f18086c2 100644 --- a/apps/web/public/robots.txt +++ b/apps/web/public/robots.txt @@ -1,14 +1,12 @@ User-agent: * Allow: / -Disallow: /dashboard -Disallow: /account -Disallow: /endpoints -Disallow: /teams -Disallow: /api-explorer Disallow: /api/ -Disallow: /cli/verify -Disallow: /blog/preview -Disallow: /agent/claim + +# Private app routes (/dashboard, /account, /endpoints, /teams, /api-explorer, +# /cli/verify, /blog/preview, /agent/claim) intentionally remain crawlable here +# so crawlers read their X-Robots-Tag: noindex, nofollow response headers and +# tags instead of indexing the +# bare URL from links. # Agent registration: how AI agents obtain an API credential (auth.md protocol). # Also advertised via the Link response header and on every page. diff --git a/content/docs/guides/verify-webhook-signatures.mdx b/content/docs/guides/verify-webhook-signatures.mdx index 0c8526ef..5764d618 100644 --- a/content/docs/guides/verify-webhook-signatures.mdx +++ b/content/docs/guides/verify-webhook-signatures.mdx @@ -1,27 +1,33 @@ --- title: "How to Verify Webhook Signatures" -description: "Verify webhook signatures for Stripe, GitHub, Shopify, Meta, and 26 more signed providers using HMAC-SHA256, HMAC-SHA1, and asymmetric algorithms. Protect handlers from forged payloads." +description: "Step-by-step guide to verifying webhook signatures for Stripe, GitHub, Shopify, Slack, Discord, and 32 providers using HMAC-SHA256, Ed25519, and RSA-SHA256." section: guides order: 5 -lastUpdated: 2026-10-02 +lastUpdated: 2026-10-20 schema: howto keywords: - ["verify webhook signature", "webhook HMAC", "webhook security", "webhook signature validation"] + [ + "verify webhook signature", + "how to verify webhook signatures", + "webhook HMAC", + "webhook security", + "webhook signature validation", + ] seo: - ogTitle: "How to Verify Webhook Signatures" - ogDescription: "Verify webhook signatures for Stripe, GitHub, Shopify, Meta, and 26 more signed providers using HMAC-SHA256, HMAC-SHA1, and asymmetric algorithms. Protect handlers from forged payloads." + ogTitle: "How to Verify Webhook Signatures (HMAC, Ed25519, RSA)" + ogDescription: "Step-by-step guide to verifying webhook signatures for Stripe, GitHub, Shopify, Slack, Discord, and 32 providers using HMAC-SHA256, Ed25519, and RSA-SHA256." --- Webhook signatures prove a payload came from the expected sender and was not tampered with in transit. Most providers sign payloads with HMAC-SHA256. Discord uses Ed25519 public-key cryptography, PayPal uses RSA-SHA256 with provider certificates, and Twilio uses HMAC-SHA1. Verifying signatures is the single most important security step in any webhook handler. Skip it, and anyone who knows your endpoint URL can forge payloads. -The webhooks.cc SDK verifies signatures for 32 signed providers with a single function call. This guide walks through the process for each one. SendGrid is supported for templates and provider detection, but its Event Webhook security model uses IP allowlisting rather than cryptographic signatures. Plaid is supported for templates and detection, but JWT/JWK verification requires Plaid API credentials and is not available in the SDK yet. +The webhooks.cc SDK verifies signatures for 32 signed providers with a single function call. This guide walks through the process for each one. SendGrid is supported for templates and provider detection; SendGrid signs Event Webhooks with ECDSA (X-Twilio-Email-Event-Webhook-Signature), which the SDK does not currently verify. Plaid is supported for templates and detection, but JWT/JWK verification requires Plaid API credentials and is not available in the SDK yet. ## Verify in the dashboard (no code) Before writing verification code, you can verify signatures directly in the dashboard: -1. **Quick check** — open any captured request, click the **Signature** tab, paste your secret, and click Verify. Runs in your browser; the secret never leaves the client. -2. **Automatic** — configure a signing secret in endpoint settings. Every incoming webhook is verified server-side and the result (valid/invalid) appears as a badge in the request list. +1. **Quick check** -- open any captured request, click the **Signature** tab, paste your secret, and click Verify. Runs in your browser; the secret never leaves the client. +2. **Automatic** -- configure a signing secret in endpoint settings. Every incoming webhook is verified server-side and the result (valid/invalid) appears as a badge in the request list. See [Dashboard Features: Signature Verification](/docs/dashboard-features#signature-verification) for the full walkthrough. @@ -40,7 +46,7 @@ A webhook handler that verifies signatures before processing, with working examp Every HMAC-based webhook signature follows the same four-step process: 1. The provider computes a hash of the raw request body (sometimes combined with a timestamp or URL) using a shared secret only you and the provider know. -2. The provider sends the hash in an HTTP header alongside the request. The header name varies by provider — `stripe-signature`, `x-hub-signature-256`, etc. +2. The provider sends the hash in an HTTP header alongside the request. The header name varies by provider (`stripe-signature`, `x-hub-signature-256`, etc.). 3. Your handler computes the same hash over the raw body using the same secret. 4. If the two hashes match, the payload is authentic and unmodified. If they do not match, the payload was either tampered with or signed with a different secret. @@ -78,11 +84,11 @@ if (!result.valid) { return new Response("Invalid signature", { status: 401 }); } -// Signature verified — safe to process the payload +// Signature verified: safe to process the payload const body = JSON.parse(request.body); ``` -Replace `"stripe"` with any supported verification provider name: `"github"`, `"shopify"`, `"twilio"`, `"slack"`, `"paddle"`, `"linear"`, `"clerk"`, `"discord"`, `"vercel"`, `"gitlab"`, `"typeform"`, `"standard-webhooks"`, `"meta"`, `"lemonsqueezy"`, `"coinbase-commerce"`, `"razorpay"`, `"cal"`, `"intercom"`, `"telegram"`, `"square"`, `"hubspot"`, `"mailgun"`, `"calendly"`, `"mux"`, `"sentry"`, `"bitbucket"`, `"docusign"`, `"adyen"`, or `"paypal"`. +Replace `"stripe"` with any supported verification provider name: `"github"`, `"shopify"`, `"twilio"`, `"slack"`, `"paddle"`, `"linear"`, `"clerk"`, `"discord"`, `"vercel"`, `"gitlab"`, `"typeform"`, `"standard-webhooks"`, `"meta"`, `"lemonsqueezy"`, `"coinbase-commerce"`, `"razorpay"`, `"cal"`, `"intercom"`, `"telegram"`, `"square"`, `"hubspot"`, `"mailgun"`, `"calendly"`, `"mux"`, `"sentry"`, `"bitbucket"`, `"docusign"`, `"adyen"`, `"paypal"`, `"resend"`, or `"workos"`. @@ -125,7 +131,7 @@ if (!valid) { } ``` -Provider-specific functions are available for all supported providers: `verifyStripeSignature`, `verifyGitHubSignature`, `verifyShopifySignature`, `verifyTwilioSignature`, `verifySlackSignature`, `verifyPaddleSignature`, `verifyLinearSignature`, `verifyClerkSignature`, `verifyDiscordSignature`, `verifyVercelSignature`, `verifyGitLabSignature`, `verifyTypeformSignature`, `verifyStandardWebhookSignature`, `verifyMetaSignature`, `verifyLemonSqueezySignature`, `verifyCoinbaseCommerceSignature`, `verifyRazorpaySignature`, `verifyCalSignature`, `verifyIntercomSignature`, `verifyTelegramSignature`, `verifySquareSignature`, `verifyHubSpotSignature`, `verifyMailgunSignature`, `verifyCalendlySignature`, `verifyMuxSignature`, `verifySentrySignature`, `verifyBitbucketSignature`, `verifyDocuSignSignature`, `verifyAdyenSignature`, `verifyPayPalSignature`. SendGrid is excluded because it uses IP allowlisting instead of cryptographic signatures. Plaid is excluded because it requires Plaid JWT/JWK lookup credentials. +Provider-specific functions are available for all supported providers: `verifyStripeSignature`, `verifyGitHubSignature`, `verifyShopifySignature`, `verifyTwilioSignature`, `verifySlackSignature`, `verifyPaddleSignature`, `verifyLinearSignature`, `verifyClerkSignature`, `verifyDiscordSignature`, `verifyVercelSignature`, `verifyGitLabSignature`, `verifyTypeformSignature`, `verifyStandardWebhookSignature`, `verifyMetaSignature`, `verifyLemonSqueezySignature`, `verifyCoinbaseCommerceSignature`, `verifyRazorpaySignature`, `verifyCalSignature`, `verifyIntercomSignature`, `verifyTelegramSignature`, `verifySquareSignature`, `verifyHubSpotSignature`, `verifyMailgunSignature`, `verifyCalendlySignature`, `verifyMuxSignature`, `verifySentrySignature`, `verifyBitbucketSignature`, `verifyDocuSignSignature`, `verifyAdyenSignature`, `verifyPayPalSignature`, `verifyResendSignature`, `verifyWorkOSSignature`. SendGrid is supported for templates and detection, while its ECDSA Event Webhook signature is not yet verified by the SDK. Plaid is excluded because it requires Plaid JWT/JWK lookup credentials. @@ -263,7 +269,7 @@ Secrets with a `whsec_` prefix are handled automatically. The SDK detects whethe -Meta signs the raw body with HMAC-SHA256 and sends `sha256={hex}` in `x-hub-signature-256` — the same scheme as GitHub — keyed by your **app secret**. Because Meta shares GitHub's header, configure `meta` explicitly rather than relying on header auto-detection. +Meta signs the raw body with HMAC-SHA256 and sends `sha256={hex}` in `x-hub-signature-256` (the same scheme as GitHub) keyed by your **app secret**. Because Meta shares GitHub's header, configure `meta` explicitly rather than relying on header auto-detection. ```ts const result = await verifySignature(request, { @@ -275,7 +281,7 @@ const result = await verifySignature(request, { -These providers each send a raw hex HMAC-SHA256 of the body in their own header — `x-signature` (Lemon Squeezy), `x-cc-webhook-signature` (Coinbase Commerce), `x-razorpay-signature` (Razorpay), and `x-cal-signature-256` (Cal.com). Pass the matching provider id: +These providers each send a raw hex HMAC-SHA256 of the body in their own header: `x-signature` (Lemon Squeezy), `x-cc-webhook-signature` (Coinbase Commerce), `x-razorpay-signature` (Razorpay), and `x-cal-signature-256` (Cal.com). Pass the matching provider id: ```ts const result = await verifySignature(request, { @@ -351,7 +357,7 @@ const result = await verifySignature(request, { -Calendly and Mux both use the Stripe-style `t=,v1=` header format — `calendly-webhook-signature` and `mux-signature` respectively — computing HMAC-SHA256 over `{timestamp}.{body}` (hex): +Calendly and Mux both use the Stripe-style `t=,v1=` header format (`calendly-webhook-signature` and `mux-signature` respectively), computing HMAC-SHA256 over `{timestamp}.{body}` (hex): ```ts const calendly = await verifySignature(request, { @@ -380,7 +386,7 @@ const result = await verifySignature(request, { -Bitbucket signs the raw body with HMAC-SHA256 and sends `sha256={hex}` in the `x-hub-signature` header — the same scheme as GitHub. Auto-detection distinguishes it from Intercom (which also uses `x-hub-signature`, but with `sha1=`) by Bitbucket's unique `x-event-key` header: +Bitbucket signs the raw body with HMAC-SHA256 and sends `sha256={hex}` in the `x-hub-signature` header (the same scheme as GitHub). Auto-detection distinguishes it from Intercom (which also uses `x-hub-signature`, but with `sha1=`) by Bitbucket's unique `x-event-key` header: ```ts const result = await verifySignature(request, { @@ -469,7 +475,7 @@ const result = await verifySignature(request, { secret: process.env.STRIPE_WEBHOOK_SECRET!, }); -console.log(result.valid); // true — signature matches +console.log(result.valid); // true: signature matches ``` This is useful for debugging signature failures. Capture the webhook, inspect the raw headers and body in the dashboard, then run verification against it programmatically to isolate whether the issue is in your secret, your body parsing, or something else. @@ -607,7 +613,7 @@ Either works. Middleware is cleaner when all routes behind a path need verificat diff --git a/content/docs/guides/webhook-testing-tools.mdx b/content/docs/guides/webhook-testing-tools.mdx index 076e28be..30b231ed 100644 --- a/content/docs/guides/webhook-testing-tools.mdx +++ b/content/docs/guides/webhook-testing-tools.mdx @@ -3,8 +3,9 @@ title: "Webhook Testing Tools Compared" description: "Side-by-side comparison of webhook testing tools: webhooks.cc, ngrok, Svix, RequestBin, Hookdeck, Beeceptor, and Webhook.site. Features, pricing, and use cases to find the right fit for your workflow." section: guides order: 8 -lastUpdated: 2026-10-08 +lastUpdated: 2026-10-20 schema: faq +canonical: /compare keywords: [ "webhook testing tools", @@ -17,7 +18,7 @@ seo: ogDescription: "Side-by-side comparison of webhook testing tools: webhooks.cc, ngrok, Svix, RequestBin, Hookdeck, Beeceptor, and Webhook.site. Features, pricing, and use cases for your workflow." --- -Several tools help with webhook development — from simple capture endpoints to full-featured testing platforms. They solve different problems: some capture and display incoming requests, some expose localhost to the internet, some handle outbound webhook delivery, and some focus on testing and CI/CD integration. +Several tools help with webhook development, from simple capture endpoints to full-featured testing platforms. They solve different problems: some capture and display incoming requests, some expose localhost to the internet, some handle outbound webhook delivery, and some focus on testing and CI/CD integration. This comparison covers the most popular options, what each does well, and where each falls short. The goal is to help you pick the right tool (or combination of tools) for your specific workflow. @@ -51,7 +52,7 @@ Webhook-specific development and testing platform. **What it does well:** - Signs test payloads for 32 verification providers, plus realistic SendGrid and Plaid templates, which use provider security models that cannot be verified offline -- `sendTo` sends signed webhooks directly to localhost — no tunnel needed in CI +- `sendTo` sends signed webhooks directly to localhost without needing a tunnel in CI - SDK testing utilities: `withEndpoint`, `captureDuring`, `assertRequest`, matchers - MCP server for AI agent integration - Sub-1ms capture latency (Rust receiver) @@ -70,18 +71,18 @@ Webhook-specific development and testing platform. General-purpose localhost exposure for any protocol. -| Feature | Detail | -| ------------- | ------------------------------------------------- | -| **Focus** | Tunneling — expose local services to the internet | -| **Protocols** | HTTP, TCP, TLS, WebSocket | -| **SDK** | None (REST API) | -| **CLI** | Go binary (`ngrok`) | -| **Free tier** | 1 agent, random URLs, connection limits | -| **Paid** | $8/month (Personal), $20/month (Pro), Enterprise | +| Feature | Detail | +| ------------- | ------------------------------------------------ | +| **Focus** | Tunneling: expose local services to the internet | +| **Protocols** | HTTP, TCP, TLS, WebSocket | +| **SDK** | None (REST API) | +| **CLI** | Go binary (`ngrok`) | +| **Free tier** | 1 agent, random URLs, connection limits | +| **Paid** | $8/month (Personal), $20/month (Pro), Enterprise | **What it does well:** -- Proxies any traffic — web pages, APIs, WebSockets, TCP +- Proxies any traffic: web pages, APIs, WebSockets, TCP - Custom domains and stable URLs on paid plans - IP restrictions and access control (paid) - Large ecosystem and platform integrations @@ -111,7 +112,7 @@ Simple webhook capture and display, now part of Pipedream. **What it does well:** - Create a URL and start capturing in seconds -- No complex setup — point webhooks at it, see what arrives +- No complex setup: point webhooks at it, see what arrives - Part of Pipedream's workflow automation platform **What it lacks:** @@ -184,7 +185,7 @@ Webhook infrastructure and event gateway for production use. -Outgoing webhook infrastructure — the opposite direction from the other tools. +Outgoing webhook infrastructure: the opposite direction from the other tools. | Feature | Detail | | ------------- | ------------------------------------------------ | @@ -205,10 +206,10 @@ Outgoing webhook infrastructure — the opposite direction from the other tools. **What it lacks:** -- Outgoing only — does not help with testing inbound handlers +- Outgoing only: does not help with testing inbound handlers - No capture or inspection of incoming webhooks - No local forwarding or tunneling -- Standard Webhooks signing only — no Stripe, GitHub, Shopify, etc. +- Standard Webhooks signing only (no Stripe, GitHub, Shopify, etc.) @@ -244,12 +245,12 @@ Mock API server with webhook capture. Each tool on this list solves a different problem. Pick based on what you need: -- **Webhook capture, signing, and CI/CD testing** — webhooks.cc. Captures inbound webhooks, signs test payloads for 32 verification providers, and provides SDK utilities for automated testing. -- **General TCP/TLS tunneling** — ngrok. Handles any protocol, not just HTTP webhooks. Use it to expose WebSocket servers, databases, or non-HTTP services. -- **Visual webhook automation** — Webhook.site. 40+ Custom Actions for transforming, forwarding, and routing webhooks without code. Also handles email and DNS. -- **Production webhook routing** — Hookdeck. High-volume inbound delivery with retries, transformations, and dead letter queues. Operational infrastructure, not development tooling. -- **Sending webhooks to your customers** — Svix. Outgoing webhook delivery with retries and Standard Webhooks signing. -- **Quick payload inspection** — RequestBin or Beeceptor. Simple capture endpoints for one-off debugging. +- **Webhook capture, signing, and CI/CD testing**: webhooks.cc. Captures inbound webhooks, signs test payloads for 32 verification providers, and provides SDK utilities for automated testing. +- **General TCP/TLS tunneling**: ngrok. Handles any protocol, not just HTTP webhooks. Use it to expose WebSocket servers, databases, or non-HTTP services. +- **Visual webhook automation**: Webhook.site. 40+ Custom Actions for transforming, forwarding, and routing webhooks without code. Also handles email and DNS. +- **Production webhook routing**: Hookdeck. High-volume inbound delivery with retries, transformations, and dead letter queues. Operational infrastructure, not development tooling. +- **Sending webhooks to your customers**: Svix. Outgoing webhook delivery with retries and Standard Webhooks signing. +- **Quick payload inspection**: RequestBin or Beeceptor. Simple capture endpoints for one-off debugging. ## FAQ @@ -274,9 +275,9 @@ Several options: webhooks.cc's free tier includes 50 requests/day with signing, ## Next steps -## Available tools +## Registry and client support + +`@webhooks-cc/mcp` is published on npm and indexed in the Model Context Protocol ecosystem so AI coding agents can discover and install it directly: -The MCP server exposes 38 tools your AI agent can call (without an API key, 10 sandbox tools; see below): +- **npm package**: [`@webhooks-cc/mcp`](https://www.npmjs.com/package/@webhooks-cc/mcp) (`npx -y @webhooks-cc/mcp`) +- **Official MCP Registry**: `io.github.webhooks-cc/mcp` (`server.json` schema validated in CI) +- **Glama & Smithery**: Ships with [`glama.json`](https://glama.ai/mcp/servers) metadata and stdio transport support for zero-config inspection +- **Supported MCP clients**: Claude Code (`claude mcp add webhooks -- npx -y @webhooks-cc/mcp`), Cursor, VS Code (GitHub Copilot), OpenAI Codex, Windsurf, and Claude Desktop -For provider templates in `send_webhook`, `secret` is required for every signed provider — only `sendgrid`, `discord`, and `plaid` templates are unsigned and need no secret. The MCP server does not inject a default secret. +## Available tools + +The MCP server exposes 38 tools your AI agent can call. Without an API key, 10 sandbox tools are available (see below). ## Provider-aware testing @@ -32,6 +49,8 @@ Agent: Captured POST request detected as Typeform / form_response The same flow works for Stripe, GitHub, Shopify, Twilio, Slack, Paddle, Linear, SendGrid templates, Clerk, Discord templates, Vercel, GitLab, Typeform, Standard Webhooks, DocuSign, Adyen, PayPal templates, and Plaid templates. +For provider templates in `send_webhook`, `secret` is required for every signed provider (only `sendgrid`, `discord`, and `plaid` templates are unsigned and need no secret). The MCP server does not inject a default secret. + ### Endpoint management | Tool | Description | @@ -87,13 +106,13 @@ Every endpoint receives email at `{slug}@mailhooks.cc`, and at `{slug}+{tag}@mai ### Forwarding -| Tool | Description | -| ----------------------- | ------------------------------------------------------------------------------------ | -| `configure_forwarding` | Set where and how captured requests and emails are forwarded, and turn it on or off | -| `get_forwarding_secret` | Read or rotate the signing secret for forwarded deliveries | +| Tool | Description | +| ----------------------- | -------------------------------------------------------------------------------------- | +| `configure_forwarding` | Set where and how captured requests and emails are forwarded, and turn it on or off | +| `get_forwarding_secret` | Read or rotate the signing secret for forwarded deliveries | | `test_forwarding` | Send the newest request (or a sample) to the forwarding URL once and report the answer | -| `list_deliveries` | The delivery log and its counts for an endpoint, or every try of one request | -| `redeliver` | Forward one request again, or every failed one | +| `list_deliveries` | The delivery log and its counts for an endpoint, or every try of one request | +| `redeliver` | Forward one request again, or every failed one | ### Teams @@ -122,17 +141,17 @@ To get everything else, the agent connects to your account: `connect_account` wi The MCP server provides 3 prompt templates for common workflows: -- **debug_webhook_delivery** — diagnose why webhook delivery is failing or missing -- **setup_provider_testing** — set up webhook testing for a specific provider -- **compare_webhook_attempts** — compare two webhook deliveries or retries +- **debug_webhook_delivery**: diagnose why webhook delivery is failing or missing +- **setup_provider_testing**: set up webhook testing for a specific provider +- **compare_webhook_attempts**: compare two webhook deliveries or retries ## Resources Three resources provide read-only access to your webhook data: -- **endpoints-overview** (`webhooks://endpoints`) — all endpoints with recent activity -- **endpoint-recent-requests** (`webhooks://endpoint/{slug}/recent`) — last 10 requests at an endpoint -- **request-details** (`webhooks://request/{id}`) — full details of a specific request +- **endpoints-overview** (`webhooks://endpoints`): all endpoints with recent activity +- **endpoint-recent-requests** (`webhooks://endpoint/{slug}/recent`): last 10 requests at an endpoint +- **request-details** (`webhooks://request/{id}`): full details of a specific request Resources return emails without the raw message or the HTML part, like the request tools. @@ -140,7 +159,7 @@ Resources return emails without the raw message or the HTML part, like the reque With the MCP server connected, you can talk to your AI agent naturally: -``` +```text You: "Create a webhook endpoint for testing Stripe" Agent: Created endpoint "stripe-test" at https://go.webhooks.cc/w/abc123 @@ -152,10 +171,10 @@ Agent: Sent signed Stripe template to stripe-test You: "Show me what was captured" Agent: 1 request captured: - POST /w/abc123 — stripe-signature + checkout.session.completed payload + POST /w/abc123 - stripe-signature + checkout.session.completed payload You: "Replay that to my local server" -Agent: Replayed to http://localhost:3000/webhooks — got 200 OK +Agent: Replayed to http://localhost:3000/webhooks - got 200 OK ``` ## Learn more @@ -179,14 +198,19 @@ Agent: Replayed to http://localhost:3000/webhooks — got 200 OK /> - + + +The `@webhooks-cc/mcp` server works with Claude Code, Cursor, VS Code (GitHub Copilot), OpenAI Codex, Windsurf, and Claude Desktop. Any client that supports the Model Context Protocol over stdio can connect with `npx -y @webhooks-cc/mcp`. + + + -The MCP server works with Claude Code, Cursor, VS Code (Copilot), OpenAI Codex, Windsurf, and Claude Desktop. Any tool that supports the Model Context Protocol can connect. +Yes. Running `npx -y @webhooks-cc/mcp` without `WHK_API_KEY` starts in agent sandbox mode with 10 tools (`create_endpoint`, `list_endpoints`, `get_endpoint`, `delete_endpoint`, `list_requests`, `get_request`, `wait_for_request`, `connect_account`, `wait_for_connection`, and `about_sandbox`), giving your agent up to 3 temporary endpoints, 25 requests per endpoint and 100 in total, for 24 hours. -Your AI agent can create endpoints, send test webhooks, inspect captured requests, configure mock responses, and replay requests to localhost — all through natural language. +Your AI agent can create webhook and inbound email endpoints, send signed provider templates (Stripe, GitHub, Shopify, Clerk, Linear, and 29 more), wait for incoming requests or verification emails, verify cryptographic signatures, configure forwarding, and replay requests to localhost. diff --git a/content/docs/sdk/signatures.mdx b/content/docs/sdk/signatures.mdx index 87d06e3b..9440b84b 100644 --- a/content/docs/sdk/signatures.mdx +++ b/content/docs/sdk/signatures.mdx @@ -1,20 +1,26 @@ --- -title: "Signature Verification" -description: "Verify webhook signatures for Stripe, GitHub, Shopify, Twilio, Slack, Paddle, Linear, Clerk, Discord, Vercel, GitLab, Typeform, Standard Webhooks, Meta, Lemon Squeezy, Coinbase Commerce, Razorpay, Cal.com, Intercom, Telegram, Square, HubSpot, Mailgun, Calendly, Mux, Sentry, Bitbucket, DocuSign, Adyen, and PayPal using the SDK." +title: "verifySignature() API Reference (@webhooks-cc/sdk)" +description: "API reference for verifySignature(), verifyRelayedRequest(), verifyForwardedEmail(), and 32 provider-specific verifier functions in the @webhooks-cc/sdk TypeScript package." section: sdk order: 5 -lastUpdated: 2026-10-11 +lastUpdated: 2026-10-20 schema: faq keywords: - ["webhook signature verification", "HMAC webhook", "verify stripe signature", "webhook security"] + [ + "verifySignature", + "@webhooks-cc/sdk signatures", + "verifyStripeSignature", + "verifyForwardedEmail", + "verifyRelayedRequest", + ] seo: - ogTitle: "Webhook Signature Verification" - ogDescription: "Verify webhook signatures for 32 signed providers using the webhooks.cc TypeScript SDK. Supports HMAC-SHA256, HMAC-SHA1, Ed25519, RSA-SHA256, and token comparison." + ogTitle: "verifySignature() API Reference (@webhooks-cc/sdk)" + ogDescription: "API reference for verifySignature(), verifyRelayedRequest(), verifyForwardedEmail(), and 32 provider verifiers in the @webhooks-cc/sdk TypeScript package." --- ## Overview -Most webhook providers sign payloads so you can verify authenticity before processing. The SDK includes verification functions for 32 signed providers, plus a generic `verifySignature` that dispatches by provider name. SendGrid is supported for templates and detection, but not cryptographic verification because SendGrid's Event Webhook security model uses IP allowlisting. Plaid is template-only for now because verification needs Plaid JWT/JWK lookup credentials. +Most webhook providers sign payloads so you can verify authenticity before processing. The SDK includes verification functions for 32 signed providers, plus a generic `verifySignature` that dispatches by provider name. For a conceptual walkthrough of HMAC-SHA256, Ed25519, and RSA webhook verification in Express and Next.js handlers, see the [How to Verify Webhook Signatures guide](/docs/guides/verify-webhook-signatures). SendGrid is supported for templates and detection; SendGrid signs Event Webhooks with ECDSA (X-Twilio-Email-Event-Webhook-Signature), which the SDK does not currently verify. Plaid is template-only for now because verification needs Plaid JWT/JWK lookup credentials. Requests and emails that webhooks.cc [forwards](/docs/forwarding) to your server have their own verifiers: `verifyRelayedRequest` for requests relayed as received, `verifyForwardedRequest` and `verifyForwardedEmail` for signed JSON. See [Forwarded requests](#forwarded-requests) and [Forwarded emails](#forwarded-emails). @@ -720,6 +726,12 @@ No. Discord uses Ed25519 public-key cryptography instead of HMAC. When verifying ## Learn more + +