Check a Captilo proof without Captilo
A Captilo certificate doesn’t depend on Captilo. With the certificate, standard tools and the public Sonic blockchain, anyone with IT skills can confirm that a photo and its record are exactly what was anchored, and when. Here are the four checks, and a script that runs them for you.
- You need
- The certificate PDF
- Tools
- Poppler, Python or any SHA-256 tool
- Blockchain
- Sonic, via sonicscan.org or any node
- Captilo servers
- Not involved
Four checks, all against values you can see
Each check compares something you compute yourself with the file itself, a value printed on the certificate or the record stored on the blockchain. A proof holds only if all four pass.
- 1
The certificate is as Captilo issued it
The fingerprints prove the bytes in the file, not what a PDF viewer shows: someone could leave the genuine photo in the file and draw another picture over it, shrink it to a speck, add a page, or save the file again with changes. So look at the file first. It must have exactly one page per proof (a multi-proof certificate prints “Proof N of M”) and exactly one %%EOF, because Captilo writes it in one go. On every page pdfimages must list exactly one image with enc “jpeg”, the photo, drawn at least 200 pt on its longer side (width × 72 ÷ x-ppi), and at most two other images, the logo and the QR code, each at most 100 pt (the logo also has an smask line). The script checks the rest: nothing drawn over the photo, no other fonts, patterns or layers, and every object where the file’s cross-reference says it is.
pdfinfo certificate.pdf | grep Pages grep -c -a "%%EOF" certificate.pdf # must print 1 pdfimages -list certificate.pdfOne page per proof, one revision, one JPEG photo per page and nothing else but the logo and the QR code.
- 2
The photo is unchanged
The photo in a certificate is the original image file, embedded byte for byte. Extract it page by page with Poppler’s pdfimages: -j writes the page’s one JPEG as a .jpg file (the logo and QR code come out in other formats). Hash that file, never “the largest image in the file”: the photo that counts is the one the page draws.
pdfimages -j -f 1 -l 1 certificate.pdf page1 # page 1: exactly one page1-*.jpg sha256sum page1-*.jpg # Linux shasum -a 256 page1-*.jpg # macOS Get-FileHash page1-*.jpg -Algorithm SHA256 # Windows PowerShellThe fingerprint equals the Picture Hash.
- 3
The record is unchanged
Below the hashes, the certificate prints the signed record of the capture: time, place, device and check results, as canonical JSON (RFC 8785). The certificate states the rule itself: the SHA-256 of this exact text is the Metadata Hash. Extract the text, then join the record’s lines from its opening { to its closing } without adding or removing a single character, not even a final line break. The same record is embedded in the file; the script also checks that the printed one is identical to it.
pdftotext -raw certificate.pdf certificate.txt # join the record’s lines into record.json, then: sha256sum record.jsonThe fingerprint equals the Metadata Hash.
- 4
Both fingerprints are on the blockchain
Without writing code: open the certificate’s Sonic TX link. It shows the transaction on sonicscan.org, a public block explorer that Captilo doesn’t run. Check that the transaction succeeded and was sent to the Captilo registry, then look at its raw input data: the Picture Hash, the Metadata Hash and the Event ID without its dashes all appear there in hexadecimal. Or ask any Sonic node directly:
curl -s https://rpc.soniclabs.com -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "eth_call", "params": [{ "to": "0x3A6873D7A36E3FcD89AF557a24de2DaAFf09cB9b", "data": "0x1b80bb3a00000000000000000000000000000000<Event ID without dashes>" }, "latest"] }'The reply is 0x followed by eight 64-character blocks: the picture hash, the metadata hash, two internal IDs, the capture and anchoring times as Unix seconds in hex, the proof version, and a last block ending in 1 when the proof exists. The version is written by Captilo’s server, not the phone: 3 means the server consumed a live capture nonce (an online capture). For a proof anchored since 2026-09-04 08:21:52 UTC, a version of 2 means no nonce was consumed: treat it as an offline capture (device-clock time only), whatever its record says. If the node cannot be reached, nothing was checked: try again.
The registry returns both hashes under the event ID, with the time the proof was anchored.
Run all four checks with one script
verify_captilo_certificate.py checks the file and every proof page of a certificate, by the same rules as captilo.app and the Captilo app, in its own code. It uses only the Python standard library and Poppler’s pdftotext, contacts nothing but the Sonic node you choose, and can be read before you run it.
Needs Python 3.8 or later and Poppler: poppler-utils on Linux, brew install poppler on macOS, or a Windows build. We run it on a set of genuine certificates and forgeries (a photo swapped, a picture drawn over the photo, pages added or swapped, the record stripped); it gives the same verdict as captilo.app on each.
Download verify_captilo_certificate.pypython3 verify_captilo_certificate.py certificate.pdf
# optional:
# --rpc <any Sonic node>
# --poppler <folder with pdftotext>
# --json (the result as JSON)
# --registry <contract address>Output for a genuine certificate, shortened
Registry 0x3A6873D7A36E3FcD89AF557a24de2DaAFf09cB9b on Sonic (chain 146) via https://rpc.soniclabs.com
Page 1: event 6e63c095-5d97-57b5-8f85-26932c6c4147
picture SHA-256 (the photo the page draws) f9289612261be27f…3eb433342deb
record SHA-256 (embedded metaCanon) 6550edb0ae528f75…e73344b51
VERIFIED
anchored at 2026-09-17 11:36:28 UTC; capture online
VERIFIED: 1 of 1 proof(s) verified.Reference
Everything a developer needs to check proofs programmatically. Every certificate’s Sonic TX link also shows the registry address as the transaction’s recipient.
- Network
- Sonic mainnet, chain ID 146
- Registry contract
- CaptiloProofRegistryV20x3A6873D7A36E3FcD89AF557a24de2DaAFf09cB9b
- Lookup
- getProof(bytes32 eventId), function selector 0x1b80bb3a. Returns pictureHash, metadataHash, orgId, tenantId, deviceTimestamp, blockTimestamp, version and exists.
- Event ID as bytes32
- Remove the dashes and pad with zeros on the left to 64 hex characters.
- Public RPC
- https://rpc.soniclabs.com, or any other Sonic mainnet node
- Fingerprints
- SHA-256, written as lowercase hexadecimal
What the checks prove, and what they don’t
They prove that the photo and its signed record are exactly what was anchored on the Sonic blockchain, and when it was anchored. Nothing in these checks relies on Captilo: the certificate, a public block explorer or node, and standard tools are enough.
The eIDAS qualified timestamp is printed on the certificate but its token is not embedded in the file, so without Captilo the blockchain’s block time is the independent time reference.
What a page shows is checked as far as images go: one photo, drawn whole, with nothing over it. Shapes or text drawn over the photo are not detected, so compare the picture you see with the JPEG you extracted.
A proof shows what the phone recorded, not who pressed the shutter or whether the scene was staged. The Disclaimer sets out these limits.
Scope. These steps apply to current certificates, which embed the record they print. Earlier formats (a single document hash, or no embedded record) are no longer supported: create a new certificate for the photo in the app. A certificate created before its proof was confirmed may have no transaction link: the script needs only the Event ID.

