All articles

tutorial

How to Test Webhook Edge Cases (Disputes, Partial Refunds, Retries)

The webhook events that break production are the rare ones: disputes, partial refunds, failed renewals, out-of-order retries. Anonymily's AI generates realistic, correctly-signed edge-case events on demand so you can test them before they happen.

How to Test Webhook Edge Cases (Disputes, Partial Refunds, Retries)

The happy-path webhook is easy: a payment succeeds, you mark the order paid. The events that actually take down production are the rare ones — a chargeback dispute, a partial refund, a failed subscription renewal, a duplicate retry arriving out of order. They're hard to test precisely because they're rare: you can't conjure a real dispute on demand, and "wait for one to happen in production" is not a test strategy.

Anonymily solves this with AI-generated edge-case events: describe the scenario in plain English and it produces a realistic, correctly-signed payload for that provider and event, then fires it at your hook. You test the disaster before it happens.


The pain: you can't trigger the scary events

  • Disputes / chargebacks — you can't make a real customer dispute a charge to test your handler.
  • Partial refunds — easy to assume "refund = full amount" and break when a partial one arrives with a different amount_refunded.
  • Failed renewals — subscription invoice.payment_failed only happens when a real card actually fails.
  • Out-of-order retries — providers retry and reorder; does your handler stay correct if order.paid arrives before order.created?
  • Malformed / unexpected shapes — the field that's usually present but occasionally null.

Static fixtures help a little, but they go stale and rarely cover the combination that bites you. You need realistic, varied, signed events for arbitrary scenarios.


The painful manual way

  1. Hand-craft a JSON fixture and guess what a dispute payload looks like.
  2. Realize it won't pass signature verification because it isn't signed.
  3. Figure out the provider's HMAC scheme and sign it yourself.
  4. POST it and hope the shape resembles the real thing.
  5. Do it again for the next edge case.

Slow, error-prone, and your fixtures drift from reality over time.


The Anonymily way: describe the edge case, get a signed event

Step 1 — Listen

npx @anonymilyhq/cli listen 3000

Step 2 — Generate and fire an edge-case event

In your editor (MCP): describe it in plain English to Claude or Cursor —

"Trigger a disputed-charge edge case on hook ab12cd34 for Stripe charge.dispute.created."

That calls the trigger_edge_case tool. Or via the API:

curl -X POST https://api.anonymily.com/v1/hooks/<hookId>/ai-trigger \
  -H "Authorization: Bearer $ANONYMILY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "provider": "stripe",
        "event": "charge.dispute.created",
        "scenario": "a customer disputes a $40 charge as fraudulent, funds withdrawn pending review"
      }'

Anonymily generates a realistic payload for that scenario, signs it correctly, and fires it through the real trigger pipeline so it arrives at your handler exactly like a genuine event:

{
  "requestId": "5e3b...",
  "signatureAdded": true,
  "description": "Stripe charge.dispute.created — $40 charge disputed as fraudulent, funds withdrawn",
  "payload": { "type": "charge.dispute.created", "data": { "object": { "amount": 4000, "reason": "fraudulent", "status": "needs_response" } } }
}

Step 3 — Ground it in a real event (optional but powerful)

Pass base_request_id to base the edge case on a real captured request, so the structure matches your account exactly and only the edge-case fields vary:

{
  "provider": "stripe",
  "event": "charge.refunded",
  "scenario": "partial refund of $12 on a $50 charge, leaving it partially_refunded",
  "base_request_id": "<a-real-captured-request-id>"
}

Now the partial-refund event mirrors your real payload shape with a realistic amount_refunded — the exact case a naive handler gets wrong.


Edge cases worth generating

Scenario Why it breaks handlers
Chargeback / dispute created Often unhandled entirely; needs to pause fulfillment
Partial refund Handlers assume full-amount refunds
Failed subscription renewal Dunning/grace-period logic is rarely tested
Duplicate retry (same event ID twice) Reveals missing idempotency
Out-of-order delivery paid before created corrupts state
Zero / negative / huge amounts Off-by-one and overflow assumptions
Missing optional field Null-handling gaps

Generate each, fire it, and confirm your handler does the right thing — before a real customer triggers it.

Confirm with replay

Once an edge-case event is captured, replay it as you fix your handler, so you iterate against the exact same payload:

npx @anonymilyhq/cli replay <hookId> <requestId>

Why this is unique

Provider "send test event" buttons fire a single canned payload. Static fixtures are manual and go stale. Anonymily generates realistic, signed, scenario-specific events for any provider/event combination on demand — and can ground them in your real traffic. That's a capability no tunnel or inspector offers, and it's the difference between hoping your edge-case handling works and proving it does.


Next steps


TL;DR

The webhooks that break production are the rare ones you can't easily trigger. Describe the scenario — dispute, partial refund, failed renewal, out-of-order retry — and Anonymily generates a realistic, correctly-signed event and fires it at your handler, optionally grounded in your real traffic:

npx @anonymilyhq/cli listen 3000

Try it in 30 seconds

Capture your first webhook — from any provider — with one command. No account required.

npx @anonymilyhq/cli listen 3000Open Dashboard →