How to verify a Veracly report is authentic
A compliance report is only useful if its reader trusts it. Every Veracly PDF is cryptographically signed and independently verifiable. Here is the mechanic, in plain English.
A compliance audit is a claim about a moment in time. The reader of the report — your developer, your lawyer, a regulator, a procurement team — has to trust that the claim is what Veracly actually issued, not something edited along the way. That trust is too important to rest on “we are a reputable vendor.” Every Veracly report carries a cryptographic proof anyone can verify in under a minute. Here is the mechanic.
What gets signed
On every scan, we compute the SHA-256 digest of the exact PDF bytes about to be uploaded. We then sign a canonical record — { scan_id, language, pdf_sha256, issued_at } — with Veracly’s private Ed25519 key, and persist the signature in our audit ledger. The signature does not live inside the PDF (embedding it would be circular: the embedded bytes would change the digest and invalidate the signature). Instead, the PDF surfaces the scan UUID as a lookup key.
The verification flow
Three steps. Anyone with the PDF can perform them:
- Read the scan UUID from the “Document integrity” block on the disclaimer page of the PDF.
- Compute the SHA-256 of the PDF on your local machine. On macOS / Linux:
shasum -a 256 report.pdf. On Windows:Get-FileHash report.pdf -Algorithm SHA256. - Visit
veracly.app/verify/<uuid>. The page shows the canonical record we signed and the public key it was signed with. You compare the SHA-256 you computed to the one shown. If they match, the signature is valid and the bytes in front of you are the bytes we issued.
The public key is also published at veracly.app/.well-known/veracly-signing-key.json. Anyone who wants to do the full verification themselves — without trusting the rendering of the verify page — can pull the public key, fetch the signed record, and run an Ed25519 verifier locally. The canonical record schema and a reference verifier in TypeScript are at packages/shared/src/auditLedger.ts in our open-source rules repository.
Why this matters for an SMB
Most SMBs will never use the verification flow themselves. The customers that ask for proof of authenticity are downstream: an enterprise procurement team running a vendor security review, a regulator following up on a complaint, an insurer underwriting cyber liability cover, a B2B prospect doing due diligence before signing a contract.
When one of those parties asks, the answer is no longer “here is a PDF, please trust it.” The answer is “here is the PDF and the verify URL — confirm it independently.” That is a categorically different conversation.
What we deliberately did not do
A few design choices worth naming, because the audit-ledger space has a lot of theater dressed as cryptography:
- We do not anchor to a public blockchain. A regulator does not want to learn about block heights to verify a compliance report. The well-known-URL plus Ed25519 keypair is the simplest construction that meets the actual requirement: independent verifiability without trusting Veracly.
- We do not embed the signature in the PDF. Tempting but circular. Any embedded value would either be a self-reference (the PDF says “my hash is X” — change the hash, change the PDF, mathematically broken) or a detached signature tucked in metadata that PDF viewers routinely strip on re-save.
- We do not require a Veracly account to verify. An authentication wall on a verification endpoint defeats the entire point of public-key cryptography. Authentication-gated audit trails are a controlled-narrative product; ours is not.
- We do not roll our own crypto. Ed25519 is the digital-signature algorithm specified in RFC 8032 and adopted across SSH, TLS, and SignAuth. The underlying libraries have been reviewed by people more qualified than we are.
What is coming next (phase 2)
The current ledger is per-scan: one signature per (scan, language). The phase-2 design adds three things:
- A daily Merkle root posted to
OpenTimestamps, so the entire day’s ledger is anchored to Bitcoin block time. Belt-and-braces against a future where someone disputes our private-key custody. - A documented key-rotation playbook published at the well-known URL. Every signing key has a validity window; an old key’s public counterpart stays published forever so historical reports remain verifiable.
- A transparency log of issued reports. We do not log the report contents (that would defeat customer privacy), only the (scan_id, sha256, issued_at) triples — so the population of signatures is itself auditable.
See also: Reading your first Veracly report · Sharing reports with developers, lawyers, and regulators
Common questions
Why does Veracly sign its reports?
Because the value of an audit report depends on the reader trusting it. An unsigned PDF can be edited; a signed one cannot — any byte change invalidates the signature. The threat model includes both bad-actor tampering and accidental drift (re-saving a PDF in a viewer that re-encodes it).
Do I need a Veracly account to verify a report?
No. Verification is fully public. The verify URL on every report uses no authentication; the public key is published at a well-known URL; the verifier code is open. A regulator who has only the PDF can confirm authenticity without ever contacting Veracly.
What changes if a signature does not verify?
The PDF in front of you is not the one Veracly issued. Either it has been edited (intentionally or by a re-encoding viewer), or it was never issued by Veracly. The verify page tells you which scan UUID was originally signed; if that scan exists in our ledger but the bytes disagree, the file has been altered.
See where your site stands.
Run a free Veracly scan and get a multi-jurisdiction report — EAA, GDPR, ADA, UK Equality Act, AODA — with copy-paste developer fixes.
Run a free scan