How the evidence gets gathered
A rebuttal is only as good as the evidence under it, so this is the part that does the digging. This post is about how the system gathers the four things a card network’s rules actually ask for — what was ordered, that it was delivered, that the customer engaged with it, and what they agreed to — each pulled from the system that really holds it, stored once, and indexed so the next step knows exactly what it has and, just as importantly, what it doesn’t.
Key takeaways
- One gather Lambda pulls four things, because four things are what a card network’s rules actually weigh.
- The order record, proof of delivery and tracking, the customer’s own messages, and the agreed policy — each from its real source.
- Everything gathered is written once to an S3 evidence bucket and indexed, one row per piece, in DynamoDB.
- The reason code decides which pieces matter most, so the gather is targeted, not a blind sweep.
- When a piece is missing, the index records it as missing — the system never papers over a gap.
Four things, because that’s what banks weigh
A card network doesn’t want a folder of everything you have; it wants the few specific things that answer the dispute. Across the common reason codes, those few things come down to four. The order record: what was bought, when, for how much, the billing and shipping addresses, and the AVS and CVC results captured at the time of sale — this is what proves a real, authorised purchase happened. The proof of delivery and tracking: the carrier’s confirmation that the goods reached the customer, with a timestamp and a signature or photo where one exists — this answers “I never got it.” The customer’s own messages: the support thread or chat where they asked about, used, or acknowledged the order — a customer who emailed “thanks, it arrived” has undercut their own dispute. And the agreed policy: the refund, shipping, or subscription terms the customer accepted at checkout, with the version and the timestamp of their acceptance — this answers “I was charged after I cancelled” or “they never told me it was final sale.”
Targeted by reason code, not a blind sweep
The dispute’s reason code, read at the front door, tells the gather Lambda which of the four pieces carry the weight. A product-not-received dispute lives or dies on the delivery proof, so that source is pulled first and hardest. A fraudulent / unauthorised dispute turns on the order record — the AVS match, the CVC result, the shipping-to-billing address, the customer’s prior order history. A subscription-cancelled or credit-not-processed dispute turns on the agreed policy and the cancellation timestamp. The gather always collects all four where they exist (a strong packet leans on more than one), but it knows which is decisive, so it spends its effort and its retries where the case will actually be won or lost.
Store once, index every piece
Each piece the gather Lambda retrieves is written to the S3 evidence bucket under a prefix keyed by the dispute id — the order JSON, the carrier’s tracking response, the exported message thread, the policy snapshot at the version the customer accepted. Storing the raw source means the packet built later is reproducible: anyone can see exactly what the rebuttal was assembled from. Alongside each S3 write, the Lambda puts one row into the evidence index in DynamoDB: the dispute id, the evidence type, the S3 key, the source it came from, the timestamp it was gathered, and a flag for whether it was found. The index is the manifest the next step reads — it never has to crawl S3 to discover what evidence exists.
Carriers and inboxes are flaky, so gather is built to retry. It runs off the SQS queue with a dead-letter queue behind it: a transient carrier-API error is retried a couple of times, and only a genuinely stuck gather lands in the DLQ with an alarm, where it surfaces to a human instead of silently blocking the case. A delivery confirmation that isn’t available yet (the parcel is still in transit) isn’t an error — it’s recorded as not-yet-available, and the deadline watch will trigger a re-gather closer to the cutoff.
A missing piece is data, not a blank
The single most important rule in this step: when a piece of evidence doesn’t exist, the system records that it doesn’t exist. No delivery confirmation for a digital product. No support thread because the customer never wrote in. No signature because the carrier doesn’t capture one for low-value parcels. Each gap is written to the index as missing, with the reason. This is what keeps the whole system honest downstream: the packet-builder is handed a truthful manifest of what’s there and what isn’t, so it can never be tempted — or asked — to fill a hole with something invented. A gap in the evidence becomes a known fact about the case’s strength, not a silent omission.
Design rules for gathering evidence
- Four sources, every time: order, delivery proof, customer messages, agreed policy. Those are what banks weigh.
- Targeted by reason code. The decisive source for this dispute is pulled first and hardest.
- Store the raw source in S3 once, so the packet built from it is always reproducible.
- Index one row per piece, so the next step reads a manifest instead of crawling storage.
- Missing is recorded as missing. A gap is a known fact about the case, never a blank to be filled.