Everything instantscan.io ships to a browser — the launcher, your publishable key, the session token, the hosted scanner itself — is readable by anyone who opens dev tools. Treating any of it as a secret would be a design error. The boundary is the server, and the blast radius of each browser-side value is deliberately small.
This page is the reference version of that posture: what is enforced, where, and what it does not cover.
Credentials, ranked by what they can do
| Credential | Lives | Can do | Cannot do |
|---|---|---|---|
Publishable key pk_ | Your HTML | Create a session, from an allowlisted Origin only | Read a result, list sessions, change settings, see credits |
Client token ?t= | A URL, for minutes | Fetch that session’s config; upload once | Anything after the upload, or for any other session |
Secret key sk_ | Your server | Create and read sessions and results | Console operations (keys, domains, billing) |
| Console session cookie | Browser, httpOnly | Everything under /v1/dashboard/* | Be read by JavaScript |
The ordering is the point. The credential most exposed can do least.
Publishable and secret key separation
A publishable key is a session-opening capability and nothing more. It is safe in your HTML because of what it cannot do, not because it is hard to find.
A secret key is trusted. We store only its SHA-256 hash, so the full value crosses the wire exactly
once — in the POST /v1/dashboard/keys response — and cannot be recovered afterwards by you or by us.
Revocation is immediate: a revoked key’s next use returns 401 invalid_api_key. Revoked keys stay in
the list with revoked_at set, so your history stays intact.
Origin allowlist
Session creation with a publishable key requires the request’s Origin to be on your allowlist, or the
API answers 403 origin_not_allowed. A key lifted from your page and used elsewhere is inert.
The same list bounds the session’s allowed_origins — which origins may embed the scanner and receive
its scanner:complete message. A session cannot be told to hand its document to an origin you never
approved.
Exact origins only. https://acme.example and https://www.acme.example are two entries, and there is
no wildcard subdomain support. Ownership is not DNS-verified: the allowlist bounds where a key works,
it does not prove you own the domain.
Client tokens
The hosted scanner authenticates with a compact HMAC-signed token carrying:
{
"sid": "ss_9Qw1p2Kt",
"tid": "tn_acme",
"iat": 1735689600000,
"exp": 1735690500000,
"jti": "jti_…"
}
Three properties matter:
- Short-lived. 15 minutes by default. Minutes, not hours.
- Bound to its session. The
jtimust match the one stored on the session. - Single-use for upload. After a successful upload the session is marked used; a second attempt is
409 token_already_used.
So a token captured from a URL cannot be replayed to inject a second document, and stops working shortly regardless.
Signed file URLs
Result URLs carry ?exp=<epoch ms>&sig=<hmac>. The signature is the authorisation — there is no
cookie or bearer token involved, and nothing to guess. Change the id or the expiry and the request is
403 invalid_signature.
Two consequences worth designing around:
- Anyone holding the URL can fetch the file until it expires. Do not forward it to a browser unless that is what you intend, and do not put it in a log you keep.
- Download and store the bytes in your webhook handler. Saving the URL and fetching it tomorrow does not work.
Files are dropped after 24 hours by default.
Webhook signing
X-Scanner-Signature: t=1735689600,v1=8f2b1d0c…
v1 is HMAC-SHA256(secret, "<t>.<raw body>"), hex. Verify against raw bytes with a constant-time
comparison, and reject timestamps outside a few minutes — a valid signature over a replayed body is
still a replay. Full implementation in Webhooks.
The secret is rotatable, and rotation applies to future deliveries only, so you can deploy the new secret on either side of the rotation without dropping events.
Where the document goes
- On the user’s device: camera access, live edge detection, contour finding, the perspective transform, the document clean-up, text recognition (OCR), PDF assembly. No frame is streamed anywhere, and the OCR model is served from our origin, not a third-party CDN.
- Over the wire, once: one HTTPS upload of the finished PDF, authenticated by the single-use client token and bound to its session.
- On our side, briefly: held so you can fetch it, dropped after 24 hours, reachable only through a signed expiring URL.
There is no server-side inference step: detection is geometric, and the one learned model — the OCR that makes the PDF searchable — runs in the user’s browser. Your documents are not training data.
Other server-side controls
- Rate limiting on session creation, per tenant.
- Upload caps —
413above 25 MB by default. - Server-side validation of customization, so a hostile
accentvalue cannot become a stylesheet injection. The colour grammar is an allowlist, not “whatever a browser accepts”. - Console passwords hashed with scrypt and a per-user salt; hashes never appear in a response.
- Login does not enumerate accounts — an unknown email and a wrong password give the same
401. - Cross-tenant reads are
404, never403.
Deliberately not relied upon
| Not relied upon | Why |
|---|---|
| Hiding the publishable key or session token in obfuscated JS | Anything in the browser can be read. Both values are made safe when extracted instead. |
| Client-side signature or integrity checks | A modified client deletes the check. Decisions that matter are made server-side. |
| Anti-debugging, DRM theatrics | Breaks real browsers, annoys real users, stops nobody trying. |
| CORS as access control | Our API reflects the caller’s origin and carries no credentials. CORS is a browser convention, not a permission system; enforcement is key validation and the origin allowlist. |
| Obfuscating the SDK to protect IP | A launcher has no IP to protect. Obfuscating it would only make your debugging harder. |
IP protection, where it applies at all, applies to the hosted engine on our origin — not to anything on your page.
Known limits
Stated plainly, because a security page listing only strengths is a marketing page:
- Storage. Results live in the API process with a TTL, not in object storage with server-side encryption and lifecycle rules. Signed URLs and the 24-hour TTL apply either way.
- Rate limiting is per API instance, not global.
- Console auth is email and password only. No SSO, no MFA, no roles, no audit log.
- Upload validation is size and content type. No magic-byte check, no antivirus.
- Domain ownership is not verified.
- Certifications. None, and we will not imply otherwise.
If your security review needs answers this page does not give, ask us — we would rather answer a hard questionnaire honestly than have you find a gap after you have shipped.