Part 4 of 7 · Chargeback responder series ~7 min read

How an evidence packet gets built

Gathering evidence is mechanical; turning it into a rebuttal a bank will actually read is the part where the system earns its keep. This post is about how one Bedrock call assembles the packet — mapping each piece of real evidence to the precise thing the dispute’s reason code is contesting, writing a tight cover narrative, and rendering it to a PDF — and, crucially, how it stays grounded: it never fabricates a tracking number or a message, and when the evidence simply isn’t there it says the case is unwinnable and recommends taking the loss.

Key takeaways

  • One Bedrock Haiku 4.5 call turns the gathered evidence and the reason code into a structured rebuttal.
  • A reason-code playbook tells the model which claim each piece of evidence is meant to answer.
  • The packet is rendered to a PDF in S3, with every claim citing the exact evidence it rests on.
  • The model gives an honest winnability read — and recommends accepting the loss when the proof isn’t there.
  • It is grounded strictly in the gathered evidence: it never writes a tracking number or a message that doesn’t exist.

The reason-code playbook does the thinking up front

A good rebuttal answers the specific thing the bank is testing, and that thing is set by the reason code. So the assemble Lambda starts from a small playbook — a plain text file in S3, editable without a deploy — that maps each common reason code to the claim it makes and the evidence that rebuts it. Product not received → rebut with proof of delivery to the customer’s address, plus any message acknowledging receipt. Unauthorised / fraudulent → rebut with the AVS and CVC match, the shipping-to-billing address, the customer’s device or order history. Subscription cancelled → rebut with the agreed terms and the cancellation timestamp showing the charge predated it. The playbook means the model isn’t guessing what a strong response looks like for this dispute — it’s following the structure card networks actually score against.

One grounded Bedrock call

The Lambda reads the evidence index from the previous step, pulls the relevant pieces out of S3, and makes a single Bedrock Haiku 4.5 call. The prompt hands the model three things: the dispute (amount, reason code, what the customer claims), the playbook entry for that reason code, and the gathered evidence — the order fields, the tracking events, the message excerpts, the policy text — each tagged with its evidence-index id. The model’s job is narrow and mechanical: write a tight cover narrative and a point-by-point rebuttal in which every claim cites the evidence id it rests on. It is told, firmly, to use only the evidence provided and to flag anything the playbook expects but the manifest marks missing. It returns structured JSON — the narrative, the cited points, the list of attached exhibits, and a winnability verdict — not free-form prose. Structured output is what lets the next step render a clean packet and lets the filer attach the right exhibits in the right fields.

How cbr-assemble turns evidence and a reason code into a packet, with a winnability branch Inside the AWS account boundary, three inputs on the left feed a central Bedrock call. The inputs are: the evidence index and the gathered pieces pulled from the S3 evidence bucket; the dispute itself with its amount and reason code; and the reason-code playbook, a text file in S3 mapping each reason code to the claim it makes and the evidence that rebuts it. These feed the centre box, cbr-assemble, which makes one Bedrock Claude Haiku 4.5 call. The call returns structured JSON: a cover narrative, a point-by-point rebuttal where every point cites an evidence id, the list of attached exhibits, and a winnability verdict. From cbr-assemble the flow branches two ways. The winnable branch renders a packet PDF to the S3 evidence bucket and marks the case ready to file, passing to the filing desk. The unwinnable branch — taken when the manifest is missing the decisive evidence — writes a recommend-accept-loss outcome and notifies the owner instead of building a packet to file. A note at the bottom reads: every claim cites real evidence; a gap is reported as a gap; no tracking number or message is ever invented. AWS account Evidence index + pieces from S3 The dispute amount, reason code Reason-code playbook cbr-assemble one Bedrock call Claude Haiku 4.5 → structured rebuttal Winnable render packet PDF → S3, mark ready to file Unwinnable recommend accept loss, notify the owner strong gap Every claim cites real evidence. A gap is reported as a gap. No tracking number or message is ever invented.
Fig 4. Assembly. The evidence, the dispute, and the reason-code playbook feed one grounded Bedrock call; a strong case becomes a packet PDF ready to file, a case with a decisive gap becomes an honest recommendation to accept the loss.

Render the packet to a PDF

The structured rebuttal is turned into a single PDF in the S3 evidence bucket: a cover page with the dispute reference and the cover narrative, the point-by-point rebuttal, and then the exhibits — the order confirmation, the carrier’s proof of delivery, the relevant message excerpts, the accepted policy — each labelled and cross-referenced from the narrative. Rendering happens in the Lambda from a fixed template, so the layout is consistent and nothing about it depends on the model. Many processors also take evidence as discrete fields and file attachments through their API rather than one PDF; the assemble step produces both shapes from the same structured output, so the filer can submit whatever this processor expects.

An honest winnability read

The same call returns a winnability verdict — strong, mixed, or weak — with a one-line reason grounded in the manifest. Strong: the decisive evidence for this reason code is present and consistent. Mixed: the evidence is there but has a soft spot (delivered, but to a slightly different address; acknowledged, but with a complaint). Weak: the decisive piece is missing or actively unhelpful — no proof of delivery on a not-received claim, or a support thread where the customer was promised a refund that never came. On a weak case the system doesn’t quietly file a hopeless packet; it writes a recommend accept-loss outcome and tells the owner why. Fighting a chargeback you’ll lose still costs the dispute fee and your attention, so knowing when to stand down is part of doing this well.

Why it can’t fabricate

The guardrail against invented evidence isn’t a polite instruction — it’s the structure. The model is given the evidence manifest and told to cite an evidence id on every claim; the renderer then checks each cited id actually exists in the index before it puts the claim in the packet. A claim that cites nothing, or cites a piece marked missing, is dropped, not printed. So the worst a model drift can do is produce a weaker packet — never a fictional one. The customer’s tracking number is the carrier’s tracking number or it isn’t in the packet at all; the “customer acknowledged receipt” line exists only if a real message is attached behind it. This is what makes the system safe to point at a bank: it can only ever say true things about what you have.

Design rules for building the packet

  • Start from the reason-code playbook, so the rebuttal answers the exact claim the bank is testing.
  • One grounded Bedrock call, structured output, every point citing a real evidence id.
  • Render from a fixed template, in both PDF and field-and-attachment form, so any processor’s API can take it.
  • Give an honest winnability read, and recommend accepting the loss when the decisive evidence is missing.
  • Cited-evidence checks at render time mean a model can only weaken a packet, never invent one.
All posts