Privacy and field inventory

Every piece of personal data that leaves a Magento store through the Conviro integration, what Conviro keeps, for how long, and what is never sent or stored. Plan §22 and §20 item 17. Read against the code on feat/magento-10-10 (d3472ce2), 2026-09-24; updated 2026-09-25 for revenue attribution (514d99b0, 101ed33d, f9e583f6, c3374a58) and release gate R3 (ceaaac4d API, ee07b4fc widget, 8791cdc4 module). For Conviro's subprocessors, legal basis and data processing terms, see the Conviro DPA — this page does not list them.

Classes (plan §22): public catalog, merchant confidential, customer PII, order PII, credentials / secrets. The attribution ids are pseudonymous identifiers (random, not derived from the person).

Summary

Leaves MagentoWhenPersonal data in itConviro keeps
customer.createdstorefront registration, Create Leads on Registration on (default on)e-mail, first and last name, customer id, groupa lead (name, e-mail, Magento customer id) + commerce_customers row (e-mail hashed)
customer.updatedan existing customer's e-mail or group changed, while customer data is sharede-mail, customer id, group — no namecommerce_customers row only (e-mail hashed)
customer.deleteda customer is deleted, while any customer data of the store view is sharedcustomer id, e-mailnothing — it deletes / anonymises what Conviro holds of that customer (below)
customer.syncedopt-in customer importe-mail, customer id, group, website, account datecommerce_customers row only (e-mail hashed)
order.placedorder created, Send Order Events on (default on)e-mail, order number and totals, item count, guest flag; attribution ids (below)commerce_orders row: order number, state/status, total, currency, item count, guest flag, dates, e-mail hash, attribution record
order.processing/completed/canceled/status_changedstate/status changee-mail, order number, totalscommerce_orders update (e-mail hashed)
order.shippednew shipmente-mail, order number, shipment number, carrier, tracking numbers, shipped SKUs and quantitiescommerce_orders last-event time; tracking and items not stored
order.refunded / partially_refundednew credit memoe-mail, order number, credit memo number, amountscommerce_orders update
order.syncedorder-history job (window 30/90/365 d)e-mail, order number, totals, item countcommerce_orders row (e-mail hashed)
cart.updatedcart saved, cart recovery on and confirmed (off by default)e-mail, quote id, up to 10 items (name, SKU, qty, price), total, cart linkmagento_abandoned_carts (raw e-mail, items, total, store view, locale, cart link) + a contacts record for the e-mail; a recovery e-mail is sent to the shopper
checkout.startedshopper reaches the checkout page, cart recovery on and confirmedquote id, item count, total, currency; e-mail only if already given; attribution idscommerce_checkouts row (e-mail hashed, attribution record); nothing is sent to the shopper
payment.failedorder placement failed, payment assist on and confirmed (off by default)e-mail, quote id, payment method code, total, error text (≤ 300 chars); attribution idscommerce_payment_failures row (e-mail hashed, error text masked, a reference to the shopper's conversation when matched); a staff-only event on that conversation; one offer of help in the chat widget
product.*, inventory.updatedcatalog / stock changesnone (public catalog and merchant stock figures)knowledge-base entry per SKU
sync.job.progresssync jobsnone (error texts have e-mails masked as [email])commerce_sync_jobs
Live REST reads by Convirowhen a shopper asks (after giving order number and e-mail for orders), flows, staffwhatever the answer needs: order status, items, totals, tracking; customer profile for a logged-in shopper; cartnot written to the order/customer tables; short-lived caches (below); facts used in an answer go into the grounding record
Storefront widgetpage view with the widgetfor a logged-in shopper: customer id, name, first name, e-mail (resolved in the browser)with the chat session (chat_sessions.metadata.userContext: first name, e-mail, customer id since 1.4.0)
Attribution cookiesthe shopper followed a Conviro product link / chatted, with consentrandom link ids / the chat's random visitor id — in the shopper's browsersee Revenue attribution
Signed identity assertion (1.5.0, unreleased)a signed-in shopper opens the chat, signed identity on at both endsMagento customer id, website / store view id and code — no name, no e-maila principal row with the customer id for 15 minutes of use (below)
identity.bindings_revoked (1.5.0, unreleased)sign-out, customer deleted, password change, another customer on the same sessionbinding ids (random) and a reason — no customer idnothing new: the matching principals are marked revoked
Verified order reads — My orders (1.5.0, unreleased)a verified shopper asks for their ordersorder number, date, status, total, currency, up to 20 item names with quantities, shipment status, carrier and tracking numbers of that customer's ordersnot stored (cards); a free-text answer sealed; counts without ids

Field inventory by event

Field lists with types are in the event reference; the personal fields:

EventFieldClassStored by Conviro as
customer.createdemailcustomer PIIraw in leads.email (lower-cased); HMAC hash in commerce_customers.email_hash
firstName, lastNamecustomer PIIleads.name
customerIdcustomer PII (identifier)leads.custom_fields.magentoCustomerId, commerce_customers.magento_customer_id
groupIdmerchant confidentialcommerce_customers.group_id
customer.updatedemailcustomer PIIhash only
customerId, groupIdcustomer PII (identifier) / merchant confidentialcommerce_customers
customer.deletedcustomerId, emailcustomer PIInot stored; used to find and remove the customer's rows; the audit log keeps the customer id and counts, never the e-mail
customer.syncedemailcustomer PIIhash only
customerId, groupId, websiteId, createdAtcustomer PII / merchant confidentialcommerce_customers
order.placedcustomerEmailcustomer PIIhash only (commerce_orders.customer_email_hash); the raw value is used in memory to cancel a pending cart-recovery e-mail and to match a conversation by e-mail (attribution)
orderId, entityId, status, state, grandTotal, currency, isGuest, itemCount, createdAtorder PIIcommerce_orders
attribution.visitorId, attribution.linkIdspseudonymous idsthe visitor id is not stored; matched conversation ids and link ids go into commerce_orders.attribution
order status eventscustomerEmailcustomer PIIhash only
order.shippedtracking[], shipmentId, items[]order PIInot stored
order.refunded / partially_refundedamounts, creditmemoIdorder PIItotals follow the order row; credit memo number not stored
order.syncedcustomerEmailcustomer PIIhash only
cart.updatedemailcustomer PIIraw, magento_abandoned_carts.email; contacts record
items[], grandTotal, currency, itemCount, cartId, cartUrlorder PIImagento_abandoned_carts
checkout.startedemail (when given)customer PIIhash only (commerce_checkouts.customer_email_hash)
quoteId, itemCount, grandTotal, currencyorder PIIcommerce_checkouts
attribution.*pseudonymous idsas for order.placed (commerce_checkouts.attribution)
payment.failedemailcustomer PIIhash only (commerce_payment_failures.customer_email_hash)
paymentMethod, grandTotal, currency, cartIdorder PIIcommerce_payment_failures
errororder PIIcommerce_payment_failures.error_text, with e-mail addresses → [email] and runs of six or more digits → [number]
attribution.visitorIdpseudonymous idnot stored; the matched conversation is referenced (session_id, cleared when the conversation is deleted)

order.placed no longer sends the billing first / last name and phone, the line items or the customer id (release gate R3): no Conviro consumer used them.

The envelope's store block (base URL, ids, code, locale, currency) and meta (versions) are merchant data, not personal data.

What is never sent

What Conviro never stores from these events

Raw e-mail in commerce_orders / commerce_customers / commerce_checkouts / commerce_payment_failures; names, phones, addresses, line items or tracking numbers in any commerce table; the chat visitor id in any commerce table; event payloads in logs (the ingest does not log them; labels of Conviro's metrics never carry a workspace id, store URL, SKU, order number or e-mail).

E-mail hashing

apps/api/src/modules/magento/commerce-email-hash.ts: HMAC-SHA256 of the trimmed, lower-cased address, domain-separated (conviro:commerce-email:v1), under the server-side key COMMERCE_EMAIL_HASH_KEY (≥ 16 characters) or, when that is not set, a key derived (HMAC with a label) from the application secret. With no secret at all it refuses to hash rather than store an unkeyed value. Each row stores a 16-hex email_hash_key_id, so a key rotation shows up as a different key id. Purpose: recognise the same shopper across orders and checkouts without holding a readable address. The hash is one-way; it cannot be turned back into the address, but anyone holding the key and a candidate address can test it — the key never leaves Conviro's servers. A customer.deleted can only match rows hashed under the current key.

Retention

In Magento

DataKeptRemoved by
Outbox payloads of delivered order / customer / cart / checkout / payment eventsnot kept: emptied the moment Conviro accepts the event (1.4.0)EventOutbox::markSent
Outbox payloads of other delivered events (product, inventory, sync progress — no personal data)until the next daily purge; the row itself 7 daysconviro_purge_outbox, daily 03:23
Outbox rows blocked (Conviro refused the credentials and nobody fixed them)30 days, then dropped with the payload emptied (1.4.0)conviro_purge_outbox
Outbox rows dead / dropped / repaired (with payload, except dropped ones)30 days after they were queuedsame
All outbox payloadsuntil DisconnectDisconnect empties every payload at once
A deleted customer's undelivered payloadsuntil the deletioncustomer_delete_after: dropped, payload emptied
Undelivered payloads when the store registers with a key of another Conviro workspaceuntil that registrationdropped, payload emptied (never sent to the new workspace; 1.4.0)
Inbound webhook replay keys (conviro_inbound_event: signature hash, event type)1 dayconviro_purge_inbound_events, hourly
conviro_webhook_log (event name, direction, HTTP status, error code — no payload, no personal data)30 daysconviro_purge_webhook_log, daily 03:29 (since 1.4.0)
Sync job rows (conviro_sync_job: counts, cursor, last error without customer data)finished jobs 90 days after they finished; the newest job of each type is kept; a queued or running job is never deletedconviro_purge_sync_jobs, daily 03:35 (since 1.4.0)

Measured on the local stack (retention.sh): a 40-day log row deleted and a 10-day one kept; two orders jobs finished 100+ days ago deleted, a recent one, the newest (200-day-old) repair job and a 120-day-old running job kept.

At Conviro

DataKept
Event idempotency key (mag:evt:<tenant>:<id> = done)7 days
Event receipts (magento_event_receipts: event id, type, state — no payload), side-effecting event types7 days (daily purge)
Order-lookup failure counters (Redis ordlk:*: per conversation, visitor / channel identity and keyed e-mail hash, never the address)1 hour
Last-event stats (mag:evtstats, type, time, lag, versions)30 days; per-day counters 8 days
SLO metric buckets (no personal data)26 h (5-minute) / 31 days (hourly)
Live-read caches in Redis — order lookups (keyed by the lower-cased e-mail) 5 min, customer profile 10 min, cart 2 min, product/price 5 min, stock 1 minas listed; all dropped on Disconnect
commerce_attribution_links (link id, conversation, SKU, click counts), commerce_checkouts90 days (daily purge)
commerce_payment_failures90 days (daily purge); an unshown offer expires after 24 hours
commerce_ordersrolling 24 months after the order was placed (daily purge; security review R3, 2026-09-25)
commerce_customers24 months after the customer was last seen (last event)
commerce_sync_jobs90 days after the job ended or last reported
magento_abandoned_carts (holds the shopper's e-mail)a finished row (sent, cancelled, converted, failed) 90 days after its last change; a scheduled one until it is sent or cancelled
Everything a disconnected store sent — commerce_orders, commerce_customers, commerce_checkouts, commerce_payment_failures, commerce_sync_jobs, and, once no Magento store is connected to the workspace, magento_abandoned_carts and commerce_attribution_links30 days after the disconnect, unless the store reconnects (audited magento.store_data.purged with counts); at once with Delete store data now (below)
leads, contacts, chat_message_grounding, conversationsno automatic purge in the integration's code; deleted with the workspace (tenant foreign keys cascade), by customer.deleted for the customer's rows, or on request — see below
ai_action_requests / ai_action_eventswith the workspace; when the conversation is deleted (visitor data deletion, the workspace's session-retention purge, inbox delete) the customer's content is scrubbed — details, reason, masked e-mail, e-mail hash, staff note and the store's messages — and the audit trail (what was done to which order, when, by whom) stays

All purges run daily in batches until done (a 4-minute budget per run, time indexes from migration 0228); a table left with rows past retention is logged and sent to Conviro's ops channel.

ai_action_requests.customer_ref holds the customer's e-mail masked (a***@example.com); details.customerEmailHash is the keyed HMAC of commerce-email-hash (it was a plain SHA-256 before the security review) and is never returned to a browser. The one-time code of a customer-confirmed action is stored only as a keyed hash, for 10 minutes of validity. The grounding record stores the facts shown to the AI (for order answers: order number, status, total, tracking values up to 120 characters) and is visible to your team only.

Deletion and export

Storefront widget

When a shopper is logged in, the widget loader reads the customer's id, name, first name and e-mail in the browser — Magento's customer section (mage-cache-storage) has the names; the id and e-mail come from the module's customer-data section conviro-customer ({id, email}, empty for guests; stored by Magento in the same mage-cache-storage entry, dropped at logout) — and passes them to Conviro's widget as user: {id, name, firstName, email}. A guest's page view reads nothing (1.4.0; 1.3.x called /rest/V1/customers/me on every guest page view). Conviro stores the first name, the e-mail and (since 1.4.0) the customer id with the chat session (chat_sessions.metadata.userContext). Until 1.4.0 the template sent the id as userId, which neither the widget nor Conviro read; both still accept userId from an older module. Nothing personal is rendered into the cacheable page (verified by fpc-privacy.sh, WidgetPrivacyTest and widget-identity.sh). The store view code, name, locale and language are passed too. The module's template does not consult a consent manager for this — see Headless / PWA for consent options.

Signed customer identity and My orders (1.5.0, unreleased)

1.5.0 (unreleased), read 2026-09-27 on feat/magento-1.5.0-customer-hub (backend, module), feat/magento-1.5.0-p34-module (admin card) and feat/magento-1.5.0-p34-ui (widget, dashboard). Off unless switched on in the store and at Conviro (Signed customer identity and My orders).

What leaves Magento

WhatWhenPersonal data in it
The signed assertion (60 s), via the shopper's browser to Conviroa signed-in shopper opens the chatthe Magento customer id; workspace, integration, website / store view id and code; a random binding id, nonce and jti; the widget-session hash (opaque to Magento). No name, e-mail, group, address, session id or token.
identity.bindings_revoked (signed event through the outbox)sign-out, customer deleted, password change / reset, another customer signs in on the same sessionbinding ids and one reason word (customer_logout, customer_deleted, password_changed, account_switched). No customer id, e-mail or session.
Verified order list / detail (GET /V1/conviro/identity/bindings/:bindingId/orders[/:orderId])a verified shopper asks for their ordersper order: order number, Magento order id, state, status and its storefront label, date, grand total and currency, item count and up to 3 (list) / 20 (detail) item names, SKUs and quantities, shipment status, and (detail only) carrier code, title and tracking number. Never sent: customer name, e-mail or group, billing or shipping address, telephone, payment method or details, IP, customer note, gift message, item prices, discounts / coupon codes, invoice or credit-memo numbers, the protect code or tracking-popup link, the customer id.
The store's public identity keyonce when switched on, and at each rotationnone
Reset identity request (POST /api/magento/identity-keys/reset, 1.5.0, owner decision 2026-09-27)an authorised admin confirms a resetthe installation id, a random request id and magento_admin:<admin user id> — never the admin's username, e-mail or name, and no customer data

What Magento keeps

DataKeptRemoved by
conviro_identity_binding (binding id, customer id, website and store view id, HMAC of the Magento session id — never the session id itself —, created / expires (16 min) / revoked time and reason)1 day after it was created; a deleted customer's rows at the deletion (Phase 5, branch feat/magento-1.5.0-p5-module)conviro_purge_identity_bindings, hourly at :41; customer_delete_after
Identity key pair, installation id, key state, key created / registered / rotated time, a pending Reset identity (request id, start time, admin user id) (core_config_data, conviro_aichat/identity/*, conviro_aichat/identity_key/created_at)until rotated or the module is removed; the pending mark until the reset is settledthe secret key is encrypted and never rendered
conviro_identity_reset_audit (1.5.0, owner decision 2026-09-27): per Reset identity attempt and outcome — admin user id and username (staff, not customers), UTC time, website / store view codes, request id, attempt, outcome, revoked counts, the first 8 characters of the old / new key id, a closed detail code. No key, token, API key, message body or customer data365 days after it was createdconviro_purge_identity_bindings, hourly at :41

What Conviro keeps

Table / storeHoldsPersonal dataKept
widget_identity_keysthe store's public keys, kid, installation id, store URL, state, timesnonewith the integration (revoked, not deleted, on Disconnect); deleted with the workspace
widget_identity_challengesSHA-256 of the nonce (never the nonce), chat id, holder-secret hash, expiry (120 s), when it was spent and the assertion's jtinone beyond the chat iddeleted by the daily retention run (04:30) once 1 hour past expiry (Phase 5 P5-04 b520da97 ; decided 2026-09-28, not deployed yet — until then rows stay); deleted with the conversation (cascade)
widget_identity_principalsthe Magento customer id, website / store view, binding id, key, chat id, SHA-256 of the identity token, created / expires (15 min) / last used / revoked time and reasonthe customer id (the store's own number); no name or e-mailpurged 7 days after expiry (audit window; P5-04 b520da97, decided 2026-09-28, not deployed yet); deleted on the Magento customer's customer.deleted (P5-05 368c9808, not deployed) and with the conversation (cascade)
chat_messages of a verified chatcommand answers: a summary without order data (e.g. "list: 2 shown"); a free-text order answer: a placeholder in content plus the text sealed (pt1., AES-256-GCM, bound to the customer's scope)order data only inside the sealed textwith the conversation, like every message
verified_handoff_contexts (0234)the one order a shopper handed over: number, status and label, dates, total, item count, shipment status, track count — no item names, tracking numbers, customer id, name or e-mailorder data of that order30 days (daily purge); deleted with the conversation (cascade)
widget_identity_daily_counts (0234)per workspace, chatbot and day: counts by closed labels (outcome, reason, latency bucket)none — no customer, session, principal or order id400 days (daily purge)
Redis widget_identity:<event>:<day>:<outcome>:<detail>ops countersnone, no workspace either8 days
Integration metadata.myOrdersHealth, metadata.myOrdersLastLoadedAtthe last "permission missing" / "store refused credentials" state and its time; when an order list last loadednoneuntil the next read changes it

Order lists and details are never cached and never written to the order tables; the AI reply cache and the debug prompt log skip every turn that carried verified order data. Metrics labels are checked against closed lists — a label that looks like an id, an e-mail or an order number is refused.

Deletion

Guest verification codes (designed, not built)

Planned for 1.5.0 (switch guest_verification_enabled in the module, flag magento_guest_otp at Conviro): a ledger guest_order_verifications with hashes only — keyed hashes of the order number and e-mail, an IP hash with a daily salt, the code as an HMAC — attempts, resends and times, kept 30 days. The code is never stored in a transcript (the widget sends it apart from the message text; other channels store [verification code]). Not built at Conviro on 2026-09-27: nothing of this is stored today.

Revenue attribution (cookies & ids)

Plan P6.4 (owner decision 4: session and link models, reported separately, "influenced", never "caused").

On the storefront (the chat widget, 101ed33d, ee07b4fc). Two first-party cookies on the store's own domain, path=/, SameSite=Lax, Secure on https, written only when the host page asked for attribution (the module passes it — Sync Settings › Revenue attribution, default on, website scope) and there is a positive consent signal:

CookieValueLifetimeWritten when
cvr_att_vidthe chat's random visitor id (v_<uuid>, issued by Conviro)30 daysthe shopper sent a message of their own (or a restored history holds one)
cvr_att_idsup to five 22-character random link ids, .-joined, newest first30 days (7 days before release gate R3) — the longest attribution window; the workspace's own window (1–30 days, default 7) is applied by Conviro when the order arrivesthe shopper landed with ?cvr_att=<id> from a Conviro product link

Consent (security review R3, 2026-09-25): the cookies need a positive signal — an explicit host decision; with Cookie Restriction Mode on, Magento's user_allowed_save_cookie holding {"<websiteId>":1} (set by the Luma and Hyvä notices); a recognised consent platform reporting consent (IAB TCF v2: purpose 1 plus a measurement purpose 7, 8 or 9, and the vendor's consent when the host names a vendor id; OneTrust: the performance group C0002; Cookiebot: statistics); or Google Consent Mode analytics_storage: granted. Every signal present can refuse, and so can the widget's own privacy policy. No signal, a platform still loading, or a consent platform the widget cannot read = nothing is written. Only the merchant setting Attribution cookies without a consent signal (conviro_aichat/sync/attribution_without_consent, website scope, default No, for regions where analytics cookies need no consent) lets the widget write them on a page that carries no consent signal and no consent platform at all. An explicit refusal removes both cookies on the next page view and suppresses even the click report below. Neither cookie holds an e-mail, a name or an IP.

URLs. Product cards Conviro shows (chat, flows, knowledge-base cards) of a Magento-connected workspace carry ?cvr_att=<22 random characters>. Landing with it, the widget reports the click to POST /v1/widget/attribution/click (public key + link id only; 30 per minute per IP; a reload within 10 minutes is not counted twice) — whether or not a cookie may be set, but never after an explicit refusal.

Orders, checkouts and failed payments (module, f9e583f6). Service/Attribution/AttributionCookies reads the two cookies only for a storefront request (frontend, REST, SOAP, GraphQL — never admin, cron or CLI), with Revenue attribution on and, under Cookie Restriction Mode, the shopper's consent cookie for that website, validates them and adds them as data.attribution {visitorId?, linkIds?} to order.placed, checkout.started and (release gate R3) payment.failed. Nothing else is read or stored in Magento.

At Conviro (514d99b0).

WhereWhatKept
commerce_attribution_linkslink id, workspace, conversation id, chatbot id, SKU, surface, click count, first / last click time — no URL, e-mail, IP or user agent90 days
commerce_orders.attribution / commerce_checkouts.attributionthe record per live order / checkout: matched conversation ids (up to 3; removed when a conversation is deleted), how it was matched (visitor, email — a lead or contact of the workspace — or email_unverified), link ids and SKUs, whether a recovery e-mail preceded the order, the windowwith the order row / 90 days for checkouts
commerce_payment_failures.session_idthe conversation a failed payment was matched to (by visitor id)90 days, SET NULL when the conversation is deleted

The visitor id itself is never stored in a commerce table; an order's e-mail is matched in memory only. Since the security review an e-mail match counts as influenced only through a verified identity — the e-mail of a lead or a contact of the workspace. An e-mail only the browser supplied (the host page's user.email, or one typed in the chat) is recorded as email_unverified and reported separately in the revenue card, never in the influenced totals. Historical orders (order.synced) are never attributed.

Also in 1.4.0 that affects privacy