Part 1 of 7 · Chargeback responder series ~10 min read

A chargeback responder on AWS for a few dollars a month

A chargeback is a small emergency on a deadline. A customer disputes a charge, the bank pulls the money straight back, and you have a tight, unforgiving window to prove the sale was real — or you lose the amount and the fee, every time. Doing that by hand means dropping what you’re doing to dig out an order, chase a tracking number, find the support thread, and write a coherent rebuttal before a date you’ll probably forget. This post walks through the design of a small system that catches every dispute the moment it lands, gathers the evidence a bank actually looks at, assembles a structured rebuttal, and files it on time — without ever inventing a thing.

Key takeaways

  • One dispute webhook in, one filed response out — the system runs the whole window in between, on a clock.
  • It gathers the four things a bank actually weighs: the order, proof of delivery, the customer’s messages, and the agreed policy.
  • Bedrock assembles a structured rebuttal tied to the dispute’s reason code; the deadline and the decision to file are plain code.
  • A deadline watch guarantees the response is filed before the bank’s cutoff — never late, with owner approval or on its own.
  • Designed on AWS for about $2.40/month at typical volume. It never fabricates evidence, and on an unwinnable case it says so.

The whole system on one page

Before any code, here’s the shape of what we’re designing.

System architecture: one processor, the evidence sources, and three pieces inside AWS At the top, three external boxes in a row. Far left, "Payment processor" — the Stripe-style processor that posts a dispute (chargeback) webhook the moment a customer disputes a charge, and that the system later files the response back to through its API before the bank’s deadline. Centre, "Evidence sources" — the systems that actually hold the proof: the order record in the commerce platform, the carrier’s delivery tracking, the customer support inbox or chat, and the refund and shipping policy the customer agreed to at checkout. Far right, "Owner" — the business owner, who gets an email summary of each dispute with File, Hold, and Accept-loss buttons. Each connects by an arrow to the AWS account container below. The processor posts the dispute in and receives the filed response back. The evidence sources are read to ground every rebuttal. The owner receives the summary and taps a decision. Inside the AWS account are three components in a row, mirroring the layout above. On the left, the Dispute intake — verifies the webhook signature, de-duplicates, records the bank’s evidence deadline, and arms a one-off deadline watch. In the middle, the Evidence and packet engine — gathers the order, delivery proof, customer messages, and agreed policy into S3, then uses Bedrock to assemble a structured rebuttal mapped to the reason code and render a packet PDF, with an honest winnability read. On the right, the Filing desk — files the response through the processor API before the deadline, honours the owner’s choice, falls back to the deadline guard so nothing lapses, and tracks whether the dispute is won or lost. Internal arrows flow left to right. A note at the bottom reads: the responder only ever assembles real evidence — it never invents a tracking number or a message, and on an unwinnable case it recommends accepting the loss. Payment processor dispute in, response out Evidence sources order, delivery, comms, agreed policy Owner file, hold, or accept dispute in grounds summary & decision AWS account Dispute intake verify, de-dupe, arm the deadline watch Evidence + packet gather four sources, assemble the rebuttal Filing desk file before the deadline, track won or lost case packet The responder only assembles real evidence — it never invents a thing, and an unwinnable case it tells you to drop.
Fig 1. One processor outside, the evidence sources in the middle, three pieces inside AWS. A dispute flows in; the intake arms a deadline watch; the engine gathers evidence and assembles a rebuttal; the filing desk files before the bank’s cutoff and tracks the result.

What you set up once (the outside)

  • The processor connection. One processor (Stripe, or a Stripe-style gateway) is wired two ways. Outbound from them: a webhook endpoint that fires on charge.dispute.created and charge.dispute.closed, pointed at a Lambda Function URL. Inbound to them: an API key, kept in Secrets Manager, that the system uses to read the full dispute and to submit the evidence before the bank’s deadline. The deadline itself comes from the processor on the dispute object — the evidence_details.due_by timestamp — so the system never has to guess how long it has.
  • The evidence sources. The four systems that already hold your proof. The order record in your commerce platform (what was bought, when, for how much, the billing and shipping addresses, the AVS and CVC checks at the time of sale). The carrier that delivered it, read through its tracking API for the delivery confirmation, the timestamp, and a signature or photo where there is one. The support inbox or chat where the customer’s own messages live. And the refund and shipping policy the customer agreed to at checkout, kept with the version and the timestamp of their acceptance. You don’t build any of these — the system just reads them.
  • The owner. The person who decides whether a given dispute is worth fighting. They get one email per dispute: the amount, the reason code, a plain-English read of how strong the evidence is, and three buttons — File (send the assembled packet now), Hold (pause for a closer look), and Accept loss (don’t fight it). In auto-file mode the strong cases file themselves and the email is just a heads-up; in owner-approval mode nothing files until they tap.

What runs on every dispute (the inside)

  • The dispute intake. The webhook lands on a Function URL. A small Lambda verifies the processor’s signature so a forged dispute can’t enter, throws away the processor’s inevitable retries with an idempotency key, and reads the dispute: amount, currency, reason code, the charge it disputes, and the due_by. It writes a row to the disputes table, records the deadline, and arms a one-off EventBridge Scheduler job for a safe margin before that deadline — the watch that guarantees the case can never silently run out of time. Then it enqueues the gather job.
  • The evidence and packet engine. A gather Lambda pulls the four sources — order, delivery proof, customer messages, agreed policy — into an S3 evidence bucket and writes an index row per piece, marking honestly where something is missing. An assemble Lambda then makes one Bedrock Haiku 4.5 call: it hands the model the gathered evidence and the reason code and gets back a structured rebuttal that ties each real piece to the specific claim the bank is testing, plus a winnability read. It renders the packet to a PDF in S3. No model decides whether to file, and no model is allowed to write evidence that wasn’t gathered.
  • The filing desk. Takes the assembled packet and, per the mode and the owner’s choice, files it through the processor’s API — always before the deadline. If the owner hasn’t answered and hasn’t chosen to accept the loss, the deadline guard files the assembled packet at the safety cutoff rather than let the case lapse. When the processor later posts dispute.closed, it records won or lost against the case, so the trail is complete. A monthly summary writes a short narrative: disputes received, value defended, win rate by reason code.

In plain words

An £85 order from your store is disputed as “product not received.” At 02:14 the processor posts charge.dispute.created; the bank’s evidence is due in fourteen days. The intake verifies it, records the deadline, and arms the watch. The gather Lambda pulls the order (placed, paid, shipped to the billing address), the carrier record (delivered, signed for, with a timestamp three days after the order), the support thread (the customer messaged “got it, thanks” the next week), and the shipping policy they accepted at checkout. Bedrock assembles a rebuttal: “Reason code: product not received. Carrier confirms delivery on 14 March, signed; customer acknowledged receipt on 21 March; shipped to the verified billing address.” It rates the case strong. The owner gets the summary, taps File, and the packet is submitted that morning — ten days early. Three weeks later dispute.closed arrives: won. The £85 stays, and so would have happened automatically if the owner had been on holiday.

The cost of running this is about $2.40 a month. The cost of not running it is every dispute you lose because the deadline slipped past while you were busy — the disputed amount, plus the non-refundable £15–20 dispute fee, on every single one.

Design rules that shaped every decision

  • It never fabricates evidence. Every line in the packet traces to a real record — an order, a tracking event, a message, a policy.
  • The deadline is sacred. A one-off watch files the assembled packet before the cutoff, so a case can never lapse by default.
  • An unwinnable case gets said out loud. If the proof isn’t there, the system recommends accepting the loss, not wasting the fee.
  • No model touches money. Whether to file and when are plain code; Bedrock only assembles the rebuttal from gathered evidence.
  • Auto-file or owner-approval is a setting. You choose how much happens without you; either way nothing files late.
  • Every dispute and outcome is logged. Audit a chargeback next year and you can see what was filed, when, and why.

Why this shape

Most small businesses handle chargebacks one of three ways: they don’t fight them at all (and quietly write off the losses), they fight them by hand under time pressure (and miss half the deadlines), or they pay a percentage-of-recovery service that takes a cut of every win. The first leaves money on the table. The second is exactly the kind of dull, deadline-bound work that slips when the week gets busy — and a missed deadline isn’t a delay, it’s an automatic loss. The third works but is expensive at SMB volume and opaque about what it files in your name.

The setup above keeps your own systems — the order, the carrier, the inbox, the policy — as the only source of truth, and adds a small system that reacts the moment a dispute opens. It gathers the real evidence, assembles a rebuttal a bank will actually read, and files it early. Strong cases can clear in one tap, so you spend your attention only where it matters. Weak cases get flagged as weak, so you don’t burn a fee on a fight you can’t win. And the deadline can never beat you, because a guard files before the cutoff whether you’re watching or not.

The next four posts walk through each piece in turn: how a dispute gets caught, how the evidence gets gathered, how an evidence packet gets built, and how a response gets filed on time. One diagram per post. A cost breakdown and a final engineering reference at the end.

All posts