npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

webhook-replay

v1.0.1

Published

Detect unsafe webhook handlers by replaying the same webhook multiple times.

Readme

webhook-replay

Detect unsafe webhook handlers by replaying the same webhook multiple times.

webhook-replay is a retry-safety test for webhook handlers.

It deliberately replays the same webhook payload under retries, concurrency, and reordering to answer one question:

Is this webhook handler safe if the provider retries it?

If duplicate external side effects are detected, the handler is UNSAFE.

This is how teams discover:

  • duplicate charges
  • duplicated credits
  • repeated emails
  • corrupted state

before it happens in production.


What problem this solves (plain language)

Webhook providers (Stripe, Shopify, GitHub, etc.) retry deliveries by design.

Retries are normal.
Duplicate side effects are not.

Most webhook handlers accidentally assume:

“This webhook will only run once.”

That assumption is wrong.

webhook-replay makes retries visible and reproducible.


What webhook-replay is

  • A local / CI test
  • A diagnostic
  • A binary safety check

It answers:

SAFE or UNSAFE under retry


What webhook-replay is not

webhook-replay does not:

  • run in production
  • intercept live webhooks
  • block deliveries
  • fix idempotency bugs automatically
  • replace provider retry logic

It exists to reveal unsafe behavior, not hide it.


Install

Quick test (no install):

npx webhook-replay ./path/to/handler.js --payload ./payload.json

Recommended (CI / deterministic):

npm install -D webhook-replay
npx webhook-replay ./path/to/handler.js --payload ./payload.json

Basic usage

npx webhook-replay ./path/to/handler.js

What happens:

  • The same webhook payload is delivered multiple times
  • Some deliveries run concurrently
  • Delivery order may be shuffled
  • External side effects are observed

If the same side effect occurs more than once, the run fails.


Debug mode (incident reproduction)

Use this when you suspect:

  • a webhook ran more than once
  • a customer was charged twice
  • credits or entitlements duplicated
npx webhook-replay debug ./path/to/handler.js --payload ./payload.json --trace

Or provide the payload inline:

npx webhook-replay debug ./path/to/handler.js \
  --payload-inline '{"id":"evt_123"}' \
  --trace

Debug mode prints:

  • number of handler executions
  • duplicate side effects detected
  • deterministic reproduction command
  • per-call timing (with --trace)

Declaring side effects (required for detection)

Handlers declare external side effects using ctx.effect(key).

The same key observed more than once is treated as UNSAFE.

Example:

module.exports = async function handler(payload, ctx) {
  ctx.effect(`stripe.charge:${payload.id}`);
};

ctx.effect() does not perform the side effect.
It declares that one occurred so duplicates can be detected.


Supported handler exports

  • module.exports = async function (payload, ctx) {}
  • export default async function (payload, ctx) {} (ESM)
  • export const handler = async (payload, ctx) => {} (ESM)

Shared state (advanced)

Each replay invocation receives an isolated ctx.

A deliberately shared store is available at ctx.shared to model durable idempotency state.

Example:

const key = `charge:${payload.id}`;

if (ctx.shared.kv.get(key)) return;

ctx.shared.kv.set(key, true);
ctx.effect(key);

The shared store exists only for the duration of the replay run.


Exit codes (CI-safe by design)

  • 0SAFE
    No handler errors. No duplicate side effects detected.

  • 2UNSAFE
    Duplicate side effects and/or handler errors detected.

  • 1 → Tool error
    Bad input, failed to load handler, unexpected crash.

If the exit code is 2, the webhook handler is UNSAFE under retry.

In CI, UNSAFE should fail the build.

You can override this with --allow-unsafe
(or WEBHOOK_REPLAY_ALLOW_UNSAFE=1).


Output (example)

On failure:

❌ UNSAFE UNDER RETRY

Duplicate side effects detected:
- stripe.charge:evt_demo_123 (7 executions)

Retrying webhooks is normal.
This handler is not safe under retry.

Reproduction:
npx webhook-replay ./handler.js --payload ./payload.json --seed 913472

On success:

✅ SAFE UNDER RETRY
No duplicate side effects detected.

CI example (GitHub Actions)

name: webhook-replay
on:
  pull_request:
  push:
    branches: [ main ]

jobs:
  replay:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20

      # Recommended for CI determinism:
      # - run: npm ci
      # - run: npm run replay

      # Or via npx:
      - run: npx webhook-replay ./examples/handler.js

If the handler is UNSAFE, the job fails and the merge is blocked.


Why this exists

Retries are unavoidable.
Duplicate side effects are not.

webhook-replay makes retry bugs provable, reproducible, and fail-fast.


What to do if this reports UNSAFE

You have exactly two options:

  1. Write perfect idempotency logic everywhere and never make a mistake
  2. Enforce single-delivery before the webhook reaches your code

Retries are unavoidable.
Duplicate side effects are optional.

webhook-replay tells you which reality you are currently in.