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_failedonly happens when a real card actually fails. - Out-of-order retries — providers retry and reorder; does your handler stay correct if
order.paidarrives beforeorder.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
- Hand-craft a JSON fixture and guess what a dispute payload looks like.
- Realize it won't pass signature verification because it isn't signed.
- Figure out the provider's HMAC scheme and sign it yourself.
- POST it and hope the shape resembles the real thing.
- 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
ab12cd34for Stripecharge.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
- AI webhook debugging — when an edge case does fail, get the root cause instantly.
- Auto-generate a webhook handler — generate handling for the edge cases you just discovered.
- The Complete Guide to Webhooks — retries, idempotency, and ordering in depth.
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