If your product accepts a bank statement as proof of income, address or balance, you have a document-fraud problem whether or not you have a document-fraud programme. This page is about the shape of that problem, and about a small, self-serve piece of it: reading what a PDF records about its own editing history and handing that back to your decision logic as JSON.
Why statements get forged
A bank statement is the most widely accepted, least verifiable document in consumer finance. It is asked for by lenders and BNPL underwriters as income and affordability evidence, by letting agents and proptech platforms as proof of rent affordability, by gig and marketplace platforms during payout onboarding, by neobanks as proof of address during KYC, and by brokers at refresh time. In most of those flows it arrives as a PDF the applicant uploaded, and in a lot of them nothing downstream ever compares it against the issuing bank.
That combination — high decision weight, self-supplied artefact, no authoritative counterparty — is what makes it attractive. Open banking removes the problem where it is available and the applicant consents; the PDF upload path is the fallback that stays open, and the fallback is where the pressure goes.
The incentive is not always criminal-syndicate scale. A great deal of it is a single applicant, ten minutes before a deadline, who is £400 short of a threshold and has just discovered that a free online PDF editor will let them retype a number. That population is large, unsophisticated, and — for exactly that reason — detectable.
What naive statement fraud looks like
Sophisticated document fraud exists, and nothing on this page claims to stop it. But the everyday volume falls into a small number of recognisable patterns:
- Edit the real thing. Download the genuine statement from online banking, open it in a browser-based PDF editor, white out a figure, type a new one, download the result. Total elapsed time: two minutes. The editor writes its own name into the file's metadata and, depending on the tool, either appends an incremental update or rewrites the whole document.
- Print, alter, rescan. Or its digital equivalent: render the statement to an image, edit the image, wrap it back into a PDF. This defeats text-level checks and produces a file whose pages are full-page rasters. Very often the forger then wants the numbers to be selectable and adds a text layer, or the edit tool drops vector text on top of the image.
- Template generation. Buy or build a statement template, populate it with invented transactions, export. These files can look immaculate on screen and carry entirely coherent metadata, because they were genuinely generated once by a single tool. Structural forensics is weak here; what betrays them is usually layout, typography and arithmetic — a different class of check.
- Reuse someone else's. The same PDF, resubmitted with a name changed, or an unaltered statement belonging to a different person. Nothing structural is wrong with the file at all.
- Sign, then modify. Where statements are digitally signed — common in parts of the EU and LATAM — appending an incremental update after signing leaves the signature intact over the original byte range and the new content outside it. Some viewers show this clearly; users click through it.
Note which of these are addressable by looking at file structure: the first two, plus the fifth, strongly; the third and fourth, barely or not at all. An honest tool tells you which bucket it plays in.
The traces it leaves
A statement is a machine-generated document. That is the whole basis of the method: the file is supposed to have been written exactly once, by a server-side library, in one pass, with one set of metadata and one font subset per typeface. Every deviation from "written once by one tool" is a question you can ask the applicant.
-
More than one revision. Bytes appended after the first
%%EOF. Strongest when a later revision overwrites an object that already existed and that carries page, content-stream or image data — the rendered appearance changed after generation. - A consumer editor in the toolchain. Online converters, desktop PDF editors, image editors, office re-save pipelines. A core banking system does not emit statements through Photoshop.
- Two metadata stores that disagree. Info dictionary versus XMP packet. Editors frequently update one and forget the other, and sometimes only one of them names the editor at all.
- Two subsets of one typeface. Glyphs of the same font embedded on two separate occasions — the signature of text added to an existing document by a second tool.
- Visible vector text over a full-page scan. Legitimate scanned documents carry either no text layer or an invisible OCR layer. A handful of visible glyph runs over a raster page is how values on a scan get altered.
-
Bytes outside a signature's
/ByteRange. Content appended after signing, provable by comparing two integers, independent of whether the certificate validates.
Each of these is documented in full — including the benign explanations, which matter more than the malicious ones for anyone building a review queue — in the field guide. The manual version of the same checks is in how to detect if a PDF was edited.
Why enterprise tooling is out of reach
Document-fraud detection is a solved commercial problem at the top of the market. The established platforms are genuinely good and genuinely enterprise-shaped: a demo request form instead of a signup page, a sales cycle, an MSA, an annual minimum, and a per-document price that assumes you are processing at institutional volume. Several of them also involve a human-in-the-loop review step, which means latency measured in minutes and your applicants' documents sitting in someone else's review queue.
If you are three engineers at a seed-stage lender processing a few hundred statements a month, none of that is buyable. The usual result is that the upload endpoint ships with no document checks at all and the problem is rediscovered after the first loss.
Tamperlens is deliberately the small end of that market: sign up, get a key, make an HTTP call. No sales conversation, no minimum, no review queue. The trade-off is honest — it is a structural-forensics engine, not a full fraud platform, and it returns evidence rather than decisions.
Stateless by design. Uploaded bytes are parsed in memory and discarded when the response is written. No document is stored, no document is logged, nothing is sent to a third party, and there is no human review step. For a compliance team, "we never hold the file" is a materially easier conversation than a data-processing addendum about a review queue.
The API, in one call
One endpoint: POST /api/v1/inspect. It takes either
multipart/form-data with a file field, or a raw
application/pdf body, up to 10 MB. Authentication is a bearer
key. Free tier: 25 documents a month, no card.
multipart
curl -s https://tamperlens.com/api/v1/inspect \
-H "Authorization: Bearer tl_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-F [email protected] | jq .
raw body — no multipart encoding, useful from a worker
curl -s https://tamperlens.com/api/v1/inspect \
-H "Authorization: Bearer tl_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/pdf" \
--data-binary @statement.pdf | jq .
A response for a statement that was run through a free online editor looks like
this. detail strings are abridged here for width; in the real
response they are full paragraphs, and evidence carries the
offsets and object numbers behind each claim.
200 OK
{
"id": "insp_2f1e9c04-7b3a-4d51-9f0e-6c2a18d4bb71",
"engineVersion": "1.2.0",
"summary": {
"riskScore": 100,
"riskBand": "high",
"signalCount": 3,
"revisions": 2
},
"signals": [
{
"id": "incremental-updates",
"severity": "high",
"title": "Page content was replaced after the document was generated",
"detail": "The file contains 2 revisions. A later revision re-writes 2 object(s) that already existed in an earlier revision and that carry page, content-stream or image data (objects 12, 14). …",
"evidence": {
"revisions": 2,
"updatesAfterCreation": 1,
"eofOffsets": [118842, 184102],
"startxrefValues": [118201, 183640],
"revisionChainBroken": false,
"contentObjectsOverwritten": [
{ "obj": 12, "kind": "content" },
{ "obj": 14, "kind": "page" }
]
}
},
{
"id": "producer-fingerprint",
"severity": "high",
"title": "Consumer editing tool in the production chain (iLovePDF)",
"detail": "The document's metadata names iLovePDF in its production chain. Statements, invoices and certificates are normally emitted directly by a server-side generator … corroborated here by 2 document revisions.",
"evidence": {
"tools": ["iLovePDF"],
"matches": [
{ "field": "Info:Producer", "value": "iLovePDF", "tool": "iLovePDF", "category": "online-editor" }
],
"producer": "iLovePDF",
"creator": "iText 7.2.5",
"revisions": 2,
"fullPageImagePages": []
}
},
{
"id": "id-inconsistency",
"severity": "medium",
"title": "Trailer /ID marks the file as changed since creation",
"detail": "The trailer /ID array holds two different identifiers. The first element is set once when a document is created and must never change …",
"evidence": {
"idOriginal": "8f2c...a91b",
"idCurrent": "41de...77c0",
"trailerCount": 2
}
}
],
"document": {
"pages": 2,
"producer": "iLovePDF",
"creator": "iText 7.2.5",
"creationDate": "2026-01-04T10:02:00.000Z",
"modDate": "2026-01-06T18:41:00.000Z",
"encrypted": false,
"signed": false,
"revisions": 2,
"sizeBytes": 184320
},
"disclaimer": "Tamperlens reports risk signals, not authenticity verdicts. Signals can have benign causes; combine them with your own decision logic."
}
riskScore is a weighted aggregation, not a probability. The
contract is deliberately narrow: any high-severity signal forces the score to
at least 70, and the high band is reserved for high-severity signals —
a pile-up of weak findings is clamped at 69. So
riskBand === "high" means "at least one finding that on its own
establishes a change", which is a statement you can defend in a file note. The
weights are versioned by engineVersion; the same bytes always
produce the same score.
Full field reference, error codes and quotas: API quickstart and the OpenAPI spec at /docs.
Wiring signals into a decision
The whole design assumes you own the decision. Some patterns that work:
-
Route, do not reject. Map
riskBandto a queue:lowcontinues automatically,elevatedgoes to manual review,highgoes to manual review with the evidence attached and a prompt for a fresh document. Auto-declining on a structural signal will decline real customers who re-saved a file in Preview. -
Store the evidence, not the file. Persist the signal ids,
severities and
evidenceobjects against the application. That is your audit trail, it is small, and it contains no document content. - Ask for a specific replacement. The single most effective response to a structural signal is "please re-download the original PDF from your bank and upload it without opening it in any other application". Genuine applicants can do this; the second submission is often more informative than the first.
-
Tune per signal, not per score. If your flow legitimately
involves customers signing statements,
incremental-updateswill fire constantly and you should weight it down for that flow while keepingsignature-coverageandfont-anomaliesat full weight. - Baseline your own traffic first. Run the checker or the free tier over a sample of statements you already believe are genuine, per issuing bank, before you set any threshold. Producer strings and revision habits vary enormously between banks, and knowing what "normal" looks like for your actual applicant population is worth more than any default we could ship.
What this does not do
Stated plainly, because a fraud tool that oversells itself is worse than none:
- It does not read your statement. There is no OCR, no transaction parsing, no balance arithmetic, no name matching. It will not tell you that a closing balance does not equal the sum of the transactions.
-
It does not verify identity or account ownership. A perfectly
genuine, untouched statement belonging to somebody else scores
low, correctly. - It does not detect a well-made template forgery. A file generated once, cleanly, from a fake template has nothing structurally wrong with it.
- It does not validate signature cryptography. It reports what a signature covers. Certificate and trust-chain validation is a different job.
- It does not survive a determined adversary forever. A forger who knows about revision history can flatten it. That is a real limit — and also why the signal set is versioned and grows: each new evasion technique leaves its own trace.
-
It does not issue a verdict. There is no
"fraud": truefield and there will not be one.
Try it on a real file first
The free checker takes a drag-and-dropped PDF and shows the same report the API returns — no account, nothing stored. Put a statement you know is genuine through it, then one you have deliberately edited in an online tool, and you will see immediately whether the signals are useful for your flow. Then get a key: 25 documents a month free, $15/mo for 200, $49/mo for 1,000, $199/mo for 10,000.
Tamperlens reports risk signals, not authenticity verdicts. Signals can have benign causes; combine them with your own decision logic.
Related reading
- PDF fraud signals field guide Every signal family, its benign causes, and how severity is decided.
- How to detect if a PDF was edited The manual checks, by hand, with shell commands.
- PDF metadata forensics Where the evidence physically lives inside the file format.
- API quickstart & pricing Auth, both request forms, error codes, quotas, plans.