OpenStampsfor industry

Security and buying pack.

What a procurement or security reviewer usually asks for on a call, written down instead. Every number below is quoted from the code that enforces it, with the file named, so it can be checked against the deployment rather than against us. Where the answer is no, it says no.

OpenStamps for Industry · Protosonic OÜ, Estonia · this page describes the deployment at openstamps.com on 16 September 2026. When something here changes, this page changes.

  1. What this pack is
  2. Architecture
  3. Data flow
  4. Sub-processors
  5. Sign-in and access control
  6. Retention, export and deletion
  7. Service limits
  8. Vulnerability disclosure
  9. Business continuity
  10. Contract
  11. Questionnaire answers
  12. What is deliberately not offered

1What this pack is.

A description of how the service is built and run, addressed to the person who has to sign off a purchase. It is not a certificate, an audit or a promise; it is a statement of what exists, in the same grammar the product uses on evidence: a thing is either here or it is not, and this page does not dress a gap as a pass.

It repeats nothing that the privacy notice, the terms or the enterprise page already carry as the document of record. Where those documents are the contract, this page points at them. Where it names a number, the number comes from a constant in the code and the file is given, so a reviewer can ask for the file.

One sentence about the product, because everything below follows from it: a stamp fixes the bytes of a file and a time bracket around them. It does not establish that what the file declares is true. Nothing on this site should be read as a claim about a supplier's facts, and nothing in this pack is a claim that we have checked them.

2Architecture.

Static pages and serverless functions. The site is static HTML, CSS and JavaScript served by Netlify's CDN. The parts a static page cannot do — the timestamp relay, the account, the journal, the vault, the hand-off mailbox, publishing — are Netlify Functions in netlify/functions/*.mjs. There is no application server of ours, no database server and no queue.

One store, in the EU. Every function that keeps anything asks one module, netlify/functions/_stores.mjs, for its store by name. That module knows two classes: evidence (journal, vault, anchors, published, handoff, workspace, copresence) and ledger (osfp-accounts, quota). Since 16 September 2026 both classes are held in one Cloudflare R2 bucket created with European Union jurisdiction, reached through Cloudflare's EU endpoint (docs/EU-RESIDENCY.md). /api/residency prints, for the running deployment, the class of place each is held in; it never prints a bucket name or an endpoint. Netlify runs the functions and holds no copy of the stored material.

Checking never touches us. A stamp is checked in the reader's browser, against the published format, from the stamp's own bytes, a public randomness round and the timestamp tokens it carries. That path opens no connection: the build gate scripts/check-no-remote-origins.mjs refuses a host reached during stamping on any page that only verifies, and refuses any remote subresource on every page. Turn the network off and check a stamp; that test is the whole of the claim. The free reader for a folder somebody hands you is /read.html: no account, offline after the first load, and nothing dropped there leaves the device.

Making a stamp does touch the network. The browser fetches the current drand beacon round (it sends nothing) and posts the file's SHA-256 digest with a fresh nonce through the same-origin relay /tsa/* to the timestamp authorities. The relay forwards bytes it cannot forge and keeps no digest in the clear; what it keeps is in the stamping row of §3.

Third-party scripts: none. No analytics, no tag manager, no font host, no chat widget. Fonts are self-hosted and their digests are pinned in fonts/MANIFEST.json; the same gate fails the build if a font file changes or a page loads a remote subresource. Google and Microsoft sign-in are a top-level redirect to the provider and back, never a script on a page.

3Data flow.

Three questions, answered surface by surface: what leaves the device, what we store, and what is never stored anywhere on our side.

Data flow by surface: what leaves the device, what is stored, what is never stored.
SurfaceLeaves the deviceStored on our sideNever stored
Stamping a file (try.html, the desk, the plot page)The file's SHA-256 digest and a nonce, to the relay and on to the authorities. The drand round is fetched, not sent.A hashed per-network daily counter in quota. For a signed-in or keyed stamp, a pairing row keyed by a keyed hash over the account, the digest and a time bucket, holding a time and a remaining count, so the three legs of one stamp are charged once (tsa-relay.mjs, claimPair); and on the account record the last 64 draw tokens (meter.mjs, drawsTracked). The digest itself, never in the clear.The file. Its name. Where the person was.
Checking a stampNothing.Nothing.Everything: no request is made.
The journal (signed-in accounts)The sealed envelope — the .cts file — and what the page knew: the digest, an optional label of up to 120 characters, the page's route, the authority names (journal.mjs).That entry, under the account's e-mail hash, until deleted or the account closes.The file itself. A file name unless the person typed one as the label.
The vaultThe file, encrypted on the device with AES-GCM under a key derived by PBKDF2 (600,000 iterations, SHA-256) from a passphrase the customer holds. The salt and a check value that tells a right passphrase from a wrong one.The ciphertext, its digest, the name and size given, the storage date, the five-year retention date, the legal-hold flag, the salt and the check value (vault.mjs).The key. The passphrase. Any plaintext. A forgotten passphrase is a lost vault; nothing here can recover it.
The desk (dds.html)Digests at seal time, as above. And, while the operator is signed in, the whole desk bundle after every save, seal, import or deletion: the desk syncs itself to the account (lib/sync.js; PUT /api/workspace in auth.mjs). Signed out, nothing leaves the browser.The bundle, under the account: the operator record; every product with its supplier and buyer names, addresses and e-mail addresses, its species and plot geometry; assessments, mitigations, statements and seals with their envelopes. Never files. Up to 2 MB, plus the five previous versions before each overwrite. Stored as sent — it is not encrypted on the device before it leaves — and read by no code of ours. It stays until the operator deletes it from the account page or the account closes.The files themselves. Anything the desk holds while signed out.
The farmer's plot page (field.html)A digest at seal time; and, when the farmer presses Send, the sealed plot record and its stamp to the buyer's mailbox.The deposit, as posted and in the clear, in the handoff store until the request expires or the buyer closes it.A farmer's name, e-mail or phone number: the page asks for none. A record the farmer never sends.
The request link sent to a phone (/api/message)The supplier's phone number or chat identity, the buyer's name and the link, to the messaging provider.A keyed hash of the chat identity, bound to the request; the row carries the request's expiry and is deleted when it is next touched after that, not when the request closes — there is no sweep, so an untouched row stays, inert. Delivery receipts by message id. Never the number (message.mjs).The phone number. The chat identity. Any message body beyond the one the function sent.
The accountAn e-mail address at sign-in; or the address the identity provider asserts.The account record: the SHA-256 of the lower-cased address as its key, the address, the plan, the period's counts, the balance, the SHA-256 hashes of API keys with their labels and dates. Beside it, the desk bundle and its kept versions, as the desk row says.A password: there is none. A card number, an IBAN, Revolut credentials: entered on Stripe's pages, never ours.
The meterNothing beyond the relay call.Counts on the account record: included, drawn, top-up remaining, one-sided, and when the period rolls.Any record, fingerprint, plot, species, supplier or customer of yours.
Published journeys and the coverage mapThe pack the operator chose to publish (≤ 512 KB); optionally up to eight plot positions rounded to 0.01°.The pack under a random token; the coverage row (industry, country, day, rounded points). Public by design.The account that published it: the public route never reads the owner index.

Farmer identity is not a field anywhere in the system, with one derived exception: when an agent or officer captures for somebody else and types a name or member number into “For whom”, the record carries a salted commitment to that text (field.html, lib/commit.js); the name and the salt stay on the phone. What a farmer's record carries is a plot, a species, a country, a quantity, an optional photo and a per-phone code; that code links records made on the same phone, and the buyer who asked for the record is its controller (privacy notice §2).

4Sub-processors.

Every party that holds stored material or carries a message, with what they do and where they are. These are the same rows as the enterprise page and are kept in step with it; when the list changes, it changes on both pages and this page is the notice.

Sub-processors: party, role, country.
PartyRoleCountry
Netlify, Inc.Hosting, CDN and the serverless functions that read and write the stores; holds no copy of the stored material since 16 September 2026United States, under EU Standard Contractual Clauses
Twilio, Inc., when configuredDelivers the buyer's request link to the phone at the plot by SMS or WhatsApp (an approved template), and answers a farmer who texts first with the field page's link. Receives the phone number, the buyer's name and the link, and carries the record files the person sends back over WhatsApp. Only where the deployment sets a providerUnited States
Telegram FZ-LLC, LY Corporation (LINE), Rakuten Viber, Tencent (WeChat), VNG Corporation (Zalo) — each only when configuredA bot or an official account as a door for the person at the plot: the request link handed to them on a tap or a typed code, and the record files they send back carried to the buyer's mailbox. Each receives the chat and the files; the site keeps a keyed hash of the chat identity, never the identity. Only where the deployment sets the channelUnited Arab Emirates (Telegram); Japan (LINE); Luxembourg/Japan (Viber); China (WeChat); Vietnam (Zalo)
Cloudflare, Inc.Object storage with EU jurisdiction (R2) — the journal, the vault, the anchors, published journeys, desk bundles, the hand-off and co-presence mailboxes, the account record and the abuse counters. Bytes only; the functions that interpret them run on NetlifyEuropean Union (bucket restricted to EU locations at creation); Cloudflare, Inc. under EU Standard Contractual Clauses for support access
Stripe Payments Europe, Ltd.Payments, invoicing and VAT determination; holds the customer record. Stripe, Inc. is its own sub-processorIreland (Stripe, Inc.: United States)
Resend, when configuredSends the one-time sign-in e-mail, and only that. It is named here because it is the only provider the account code can use — one implementation, chosen by an environment variable — and it becomes a sub-processor only on a deployment that has configured it, which is why the privacy notice holds the name until one does. Where it is not configured the account function answers 503 and no e-mail is sentUnited States
FreeTSARFC 3161 timestamp authority for the closing edge. Receives a digest, never a file. A volunteer service, not a qualified trust service providerGermany
DigiCert, Inc.RFC 3161 timestamp authority for the closing edge. Receives a digest, never a file. A courtesy service for code-signing customers, not a qualified trust service providerUnited States
Sectigo Limited, when configuredRFC 3161 timestamp authority asked only when FreeTSA or DigiCert gives no token for a stamp, at most once every 15 seconds for the whole deployment, and only where the deployment switches it on. Receives a digest, never a file. A courtesy service Sectigo describes as intended for its code-signing customers, not a qualified trust service providerUnited Kingdom
Microsec zrt.RFC 3161 qualified timestamp authority for the paid third leg. Receives a digest, never a file. A qualified trust service provider on the Hungarian trusted list (nmhh.hu, sequence 99)Hungary

drand is not on this list, and that is the point. The drand beacon is a public randomness source operated by the League of Entropy. It receives nothing from us and holds nothing of yours: the lower edge of a bracket is a round number that already existed before your bytes did.

The qualified authority. Contracted since 24 September 2026: Microsec zrt., a qualified trust service provider on the Hungarian trusted list (Hungary), supplied under the annual agreement signed 24 September 2026 (5,000 qualified tokens a year); the paid third leg is the row above, and the enterprise page states the terms under which it is supplied. It receives a digest, never a file, exactly as the two free authorities do.

Two parties the privacy notice names that this table does not. Hetzner Online GmbH (Germany) hosts the self-operated CRM that receives an enquiry sent from the enterprise page (crm.mjs). The OpenStreetMap Foundation receives the words typed into a plot map's place search through /api/geocode, with this server's address, and its tile server sees the visitor's IP address only when the visitor switches map tiles on (privacy notice §7). Neither holds any record, file or account of yours.

5Sign-in and access control.

Three ways in, no password (netlify/functions/auth.mjs). A one-time sign-in link sent to the e-mail address, which also carries an 8-character code for the installed app. Continue with Google, for Google Workspace and personal Google accounts. Continue with Microsoft, for Microsoft Entra ID work and school accounts and personal Microsoft accounts. Google and Microsoft use OpenID Connect with the authorization-code flow and PKCE, the exchange server-side; only an address the provider marks as its own becomes an account, and it is the same account the link would have opened (the account key is the SHA-256 of the lower-cased address).

Multi-factor. The link and the code are single-factor possession of the mailbox. With Google or Microsoft, your provider's own MFA and conditional-access rules apply, because your provider is what signs the person in. There is no second factor of ours.

Tokens and sessions. A sign-in token is 32 random bytes, stored under its own SHA-256, used once, dead in 15 minutes; the link renders a page naming the address and consumes nothing until its button is pressed, so a mail scanner cannot burn it or sign anybody in. A session is a signed statement, not a lookup: HMAC-SHA-256 over the account hash and an expiry, 30 days, in an HttpOnly, Secure, SameSite=Lax cookie. Rotating AUTH_SECRET signs everybody out. Every route a browser session can change state through checks the request's origin (sameOriginOk in _quota.mjs; a bearer token or the native app's own header stands in for it, since neither can be lured out of a browser). The volume API, POST /api/stamp, is keyed by x-osfp-key and has no origin check by design; provider webhooks are checked by signature.

Teams (team.mjs, docs/TEAMS.md). An owner on a plan with more than one seat starts a team and invites colleagues by work address. Roles: owner (pays; makes and removes admins; disbands; cannot close the account while the team stands), admin (invites and removes members, withdraws invitations, renames), member (stamps against the owner's balance under the owner's allowances). Each person keeps their own sign-in, API keys, journal rows and vault files. Seats are the plan's: Trial 1, Maker 1, Studio 5, Works 15, Mill 40, Plant 100, Enterprise unlimited in price and bounded at 250 in code (TEAM_LIMITS.seatsCap), plus 5 per seats add-on. An invitation is an address, a role and 14 days, and can be accepted only by whoever signs in as that address. Seats are enforced on invitation, not by removing anybody on a downgrade. No SCIM, no SAML, no ownership transfer.

API keys (meter.mjs). Any account issues keys on the account page and revokes them there: 32 random bytes, shown once; what is stored is the SHA-256 of the key, the label, and when it was made and last used. At most 20 live keys per account; a revoked key keeps its row as the audit trail so the same string can never be minted twice, and the row goes when the account is closed. A key is presented as the x-osfp-key header and stamps against the account's balance.

Our own access. Operations routes that read or move stored material (/api/residency probe and migration) answer only to the one account named as OWNER_EMAIL in the deployment's environment. Store credentials live in Netlify environment variables, never in code, and a plain-http store endpoint is refused by _stores.mjs. The people who operate the deployment can read the ciphertext in the vault and cannot decrypt it; they can read a hand-off deposit and a desk bundle with its kept versions, both stored in the clear, and undertake not to.

6Retention, export and deletion.

The vault. Each file carries a retention date five years from the day it was stored (vault.mjs, retentionYears: 5, the EUDR record-keeping period). Nothing deletes a file when that date arrives: a person does, after any legal hold is lifted, because a vault that quietly emptied itself would be the opposite of one. Every account has a vault: the trial's is 25 MB, a paid plan's is the plan's (Maker 250 MB up to Plant 200 GB, plus add-ons — the table in §7; meter.mjs PLANS, vault.mjs capacityFor). When a plan lapses the capacity falls back to 25 MB, an upload over it is refused (402), and the files already stored stay listed and readable until you delete them or close the account. A legal hold is set per file from the account page; while it is set, delete answers 423 and the act of lifting it is recorded on the file.

The journal. An entry stays until you delete it or close the account. Every night at 02:00 UTC every entry is folded into a Merkle root that is itself stamped; a deleted entry's leaf remains in the roots already made, as a hash, which is what makes the anchor honest.

What runs unattended. Three scheduled functions read stored material without anybody pressing anything: the nightly anchor above (anchor.mjs, 02:00 UTC); managed renewal (renew.mjs, 03:00 UTC), which, on a paid plan that has not switched it off, appends a fresh timestamp token to the renewal chain of every journal entry whose checkability lapses within a year; and the monthly statement (statement.mjs, 06:00 UTC on the first), which mails a plain-text count of the previous month's stamps to accounts that opted in and reads nothing for accounts that did not.

The hand-off mailbox. A request expires 14 days after creation, 1 to 30 at the buyer's choosing, applied when the token is next touched; the buyer's desk can close it at once. There is no sweep job: an expired request nobody touches stays in the store, unread, until it is touched.

Export. From the account page: Export everything writes the journal as JSON with every envelope, up to 500 entries in one export (journal.mjs, exportMax; GET /api/journal/export); the vault, once unlocked on your device, exports every file decrypted with its .cts as a zip; the desk's bundle downloads from the desk or from the account's synced copy, any of its five kept versions included. A published journey is public JSON at its own link. Every export is in the published format and needs nothing of ours to be read.

Closing the account — exactly what netlify/functions/close.mjs does, in its order, because the order is the safety:

  • A team owner with a standing team is refused (409): disband first. A member or admin is taken off the roster.
  • If there is a subscription, Stripe is asked to end it at the close of the paid period before anything of ours is touched; a refusal aborts with nothing deleted. The Stripe customer and its invoices are kept, because tax law obliges both sides to keep them.
  • Then, deleted: every journal entry; every vault file and the vault's key record, a legal hold notwithstanding, since the hold is the account's own instrument and the account is ending; the workspace bundle and every kept version; every API key's index row; the account record itself.
  • Written: one marker under the address's hash holding the closing date and nothing else, so the same address signing in again starts fresh without a second trial.
  • The session cookie is cleared; every other browser's cookie dies on its next read.

What closing does not delete, said plainly: a journey you published stays public until you withdraw it (DELETE /api/publish/{token}), so withdraw first — withdrawal needs a signed-in session and the owner index that closing deletes, so after closing it takes signing in again with the same address; hand-off requests you made expire on their own schedule; the leaves of your journal entries stay inside past nightly anchors as hashes; abuse counters expire with the UTC day; Stripe keeps what Stripe must. Closes are capped at 5 per network per day.

The full retention table, surface by surface, is privacy notice §13.

7Service limits.

The caps the deployment enforces, quoted from the frozen constants that enforce them. Per-network caps are hashed per-IP daily counters that expire with the UTC day; they are abuse ceilings, not a price, and a stamp the relay refuses seals one-sided rather than not at all. What a plan buys is on the pricing page.

Service limits by surface, with the constant and file each comes from.
SurfaceLimitWhere
Stamping
Relay calls36 per network per day — 12 stamps at three legs, 18 at two; 2,000 free-leg calls per signed-in account per day, and 2,000 per request link, counted apart from the buyer's own. Where the Sectigo fallback is switched on, a fallback post counts as one more call on the same counter_quota.mjs LIMITS
Relay body8,192 bytes (a digest and a nonce); a file hashed in the browser up to 8 MB_quota.mjs LIMITS
Volume API (POST /api/stamp)5,000 posts per key per day, an abuse ceiling above any plan's month; the two free authorities are asked for at most 100 stamps per account per day (all its keys together) and 5,000 for the whole deployment, by default — past either, a funded stamp still reaches the qualified authority where one is configured; body up to 4 KB; 8 seconds waited on an authoritystamp.mjs STAMP_LIMITS
Account
Sign-in10 sign-in mails per network per day; link and code live 15 minutes, used once; 30 code tries per network per day; provider round-trip 10 minutes; session 30 daysauth.mjs AUTH_LIMITS
API keys20 live keys per account; 10 new keys per network per day; label up to 80 charactersmeter.mjs METER_LIMITS, auth.mjs
Desk sync (workspace)One bundle up to 2 MB, plus the five previous versions; 100 writes per network per dayauth.mjs AUTH_LIMITS
JournalEntry up to 96 KB; label up to 120 characters; 5,000 posts and 20,000 reads per account per day; list pages up to 100; export up to 500 entriesjournal.mjs JOURNAL_LIMITS
VaultPer file 4 MB of ciphertext plus 4,096 bytes; 2,000 uploads and 5,000 reads per account per day; name up to 160 characters; capacity by plan — Trial 25 MB, Maker 250 MB, Studio 2 GB, Works 10 GB, Mill 50 GB, Plant 200 GB, Enterprise 100 GB — plus any prepaid vault term (100 GB or 1 TB for a year, bought once; every file stored during a live term is held five years from the day it is stored); a lapsed plan falls back to 25 MB plus its live termsvault.mjs VAULT_LIMITS, meter.mjs PLANS and VAULT_TERMS
Teams30 invitations and 100 writes per account per day; an invitation lives 14 days; seats by plan (§5), never more than 250team.mjs TEAM_LIMITS
Closing5 per network per day; body up to 1 KBclose.mjs CLOSE_LIMITS
Billing20 checkouts and 20 customer-portal openings per network per daybilling.mjs BILLING_LIMITS
Filing (a filing deployment)40 submits and 120 reads per network per day; body up to 256 KBdds-file.mjs DDS_LIMITS
Enquiry from the enterprise page10 per network per daycrm.mjs CRM_LIMITS
Supplier hand-off
Requests30 created and 30 closed per network per day; expiry 14 days by default, 1 to 30 at the buyer's choosinghandoff.mjs HANDOFF_LIMITS
Deposits256 KB per deposit; 20 deposits per request; 40 posts, 120 reads and a separate 120 request-summary reads per network per dayhandoff.mjs HANDOFF_LIMITS
Request messages50 sends per account and 100 per network per day; 500 inbound and 2,000 delivery receipts per network per day; 100 receipts kept per request; a month's allowance by plan — Trial 5, Stand 5, Maker 30, Studio 150, Works 600, Mill 1,500, Plant 3,500, Enterprise 2,250message.mjs MESSAGE_LIMITS, meter.mjs PLANS
Public pages
Published journeysPack up to 512 KB and 64 stations; 20 publishes per network per day; public reads cached five minutes at the edge and not metered. How many pages an account may keep standing is the plan's allowance, not an abuse limit — trial 1, Maker 10, Studio 50, Works 250, Mill 1,000, Plant 2,500, Enterprise 1,500, each per publisher; withdrawing a page frees its placepublish.mjs PUBLISH_LIMITS · meter.mjs PLANS[*].stories
Coverage mapUp to 8 rounded points per journey; 5,000 rows listedcoverage.mjs COVERAGE_LIMITS
Phone Sync rendezvousA session lives 10 minutes; 240 posts per network per day; body up to 8,192 bytescopresence.mjs COPRESENCE_LIMITS, _quota.mjs

A limit changed in code is a limit changed here; the constants are frozen objects, and the tests under netlify/functions/__tests__/ read the same objects the functions do.

8Vulnerability disclosure.

Findings go to security@openstamps.com, which is also the contact in /.well-known/security.txt. It is read by people. No response time is promised.

There is no bounty programme. None is planned; this page will say so when that changes. What exists instead is that the part worth attacking is public before you sign: the envelope format, the furniture profile and the conformance vectors are published under their stated licences, and the reader runs on your device, served from this site with its source readable there, so a stamp can be attacked on your own bench without asking us. Neither the reader nor our server source is published under an open licence.

No independent penetration test has been commissioned. What has been done is internal: the sign-in link was split into a read-only page and a same-origin button after a review found the old single route could sign a visitor into somebody else's account (auth.mjs records the finding and the fix); the build gates refuse remote subresources, undeclared hosts, tick glyphs and verification language in state positions; and every function's guards run in tests. That is a description of our own work, not a substitute for somebody else's.

9Business continuity.

Durability of the store. Stored material rests in Cloudflare R2. Cloudflare's own documentation describes R2 as designed for eleven nines of annual durability; that is the provider's design statement about its service, read at its durability reference, not a measurement of ours, and we do not add to it. We take no backup of our own of the bucket.

No independent restore test has been performed yet. When one is, its date and method will be printed here.

What does not depend on us. A stamp is checked from its own bytes on the reader's device; every record the customer exported is readable in the published format without any service of ours. The evidence a customer holds on its own storage is the record; what we store is a copy the customer chose to keep. A customer who keeps its only copy of a file in the vault is relying on the provider's durability above and on its own passphrase.

Availability. No uptime figure is promised for any surface; the enterprise page explains why and what the meter gives back when an authority does not answer. The status page reports on a five-minute measurement whether each leg answered.

Shutdown. The founder's published intention — 90 days' notice, release of the relay and the meter under an open licence, mirrors of the specification and reader — is on the enterprise page and is not in the terms. Read it as an intention. The one continuity term that is a term is terms §6: every stamp already made stays checkable by anyone, permanently, after we stop.

10Contract.

The contract is three pages, the same for every self-serve plan from the free trial up: the terms, the cancellation and refund policy and the privacy notice. Governing law is Estonian. The counterparty is Protosonic OÜ, registry code 16344167, VAT EE103014252, trading as OpenStamps.

Data-processing agreement. Protosonic is processor for the hand-off mailbox and for filing deployments, where the buyer or operator is controller, and controller for the website, accounts and payments. A data-processing agreement for the processor surfaces is available on request from pilot@openstamps.com; no template is published on this site.

Not negotiated on the self-serve plans: no redlines, no master agreement of your drafting, no custom retention, no custom service levels. A scoped deployment is bought differently and is described on the enterprise page.

11Questionnaire answers.

The rows a security questionnaire usually carries, answered once and in public. Copy them into your form; we will not send a different set privately.

Security questionnaire, answered.
AskedAnswer
SOC 2 reportNo. None held, none commissioned, none scheduled.
ISO/IEC 27001No. No certification body has assessed us against 27001 or anything else.
Cyber insuranceNone.
Uptime SLA or service creditsNone, on any plan (see the enterprise page).
Penetration testNone commissioned. The format and the reader are public to attack; findings to security@openstamps.com.
Multi-factor authenticationThe sign-in is a one-time link or code to the e-mail address, or Google or Microsoft sign-in, under which your provider's own MFA applies. No second factor of ours.
Data locationEU: every store, evidence and ledger, in a Cloudflare R2 bucket with EU jurisdiction since 16 September 2026. Functions run on Netlify (United States) and hold no copy. Payments at Stripe (Ireland).
Encryption in transitHTTPS on every route; a plain-http store endpoint is refused by the code.
Encryption at restVault files: encrypted on the customer's device before they leave it; we hold no key. Everything else: under the storage provider's own at-rest encryption, as Cloudflare documents it; nothing of ours is layered on top.
BackupsNone of our own. The store's durability is as its provider states (§9). No independent restore test has been performed.
Incident notificationWe notify affected accounts by e-mail, at the address on the account. No contractual time is promised.
Sub-processor changesThis page and the enterprise page are updated; the page is the notice. No subscription mechanism exists.
Single sign-onOpenID Connect with Google Workspace and Microsoft Entra ID, on every plan at no extra price. No SAML.
User provisioning (SCIM)No. Members are invited by address, one at a time.
Role-based accessOwner, admin, member within a team (§5). No finer roles.
Audit logThe journal lists every stamp an account made, and a team's journal lists every member's stamps with who made them. Revoked API keys keep their rows. There is no separate log of sign-ins or of reads.
Data deletion on exitBy the customer, from the account page, at any time; account close deletes what §6 lists, in that order.
Data portabilityJournal export (JSON with envelopes), vault export (files with their stamps), desk bundle; all in the published format.
Source-code escrowNo. The format, profile and conformance vectors are published; the reader runs on your device. The server source is not published.
Named account managerNone. pilot@openstamps.com and security@openstamps.com are read by people; neither carries a response time.
Customer referencesNone given. Check a stamp instead.
Analytics or trackers on the pagesNone. No cookie banner because there is nothing to consent to. Two cookies exist, both functional: the session cookie, set only after sign-in, and osfp_oauth, an HttpOnly state cookie set for the 10 minutes of a Google or Microsoft round-trip and nothing else.

12What is deliberately not offered.

  • Custom integrations. The API is the API; the format is published so that you or a third party can build against it. Nobody here builds a connector for you.
  • Any guarantee that a supplier's facts are true. A stamp fixes bytes and a time bracket. It does not establish that a harvest was legal, that a plot is where a record says, or that a photo shows what it claims. Nothing on this site checks a supplier's declaration, and we will not say otherwise in a questionnaire.
  • Continuous consulting. No retained advisory, no named contact, no standing calls. What we would have said in a meeting is published; what is not published is not privately better.
  • Private answers. This page, the enterprise page and the privacy notice are the questionnaire. A differently worded private version would be a second set of claims, and one set is enough to be held to.