Your app was down for ten minutes. Did you lose the payment webhook?

webhook-relay sits between Stripe, GitHub, Shopify or any sender and your app. It checks the signature, saves the event to disk before it answers 200, then delivers it to your app with retries and backoff. If your app is down or deploying, nothing is lost; if an event fails for good you replay it from a dashboard. One Node process, no dependencies, open source, in your own account.

How long an outage can you survive?

The default retry schedule, applied to your outage. Edit the delays to see your own policy.

AttemptAt (after arrival)Your app is

Build your routes

Point the sender at , then set this on your server:


    

Put the signing secret in the route's secret field (the Render blueprint keeps ROUTES_JSON out of your repository). Your app still verifies the original signature itself: the relay forwards the sender's headers and the exact bytes unchanged.

Run it yourself

git clone https://github.com/Duncan-Haywood/oss-monorepo && cd oss-monorepo/ventures/webhook-relay
ADMIN_KEY=$(openssl rand -hex 16) ROUTES_JSON='[...]' node server.mjs
# dashboard: http://localhost:8080/admin

Render, Fly.io (fly.toml) and Docker Compose (docker-compose.yml) deploys are in the README. Mount a persistent disk at the path in STATE or events are lost on restart.

Questions

Why not just let the provider retry?

Providers differ and you do not control them. Some retry for days, some for minutes, and some, GitHub for one, do not automatically redeliver a failed delivery at all. A relay answers the provider 200 as soon as the event is saved, then retries against your app on a schedule you set, and lets you replay any event by hand.

What does it do that a queue in my own app does not?

It sits in front of your app. If your app is down, mid-deploy or crashing on the way in, the event is already safe on disk. It also verifies the signature at the edge, so a forged request is rejected before it reaches your code.

Which providers can it verify?

Stripe, GitHub, Shopify, Slack, Paddle Billing, Typeform, anything that follows the Standard Webhooks spec (Svix, Resend, Clerk and others), and any provider that sends an HMAC-SHA256 hex digest in a header you name. Use scheme none to skip verification on a route.

What happens to events my app rejects with a 4xx?

A 4xx other than 408, 425 and 429 means your app understood the request and refused it, so retrying the same bytes cannot help. The event is parked as dead, an alert goes to ALERT_WEBHOOK_URL if you set one, and you can replay it from the dashboard after you fix the cause.

Can it lose or duplicate events?

It writes the event to disk before answering the provider, so an accepted event survives a restart. Delivery is at-least-once: your app should treat a repeated x-relay-event-id as the same event. Events that carry a provider event id are de-duplicated on the way in.

Where does my data live?

On the disk of the server you deploy in your own account. The author runs nothing in between and never sees your events or secrets.