This is a factual description of the software, not legal advice and not a compliance
certification. Which of these facts matter under your regulator, and what you have to do about
them, is a decision for you and your counsel.
Who holds what
Veriqa is delivered as code you run. In the self-hosted model that is the whole answer to “where does the data go”:- You are the controller. The database, the key material and the logs are on your infrastructure, under your connection strings. Nothing is provisioned for you.
- Veriqa (the vendor) is neither controller nor processor of your users’ data. No user data reaches the Veriqa vendor: the product makes no calls home, ships no telemetry and has no vendor endpoint of any kind. The only outbound addresses in the code are the messenger APIs you configure and the SMTP server you point it at (see What leaves your perimeter).
- The messenger providers are your counterparties, not the Veriqa vendor’s. Telegram, MAX and the WhatsApp Cloud API see the messages you send through your own bot credentials. Your relationship with each of them is direct.
Personal data inventory
Everything the shipped product touches, and nothing beyond it.
Not in the list: message bodies from the channel (a channel adapter reads what it
needs to identify the sender and confirm, and keeps nothing else) and any
long-lived profile of the user beyond the collected claims above. The product ships no user
directory — see
Channel identities.
Collection defaults
These are on out of the box. Nothing here needs an action from you to start; each needs one to stop.
Two qualifications that matter more than the flags:
Geolocation is on by default but inert by default. The lookup needs the satellite package
Veriqa.Core.AuthServer.MaxMind and a local .mmdb database, and the base packages carry
neither. Without them the provider reports itself unavailable and the geo fields stay empty — so a
stock installation collects no location, even though the flag says true. Adding the database is
what starts the collection.
The IP is collected but never shown and never logged. It goes into the snapshot inside the
transaction and stops there: the confirmation message names only the application, browser, OS and
region, and the diagnostic log for an untrusted forwarding configuration records a result code
rather than the address.
Turning all of it off is one section:
appsettings.json
Enabled: false no snapshot is built, and the confirmation message simply carries no context
line. You can also keep the context and narrow it — drop region from DisplayFields, or set
CollectIpAddress: false (which also removes geolocation, since the lookup has no input). Details
and the per-application narrowing are in Initiator context.
Where it is stored
Channel identities: nothing is stored by default
The port that would persist a channel identity — channel user id, phone, email, display name, as an upsert with neither TTL nor deletion — isIChannelIdentityRepository, and Veriqa ships no
implementation of it and registers none. Sign-in works without one: the resolved identity travels
with the transaction and is gone with it. Keeping that data indefinitely is an act you perform
deliberately, by registering an implementation of your own; see
Storage.
Retention
In short: the transaction store empties itself within about fifteen minutes of a
sign-in, and the audit trail is the only Veriqa store that keeps anything for days. Everything with
an indefinite lifetime is something you added — a channel identity repository, the collected claims
store, or tokens with a lifetime you chose.
The audit trail is off until you ask for it, twice
Two independent switches, and both are needed:- The satellite is opt-in — a host that never calls
AddVeriqaAuditTrailhas no trail at all. Veriqa:Logging:Modeships asSystem, and records are written only inAudit.DisabledandSystemare the same thing to the receiver: write nothing.
RetentionDays. ConfigureRetention only
tunes how often the sweep runs and how large its batches are — it does not turn retention on, and
skipping the call does not leave records unbounded. A retention of zero or less is refused at
startup rather than read as “keep nothing”.
The one case where nothing sweeps: a custom sink you registered with UseSink<TSink>(). Records
then live in your store, the built-in sweep is not registered, and the host says so at startup.
Bounding that store is yours.
appsettings.json
What an audit record actually contains
A closed set of attributes, fixed in the schema — one column per attribute, no free-form payload: the timestamp, the actor, the action code, the target (the transaction id), the outcome and its reason code, plus the transaction type, channel type, correlation id, tenant,client_id, UI locale
and time zone, the channel’s own safe details and its declared inbound verification level.
The actor is masked. For an action the user performed in a channel it is
{channel}:{masked id} — at most the last two characters of the channel user id stay readable. The
unmasked value never leaves the transaction store.
No IP, no User-Agent, no geolocation, no display name, no email reaches an audit record. The
initiator context lives in the transaction and dies with it.
The single exception you can switch on is the parameters of the confirmed operation — the declared
action kind and its slot values. Those are your application’s subject data and may be anything, so
they are off by default and copied into the record only when you ask
(Hardening).
Encryption
There is no application-level encryption of stored personal data beyond that one protected field,
and no key management of its own. If your threat model requires personal data encrypted at rest,
that requirement is met at the storage layer you operate — encrypted volumes, TDE, an encrypted
connection to the database — not by a setting in Veriqa.
Secrets —
client_secret, bot tokens, SMTP credentials — belong in environment variables or a
secret store, never in config files in the repository. Passwords are never written to a log: where
one has to be compared, the comparison is over a SHA-256 hash.
Keys
Three key materials, three different homes. Token signing and encryption certificates. InDevelopment the product uses OpenIddict’s
development certificates. Outside Development it loads X.509 certificates you supply, by path or
base64 (Veriqa:OpenIddict:Server:SigningCertificate* / EncryptionCertificate*), and refuses to
start without them.
The DataProtection key ring. Priority is Redis
(Veriqa:DataProtection:RedisConnectionString), then a directory
(Veriqa:DataProtection:KeysDirectory); with neither set, the keys live in memory and are lost on
restart. That is fine for development and wrong everywhere else — after a restart the protected
expectations of live transactions can no longer be read. Data Protection rotates its own keys, and
values written under an earlier key stay readable while that key is still in the ring, so a ring on
durable storage makes rotation a non-event.
Channel and application secrets — bot tokens, webhook secret tokens, client_secret, SMTP
credentials. Rotating them is a channel-side and client-side operation; nothing in Veriqa caches
them past a configuration reload.
What leaves your perimeter
Only what you configured a channel for. Per confirmation, the outbound message carries:- the name of the application that started the sign-in;
- the browser, OS and region of the initiator, where those are known and displayed;
- a link or buttons to confirm, and how long the request is valid.
User-Agent, the user’s own profile data, or anything
your application declared about the transaction.
Where it goes depends on the channel: api.telegram.org for Telegram, platform-api.max.ru for
MAX, graph.facebook.com for the WhatsApp Cloud API, and your own SMTP server for Email. In the
other direction, each channel returns the sender’s profile — the fields in the inventory table
above. The messenger sees the message content and the recipient because that is what delivering a
message means; the Email channel sees whatever your SMTP relay sees.
The external event bus. If you wire the RabbitMQ publisher, transaction events leave the process
carrying the safe attribution only — the masked channel identity among it. The parameters of a
confirmed operation are explicitly excluded from serialization and never reach the bus, whatever the
audit configuration says.
Logs
Application logs are not an audit trail and are not designed to be evidence. What matters for this page is what they must never contain, and what the code does about it: identifiers are written as a 16-character non-reversible fingerprint (the leading bytes of a SHA-256) rather than as themselves, so an operator can still stitch the steps of one sign-in together without the log holding the identity. Tokens, secrets and the initiator’s IP are not logged at all. That is the shipped behaviour of Veriqa’s own log statements. Your host, your reverse proxy and your platform’s request logging are outside it — an access log in front of the auth server records client IPs by default, and bounding that is part of the same deployment step that configuresForwardedHeaders:KnownProxies.
What is left for you to decide
Nothing on this list has a right answer the product can pick for you.- Whether to keep the initiator context at all, and whether to add a GeoIP database.
- Whether to enable the audit trail, at what retention, and whether records should carry the parameters of confirmed operations.
- Whether to persist channel identities — and, if so, how they get deleted.
- Whether to turn on the collected claims store, and with what retention.
- How long expired and revoked token records stay in the OpenIddict database before pruning.
- Where the DataProtection key ring lives.
- Encryption at rest, backups and their retention, at the storage layer.
- How a deletion request from a user is executed across your stores. Veriqa deletes its transaction
rows on its own schedule and its audit rows on retention; the collected claims store you reach by
sub(endpoint or host call); anything you added is yours to reach.
Next steps
Production hardening
The checklist to run before going live.
Storage
Which store holds what, and how to wire each one.
Known limitations
What the product does not do.