Data, Privacy, Export and Erase
What Vendwick stores and for how long, what customer export and erase cover, and where secrets live. Everything is in EmDash plugin storage (D1 on Cloudflare), plus release files, invoice PDFs and import uploads in a private R2 bucket. Update your privacy policy to match.
Collections
| Collection | Holds |
|---|---|
products, plans | Your catalog. |
checkouts | Every checkout attempt, paid or not: kind, status, provider and payment id, amount, currency, the frozen quote and terms, the buyer’s email, first and last name, and optionally company, phone, country, region, tax ID and site URL, plus checkout answers. Since 0.2.0 also the shipping address (for carts with a physical item), the provider’s refundable payment ids, the refund ledger and the amounts admin refunds hold while they wait for the provider. |
paymentEvents | One row per provider webhook event (provider, event id, type, payment id, matched checkout, status). Used to ignore duplicate deliveries. No customer details. |
orders, orderItems | Paid orders: number, status, amounts, currency, coupon code, tax rate, tax country, the buyer’s tax ID, email and name, invoice and credit note numbers, line items. Since 0.2.0 also per-item discount, tax and refunded amounts, the shipping address and shipping status, the dispute status, and what refunds still in flight have reserved per item. |
customers | Email, first and last name, display name, billing details, site URL, and (0.2.0) the last shipping address used. |
customerSessions | Account and page access sessions. The id is a SHA-256 of the cookie token (the token itself isn’t stored), with a hashed IP. |
licenses | License keys (stored in plain text, because the EDD-compatible API matches them byte for byte), status, expiry, seats, the owner’s email. |
activations | Sites a license is active on: normalized site URL, its hash, an instance id, local/staging flag, dates. |
packages, downloads | Releases, and one row per download with the license, the release, how it was fetched and a hashed IP. |
coupons, redemptions | Coupons and who used them. |
bumps (0.2.0) | Order bump offers. No customer details. |
refunds, refundJobs (0.2.0) | One row per refund: order, amount, lines, status, source (admin, provider or dispute), your internal reason, the admin who made it, provider ids and failure reasons. The jobs apply each refund once. |
disputes (0.2.0) | One row per chargeback: provider ids, order, amount, the provider’s reason code, status, the licenses it suspended. |
shippingZones, stock, shipments (0.2.0) | Physical Goods and Shipping. Zones and rates; stock per plan with the holds of open checkouts (checkout ids, no customer details); shipments with items, carrier, tracking number and link, and dates. |
webhookEndpoints, webhookDeliveries (0.2.0) | Outgoing Webhooks. Endpoint URLs, event filters and the sealed signing secret; every delivery’s body as sent (customer email, name and country, order and license data, full license keys only for endpoints that opted in) and the first 2,000 characters of each response. |
subscriptions, providerLinks | Automatic renewals, the provider’s subscription and customer ids, the quote renewals charge from. |
invoices, invoiceItems, invoiceEvents, invoiceRequests, invoiceSchedules, invoiceOccurrences | Client invoices with the client’s name, email and billing details, saved line items, the activity log, agent request ids, repeating schedules and their periods. |
accessRules | Page access rules. |
reportDaily | Daily totals per currency and product. No customer details. |
fulfillment, reversals | Outbox jobs for paid and refunded checkouts. |
uniques, counters | Uniqueness claims (license keys, emails, payment ids, numbers) and number counters. |
ephemeral | Short-lived markers with their own expiry: rate-limit buckets (including stock holds per IP), throttles, once-a-week recovery markers, and one marker per import upload in progress (its storage prefix). The 5-minute cron tick deletes expired ones. |
events | The audit log: event type, object, actor and metadata. The actor is an admin user id or, for things a customer did in the portal, the customer’s email. A customer’s sign-in and access-link events also name them as the object. Webhook endpoint events keep the URL without credentials and with each query value replaced by ***. |
importRuns, importLinks, importConflicts | Store importer (off by default). One row per import run: source, the old store’s URL, file name and size, counts, the first conflicts and unreadable line numbers (conflict messages name source record ids and, for customers, the email and both names), the admin who ran it. Conflicts beyond the first 100 go to importConflicts (up to 500 per run). One link per imported record: source, type, the old store’s id, the Vendwick id, and notes (FluentCart activation hashes, the source license’s last update). |
mailQueue | Every email Vendwick sent or tried to send (shipping emails include the shipping address): recipient, subject, full text and HTML, status, attempts. Purchase emails include license keys until they’re delivered; after that the stored copy has them masked. Delivered emails are deleted after mail_retention_days. |
IP addresses and user agents
- Raw IP addresses aren’t stored. Sessions, downloads and rate-limit buckets keep an HMAC-SHA256 of the IP under the store’s signing secret, cut to 32 hex characters.
- The visitor’s IP is sent to Cloudflare Turnstile when you turn on the checkout bot check.
- User agents aren’t stored.
- The visitor’s country from Cloudflare’s request metadata is used as the default checkout country, and saved with the checkout when the buyer doesn’t change it.
Retention
- Expired portal and page access sessions are deleted by a daily job.
- Ephemeral markers are deleted when they expire.
- Email log:
mail_retention_days(Store settings, default 30, 1 to 3650) is how long delivered emails stay. The daily job deletes delivered (sent) emails created longer ago than that. Emails that are still queued, failed or waiting for an email provider are kept, because they may still go out. - License keys in the email log are masked (
first8••••••••••••last8) in the stored text and HTML as soon as the email is delivered. The buyer gets the real keys; an email that hasn’t been delivered yet keeps them so a retry sends the real keys. Resending a delivered email from the email log sends the masked copy; use Resend receipt on the order for a copy with the keys. - Invoice PDFs (0.2.0) are cached in private storage (
invoices/..., never public) the first time someone downloads one. A cached PDF is replaced when its document changes and deleted by customer erase. - Store import files: an uploaded export (it holds license keys and customer details) is kept in private storage only while its run needs it. It is deleted when the import finishes or the run is canceled, and by the daily job 7 days after upload whatever its state, also while the Store Importer module is off. An upload that stopped before its run was created (a dropped connection, a crash) leaves parts behind: the daily job deletes them a day later. The run’s history, its conflicts and the import links stay.
- Webhook deliveries (0.2.0) are deleted by the daily job 30 days after they were created. That job runs only while the Outgoing Webhooks module is on, so turning the module off keeps the log.
- Stock holds lapse with their checkout and are removed on the next write to that plan’s stock.
- Nothing else is deleted automatically. Checkouts, payment events, orders, refunds, disputes, shipments, downloads and the audit log are kept until you erase a customer or remove the data yourself. The "Product pages" content type and its entries are EmDash content, managed like any other.
Export
The Customers admin page exports one customer (commerce/customers/export) as one JSON file: the customer record with their saved shipping address, orders (with their shipping address) and items, the orders’ refunds, disputes and shipments, licenses (keys masked), activations, subscriptions, checkouts (with the shipping address entered at checkout), client invoices (number, status, amounts, lines, client name, email and billing details) and the email log (template, subject, status and dates, not bodies).
The export doesn’t include webhook deliveries: they are copies of order and license data already in the export, sent to your own endpoints.
The route returns one page at a time: at most about 200 rows per call and a cursor for the next call, null once everything has been returned. The admin screen follows the cursor and merges the pages, so there’s no row cap. If you call the route yourself, pass cursor back until it’s null. The first page is logged in the audit log as customer_exported.
Erase
Erase (commerce/customers/erase) anonymizes; it never deletes financial records. You confirm by typing the customer’s email.
- Orders keep their number, status, amounts, currency, coupon, tax details, invoice and credit note numbers and dates, so accounting, reports and numbering stay intact. The name is cleared and the email becomes a placeholder like
erased-<hash>@erased.invalid. The tax ID is kept only on orders that have an invoice number. The shipping address is cleared except its country (tax and shipping reports use it). - Licenses keep working; their email becomes the placeholder.
- Checkouts get the placeholder email, empty names, and no company, phone, tax ID, site URL or answers. Their shipping address keeps only the country. The quote frozen on each checkout gets the placeholder email and no tax ID too. The frozen terms hold only catalog rows.
- Subscriptions get the placeholder email.
- Client invoices are anonymized like orders: numbers, status, amounts, dates and lines stay. The client name and billing address (company, street, city, region, postcode, phone) are cleared, and the email becomes the placeholder. The billing country stays, and so does the VAT number on an issued (numbered) invoice. A repeating schedule built on one of those invoices is paused. Free text you wrote yourself (title, message, notes) isn’t changed; check it if it names the client.
- Audit log events found through the
actorindex (the customer’s email as actor) and theobjectType/objectIdindex (the customer as object, by id or email) get the placeholder in place of the email, and personal values in their metadata (the email in any case, the customer’s names, and fields such asemail,customer_nameorphone) are scrubbed. - Invoice PDFs generated for the customer’s orders, credit notes and client invoices are deleted from private storage. A PDF downloaded later is rendered again from the anonymized record.
- Email log rows to that address are deleted, and so is the store’s own "new order" notice for each of the customer’s orders (it names the customer).
- Webhook deliveries that mention the customer have the email, names and other personal values scrubbed from the stored body, the response and the error log. Every delivery is read (there is no index by customer), so a large log can take more than one call.
- Store import conflicts that name the customer (customer conflicts quote the email and both names) get their message replaced with "details erased with the customer", in the run row and in the overflow collection.
- The customer record, its email claim and every session are deleted last.
Erase works in batches for about 15 seconds per call. If it reports done: false, run it again; it continues where it stopped.
Erase doesn’t touch activation site URLs or download rows (hashed IPs only).
The customer’s own saved shipping address goes with the customer record. Shipments keep their carrier, tracking number and items (no address). Erased customers are skipped by reminders and recovery emails, and the mailer never sends to an @erased.invalid address.
Secrets
All of these are EmDash secret settings, encrypted at rest with EMDASH_ENCRYPTION_KEY and never returned by admin routes:
- the store signing secret (generated automatically; signs account links, download links, return URLs and page access links; changing it invalidates links already sent);
- Stripe secret keys and webhook signing secrets, PayPal client secrets, Razorpay key secrets and webhook secrets, Polar access tokens and webhook secrets (per test and live mode);
- the mock gateway webhook secret;
- the Turnstile secret key;
- the Cloudflare API token for the PDF engine (0.2.0);
- your Vendwick license key.
Outgoing webhook signing secrets (0.2.0) are stored in the webhookEndpoints collection, sealed with AES-GCM under a key derived from the store signing secret. They are shown once and never returned again. Changing the store signing secret makes them unreadable, so rotate each endpoint’s secret afterwards.
Publishable or public values (Stripe publishable keys, PayPal client and webhook ids, Razorpay key ids, the Turnstile site key) are plain settings.
Logs
Vendwick writes warnings and errors to EmDash’s plugin log: failed email deliveries (template id and error), failed gateway or subscription calls (ids and the error message), and counts from cron jobs. Card details never reach Vendwick: buyers pay on the provider’s hosted pages.