What gets stored
The Postgres database holds the metadata required to drive the lifecycle and audit it later:- Sessions — status, requested share fields,
redirect_url, expiry, hashedcancel_token, timestamps. - Attempts — status, failure code if any, risk score, hashed mobile write token, hashed device ID, app version, current phase, timestamps.
- Events — type, trigger ID, trigger type, timestamps. No PII.
- Webhook deliveries — endpoint ID, status, attempt count, payload (encrypted with the endpoint’s registered key when applicable), HTTP status of the last attempt, timestamps.
Retention windows
Verification metadata is retained only while it has an operational or compliance purpose:- Completed, expired, and cancelled verification sessions are purged after 30 days.
- Verification attempts are minimised after 7 days. Kayle keeps lifecycle status and timestamps, but clears failure details, mobile handoff hashes, device/app metadata, phase state, risk score, connection claims, and the mobile attestation key reference.
- Verification events and webhook delivery attempt history are purged after 30 days once no retained webhook payload remains.
- Verification audit log rows are purged after 365 days.
- Mobile App Attest key rows are deleted after 90 days of no use once no retained attempt still references them.
undelivered_payload_retention_hours setting. See Webhook deliveries.
What gets discarded
Every byte of document and biometric data is processed in memory inside the API’s verification pipeline and is gone when the WebSocket closes:- DG1 (MRZ data)
- DG2 (on-chip portrait)
- DG14 (chip authentication parameters)
- DG15 (active authentication public key)
- SOD (security object)
- Selfie frames
- Active authentication challenges and signatures
- Chip authentication transcripts
- Biometric verifier request and response bodies
- CRL responses
Logs
Runtime logs use the sharedevlog wrapper in @kayle-id/config/logging. Request logs strip query strings and only keep the method, path, status, request ID, and explicit safe event fields. Error logs use caller-provided safe messages and error names instead of serialising Error.message, Error.stack, SQL parameters, request bodies, MRZ data, chip data, selfie frames, liveness videos, or biometric verifier responses.
Production log sinks must keep Cloudflare Workers Logs or Logpush retention bounded to 30 days or less. Kayle’s application logs are diagnostic only; verification state is audited in Postgres with the retention windows above.
What reaches your webhook
Webhook payloads are always end-to-end encrypted: Kayle wraps every event body as a JWE addressed to a public key you’ve registered with the endpoint, so Kayle, your CDN, and any intermediary cannot read the cleartext. Endpoints without an active encryption key fail delivery before the body is sent. See Encrypted payloads for the registration flow. For a confirmed Kayle check, the cleartext you receive after decryption contains only the claims the user consented to share:verification.session.expired, verification.session.cancelled) carry no claim data either. The cancelled payload includes an outcome, a reason, and the per-check retry counters so you can see why a session ended without verifying the user; see the verification.session.cancelled reference.
When a user redeems their cancel token (privacy withdrawal) after the session has already terminalized, Kayle scrubs any not-yet-delivered succeeded or failed payload and emits a replacement verification.session.cancelled event with reason set to either privacy_cancelled_after_terminal_success or privacy_cancelled_after_terminal_failure. This means privacy withdrawal can suppress the claims you would have received — but you will always learn that the session reached a terminal state.
Stable identifiers without raw values
If you need to recognise a returning user without storing their document number or birth date, requestkayle_document_id (one identifier per document, per organization) or kayle_human_id (one identifier per human, per organization). Both are derived from the cryptographically attested document and stable across re-verifications.
Where to look in the code
If you self-host or audit, the relevant entry points are:- Database schema:
packages/database/src/schema/{core,auth,webhooks}.ts - Webhook payload builders:
apps/api/src/v1/webhooks/deliveries/payloads.ts - Share manifest:
apps/api/src/v1/verify/share-manifest.ts - Verification pipeline:
apps/api/src/v1/verify/