Protected customer data in the Shopify integration
Shopify classifies customer name, address, email and phone as protected customer data, and sets requirements for apps that access it. This page separates two things that are usually blurred together: what Shopify requires, and what this integration currently does. Where a control cannot be confirmed from the code, it is marked as such rather than claimed.
Last reviewed against the implementation on
At a glance
- Fields requested
- Name, email, phone, and the Shopify customer id
- Addresses
- Never extracted — not from customers, not from orders
- Stored in the ERP
- Name and email on the party ledger. Not phone.
- Raw payloads
- Retained in webhook and sync logs — see below
- Access tokens
- Encrypted at rest (verified in code)
What this page covers
Connecting a Shopify store to an ERP means customer information moves between two systems. This page documents exactly which fields move, what happens to them, and what is deleted when someone asks for it.
It deliberately separates two things that marketing pages tend to merge:
- What Shopify requires of any app handling protected customer data.
- What this integration currently does, taken from the code.
Meeting a requirement and describing a requirement are not the same thing. Where something cannot be confirmed from the application code — anything about servers, backups, staff access or written policy — it is listed under Requires infrastructure verification rather than presented as done.
Not a legal document
This is engineering documentation describing observable behaviour. It is not a privacy policy, a data processing agreement, or legal advice, and it does not assert any certification or review outcome.
What Shopify requires
Shopify treats information relating to an identifiable customer as protected customer data. Fields it identifies specifically — name, address, email and phone — carry the stricter obligations, and apps requesting them are held to a higher tier of requirements than apps that only touch customer records without those fields.
The requirements relevant to an integration of this shape are, in plain terms:
| Shopify requires | In plain language |
|---|---|
| Data minimization | Process only the minimum personal data the app actually needs to work. |
| Transparency | Tell merchants what personal data you process and why. |
| Purpose limitation | Use it only for the purposes you stated. |
| Consent and opt-out | Respect customer consent and opt-out decisions where they apply. |
| Data protection agreements | Have privacy and data protection agreements with your merchants. |
| Retention periods | Do not keep personal data longer than it is needed for. |
| Encryption | Encrypt personal data in transit and at rest. |
| Access control | Limit which staff can reach protected customer data, and keep an access log. |
| Backups and environments | Encrypt backups, and keep test data separate from production data. |
| Incident response | Have a security incident response policy. |
The authoritative and current text is Shopify's own — Protected customer data requirements. Where this page and Shopify's documentation disagree, Shopify's is correct and this page needs updating.
What data the integration uses
This table is derived from the code that reads Shopify's response, not from a list of what the API could return. The integration requests customer and order access, but only ever extracts four customer values.
| Field | Used | Purpose |
|---|---|---|
| Customer name | Yes | Becomes the name on the party ledger the invoice is billed to. An invoice must name who it is billed to. |
| Yes | Identifies a returning customer so repeat orders reach one ledger instead of creating a new one each time. Stored on the ledger. | |
| Phone | Yes | Carried so it can be sent back to Shopify on a customer export. Not written to the ERP ledger. |
| Shopify customer ID | Yes | The stable link between the store customer and the Bizmitra ledger. Survives a rename on either side. |
| Billing address | No | Never extracted. |
| Shipping address | No | Never extracted. |
| Geolocation, postcode, city | No | Never extracted. |
| Order history and totals per customer | No | Not built. Orders are imported as documents, not aggregated into a customer profile. |
| Payment instrument details | No | Never received. Payment arrives as an amount and a status, not card data. |
Addresses are not extracted from orders either
A Shopify order payload carries billing and shipping addresses. The order importer passes the nested customer through the same four-field reader used for standalone customers, so no address reaches a Bizmitra record from either path. This is worth stating explicitly because "we only sync customers, not addresses" is often untrue once orders are involved.
Where the data goes
A customer record, or an order carrying a nested customer.
Reads four values only: name (first + last joined), email, phone, customer id. Everything else in the payload is ignored.
Skips the record entirely if it has neither a name nor an email.
Matched by stored id, then by email. Creates a Sundry Debtor ledger only if neither matches.
The ledger is the billing party on the sales order, invoice, receipt and any credit note.
A customer record with no name and no email is discarded rather than written as an empty ledger. Shopify checkout-only records can legitimately look like that.
What is stored, and where
| Location | Customer data held |
|---|---|
| Party ledger (ERP) | Name and email. Phone is not written here. The ledger is an accounting record referenced by posted invoices. |
| Entity mapping | Shopify customer id ↔ Bizmitra ledger id. No names, no contact details. |
| Sync job | The four normalised values, plus the raw provider payload where one was captured. |
| Webhook log | The full raw webhook body as received from Shopify. |
| Application logs | Identifiers only — shop domain and Shopify customer id. No names, emails or phone numbers are written to application logs. |
Raw payloads in logs
This deserves its own section because it is the one place where the integration holds more than the four fields it uses.
Incoming webhooks are logged with their full body as received, and sync jobs may store the raw provider payload alongside the normalised values. Those raw bodies contain fields the integration never reads — including the addresses listed as "not extracted" above.
Stated plainly
"Bizmitra does not use addresses" is accurate. "Addresses never touch
Bizmitra storage" would not be accurate, because a raw
orders/create body containing a shipping address is retained in
the webhook log. The distinction matters, so the page makes it rather than
rounding it off.
These logs exist for diagnosis and replay — they are what makes a failed sync recoverable rather than lost. There is no automated retention or pruning for them in the application today; see known gaps.
Missing or unavailable fields
Shopify does not guarantee every field on every record: access may not be approved, a field may be blank, or a checkout record may carry almost nothing. The integration is written for that, and this behaviour is verified in code rather than assumed:
| Situation | What happens |
|---|---|
| A field is absent or null | Read as null. Every field is read defensively; no absent key raises an error. |
| First or last name missing | The remaining part is used. If both are missing the name is null, not the string "null" or a stray space. |
| Name and email both missing | The record is skipped. Nothing is written. |
| Email missing but name present | A ledger is created under the name. Later matching falls back to the stored id, since there is no email to match on. |
| Name missing on an order | The ledger is named from the order reference rather than left blank. |
| Phone missing or malformed | Dropped. On export it is only sent if it passes a strict format check, so one bad number cannot fail the whole customer. |
| Customer id missing | No mapping is recorded; matching falls back to email. |
In short, a reduced or redacted customer payload degrades the record rather than breaking the sync, and the one case with nothing usable in it is dropped instead of stored.
Data sent back to Shopify
Customers can also be sent from Bizmitra to Shopify. What is sent is name (split back into first and last), email, phone where the ledger holds one in a readable form, and the Shopify customer id when the record is already linked.
Nothing else from the ERP is included — no ledger balances, no invoice history, no accounting data. And no customer data is sent anywhere other than Shopify: the integration has no third-party analytics, enrichment or advertising destination for it.
Verified controls
These are confirmed in the application code and can be checked by reading it.
| Control | What is verified |
|---|---|
| Authorisation | OAuth. You approve the app from your own Shopify admin; Bizmitra never receives a store password, and access can be revoked from Shopify. |
| Access scopes | The app requests product, customer, order and inventory access only. It does not request the extended historical-order access that reaches beyond Shopify's standard 60-day window. |
| Encryption in transit | All calls to the Shopify Admin API are over HTTPS. |
| Token storage | Stored access and refresh tokens are encrypted at rest by the application, and are excluded from model serialisation so they cannot leak into an API response or log line. |
| Webhook authenticity | Every incoming webhook is verified against its Shopify signature before processing. Unsigned or tampered requests are rejected. |
| App-entry verification | Requests loading the app are signature-checked before any store context is trusted. |
| Data minimization in code | The adapter extracts four customer values and ignores the rest of the payload — including address fields. |
| No PII in application logs | Connect log statements record shop domains and ids only. No customer name, email or phone is written to application logs. |
| Compliance webhooks implemented | The three Shopify privacy webhooks are implemented and queued off the request thread. What each one does is documented below. |
Requires infrastructure verification
The following cannot be established by reading the application code. They are properties of servers, databases, backups, staff process and written policy. They are listed here unresolved on purpose — turning an assumption into a claim is exactly what this page is trying to avoid.
Not verified from the codebase
Encryption at rest for the database and disks · encryption of backups · separation of test and production data · data loss prevention · restrictions on which staff can reach protected customer data · password and authentication policy for staff accounts · an access log of staff reads of protected customer data · a written security incident response policy · a data processing agreement offered to merchants · defined retention periods.
Several of these are explicit Shopify requirements. Their presence on this list means "not confirmable from this repository", not "absent" — and equally, not "present". If you are evaluating the integration for a business that needs these answered, ask for them directly and expect evidence rather than a page like this one.
Deletion and compliance requests
Shopify sends three privacy webhooks. All three are implemented. What each actually does today:
| Request | What the integration does |
|---|---|
customers/data_request |
Compiles what is held against that Shopify customer id — the id mapping and the internal record it points at — and records it for audit. Delivering that report to the merchant is a manual operational step; it is not automated. |
customers/redact |
Deletes the external-to-internal id mapping for that customer on that store. It does not delete or anonymise the ERP party ledger, and it does not remove raw payloads already held in webhook or sync logs. Re-running it is safe. |
shop/redact |
Deletes the store connection. Entity mappings and the conflict log are removed with it by database constraint. Sync jobs and webhook logs are intended to be removed the same way, but see the gap below. ERP accounting records are not deleted. |
Why a customer ledger is not simply deleted
Once an invoice is posted, the party ledger it was billed to is part of a financial record, and Indian businesses are required to retain accounting records for years. Deleting it would break posted vouchers and your audit trail. That is a genuine tension between an erasure request and statutory bookkeeping, and resolving it is a policy decision — which is precisely why this page reports the current behaviour instead of implying it is settled.
Known gaps
Recorded here because a privacy page that lists only what works is not a privacy page. Each of these is a real finding from reviewing the code:
- No retention or pruning of sync jobs and webhook logs. Nothing in the application deletes them on a schedule, so raw payloads accumulate indefinitely. Shopify's requirements include applying retention periods.
-
Redaction does not reach logged payloads.
customers/redactremoves the id mapping, but a raw webhook body for that customer can remain in the webhook log. - Redaction does not reach the ERP ledger. The name and email on the party ledger survive a redact request. See the note above on why this is not a one-line fix.
- Shop redaction cascade is partly unverified. Entity mappings and the conflict log are confirmed to cascade. The sync-job and webhook-log tables are not created by a migration in this repository, so whether they carry the same cascading constraint has to be checked against the live database schema rather than read from code.
- The data-request report is not delivered automatically. It is recorded for an operator to act on.
If any of these matter to your evaluation, they are reasonable things to ask about directly rather than infer from a website.