Skip to content

Unforged

Unforged

Checks a UPI payment screenshot against the bank's own signed alert.

A UPI screenshot can be edited, or a real one shown again for a second order. Unforged checks it against the credit alert the seller's bank emailed, which the bank signs with DKIM. Each bank credit pays for one order only, enforced by a unique key in Aurora DSQL. Built for the AWS Builder Center Zero to Shipped hackathon.

20/20rounds with one winner
37/37edited emails rejected
0/500requests failed

Problem

A seller paid to a personal UPI ID has a few seconds to trust the buyer's screenshot. Any photo editor changes the amount, and last week's real screenshot passes every lookup when shown again. Matching the UTR in the bank app catches an edit. It never catches the real screenshot shown twice.

Approach

  • Treat the bank's credit alert email as the record. It is signed with DKIM, so one changed character breaks it.
  • Let each signed credit be claimed once. The claim is one Aurora DSQL transaction against a unique key, and a second claim comes back Already claimed.
  • Write a receipt signed by AWS KMS in the same transaction. Each receipt hashes the one before it, so the ledger is a chain anyone can recheck.
  • Read the screenshot with Amazon Textract, then let code return one of six verdicts with its reason. There is no fraud score.
  • Built with Claude Code connected through the AWS MCP Server as its own IAM user. CloudTrail recorded 1,198 of its calls.

Outcome

Live on CloudFront, API Gateway, Lambda, Aurora DSQL, KMS and Textract in one CDK stack. Fifty simultaneous claims on one credit gave one winner in each of 20 rounds, where a plain check-then-insert approved 1,000. A real bank alert and a real phone screenshot are not tested yet.

Highlights

  • DKIM signature on the bank's credit alert is the source of truth, not the screenshot.

  • Each credit is claimed once through a unique key in Aurora DSQL, with conflict retries for optimistic concurrency.

  • Receipts signed by AWS KMS, hash-chained, and checked in the browser with WebCrypto.

  • Textract reads the screenshot. A field with two candidates stays empty instead of being guessed.

Stack

  • TypeScript
  • AWS
  • Aurora DSQL
  • AWS Lambda
  • Amazon Textract
  • AWS KMS
  • AWS CDK