Last updated in the repository as docs/security/SUBPROCESSORS.md.
Every company that processes SignSealer customer data, what each one is given, and where it is.
Status: DOCUMENT ONLY, maintained by hand. Nothing checks that this list matches what the code actually calls. Recorded as an unevidenced control (CC9.1).
Last reviewed: 2026-09-09 (the review added).
| Subprocessor | What for | What they see | Where |
|---|---|---|---|
| Supabase | The Postgres database — everything | All of it: agreement content, signer names and addresses, evidence, credentials as stored (hashed or encrypted) | us-west-2 |
| Resend | Sending signing links, reminders and completion copies; receiving bounces at mail.signsealer.com | The recipient's address, the subject, and the message body — which contains the document's title and the signing link, but not the document's text | US |
| Stripe | Paying online: Checkout, subscriptions, the Customer Portal, invoices; the webhook back | The business's name, its contact email (or the member's, at Checkout), our tenant id and plan code as metadata, and the card — which Stripe holds and we never see. Never a signer, a document or anything from the evidence store | US |
| OpenRouter, routing to Anthropic | Drafting help in the portal: a first draft of a template from a description, when a customer asks for one. Tidying: a template's wording re-set with headings and plainer sentences, when a customer presses the button. AI review before sending: things worth checking in a template's text, when a business has switched it on under Settings and asks for one | For a draft: the business's name, the kind of document, the trade it picked in setup, where it operates, and what it typed into "what is this for". For a tidy or a review: the template's text as written — placeholders still in braces — with the business's name, the kind and where it operates. Never a signer, never a filled-in blank, never a sent document, never anything from the evidence store | US |
| Telnyx | Sending text messages: signing links and reminders a signer asked for by text, and the six-digit sign-in codes to a verified mobile number once the keys are on the api service; receiving STOP and HELP | The phone number, and the message — a signing link, a reminder, or a code. Never a document's text | US |
| Google (Sign in with Google) | Another way to start a portal session, when configured and when the person chooses it | Google learns that this person signed in to SignSealer; we receive their Google subject, email, whether Google verified it, and their name. Nothing of ours goes to Google | US |
| Apple (Sign in with Apple) | The same, for Apple | Apple learns the same; we receive the Apple subject, the email (a relay address if they chose one), and the name once. Nothing of ours goes to Apple | US |
That is the whole list today.
Supabase holds the database. They are the processor for everything the product stores. Credentials are stored hashed or encrypted before they reach disk, and the encryption key is not in the database — so a Supabase employee with database access has ciphertext for webhook secrets and partner tokens, and plaintext for agreement content.
Resend is handed one message at a time: to, subject, text. The text of a signing-link email contains the signer's name, the sender's name, the document's *title*, and the link. It does not contain the document's body. A completion copy contains the title and the verification code.
The link is a bearer credential, which means it is in Resend's logs for their retention period. That is inherent to emailing a link and is the reason the token expires and is rotated on every reminder.
OpenRouter is handed one request per click of "Write a draft": a system prompt that is the same for everybody and a user message built from the six fields named in the table. src/portal/draft.ts is the whole of what builds it, and tests/draft.ts asserts the prompt holds no tenant id, user id, address or token. The draft that comes back is shown in the new-template form and is not stored by us until the customer saves it as a template; what is stored on every run is the model, the token counts and the cost (public.record_draft, 00190). OpenRouter's and Anthropic's own retention of prompts is governed by their terms; we send them nothing we would mind them keeping, which is the design rather than a hope. Switched off entirely when OPENROUTER_API_KEY is absent.
OpenRouter, for the review (src/portal/review.ts, 00202), is handed one request per click of "Review with AI first" on the send screen, and only for a business that has switched ai.review on under Settings — the engine refuses to record a review while it is off, so a screen cannot run one by mistake. The request is the template's text *as written*, placeholders still in braces ({{guest_name}}, not a name), with the business's name, the kind of document and where it operates. The blanks the person has just filled in on the send screen are not sent; neither is any signer. What comes back is read strictly: a finding whose quote is not in the text is dropped, a finding that is a verdict is dropped, and the rest are kept on document_reviews so that when the document is then sent the evidence package can say what was flagged and that it went out anyway. The model, tokens and cost are metered; the prompt and the raw answer are not kept. tests/review.ts asserts the prompt carries no id and no address.
OpenRouter, for tidying (src/portal/polish.ts), is handed one request per press of "Tidy up the wording" on a template screen. It carries the same things the review does and nothing more: the template's text as written, placeholders still in braces, with the business's name, the kind of document and where it operates. What comes back is checked before it is shown — every placeholder that went in must come back and none may be invented, and text that has collapsed to a summary is refused — and then it is shown in the form the person was already in, beside a button that restores their own words. It is never written to a template; a template changes only when somebody presses Save. The model, tokens and cost are metered through the same public.record_draft the drafter uses, so the two share one daily cap; the prompt and the raw answer are not kept. tests/templates.ts asserts the prompt's rules and the checks. Switched off entirely when OPENROUTER_API_KEY is absent.
Uploaded files are not a subprocessor at all. A .docx or a text file dropped on the templates screen is read in this process — src/portal/import.ts reads the zip and the paragraphs itself, with no library and no network — and what survives is the words, in the form, unsaved. The file is never written to disk, never stored, and never sent anywhere.
The wording of the four messages a signer reads is the business's own (00236) — the signing request by email and by text, the reminder, and the signed copy. That changes what is *said*, never who it goes to or what leaves the building: the same address, the same provider, the same signing link. One Two sentences are not theirs to remove — "Reply STOP to opt out." and "Reply HELP for help." on a text are appended by the sender rather than carried in the template — and a signing request without its link is refused rather than sent.
Stripe is handed, per Checkout, a price id, a success and cancel URL, the business's name in the subscription description, the tenant id and plan code as metadata on the session and the subscription, and either the Stripe customer id already on file or the contact email. What comes back through /hooks/stripe is verified over the raw bytes with the endpoint's secret and reduced to ids, a status and a price before anything of ours reads it (src/billing/stripe.ts, app.apply_stripe_event). Switched off entirely when STRIPE_SECRET_KEY is absent.
The marketing site and the documentation are static files. They carry no customer data, ship no script, and load nothing cross-origin. Vercel is therefore not a subprocessor of customer data — but it does serve signsealer.com, so a compromise there is a phishing vector against signers even though no data flows through it.
tests/site.ts and tests/docs.ts.OPERATIONAL — no procedure. There is no notice period agreed with customers for adding a subprocessor, because there is no DPA template yet. Adding one is a prerequisite for any enterprise customer.

Free for the first 25 agreements a month. No card to start.