Skip to content
Ecom

Security Hardening Checklist for Shopify Plus Stores

Access control, webhooks, customer data, IP allowlisting, audit review. The 15-point hardening checklist WitsCode runs on every Shopify Plus onboarding.

By WitsCode10 min read

Shopify Plus gives you the controls. It does not turn them on for you. The default posture of a new Plus store is a 24-hour admin session, an open admin login page reachable from any IP on earth, a staff list that grows during launches and never shrinks, and a set of custom apps whose webhook endpoints accept any POST body that looks shaped right. None of that is negligent; it is just the out-of-the-box state. The job of hardening is to walk the organization settings, the app layer, and the operating cadence and tighten each one with intent.

This is the 15-point checklist we run on every Plus onboarding and on every quarterly security review. It is ordered the way we work through it in practice: identity first, network perimeter second, the webhook and app layer third, data egress fourth, and the operating rhythm that keeps the other four honest last. Each item is a paragraph, not a bullet, because the detail is where stores get compromised.

Identity and access: stop sharing logins, start scoping roles

Organization-level 2FA enforcement. In the Shopify organization admin (the top-level settings for Plus accounts, not the individual shop admin), turn on the policy that requires every staff account to have two-factor authentication before they can log in. This is not the same as asking each staff member to enable it in their own profile. The org-level policy refuses the login entirely if the account has no second factor registered, which closes the gap where a new hire logs in once with just a password before their manager reminds them to enroll. If your identity stack supports SAML, point Plus at your Okta or Azure AD or Google Workspace tenant and let the IdP carry the 2FA requirement. SSO means the Shopify staff list becomes a mirror of your directory, and offboarding in one place propagates to the store.

Custom role per contractor engagement. Shopify Plus gives you unlimited custom roles. Use them. Every freelance theme developer, every migration agency, every analytics consultant gets a role named after the engagement, not a generic "Developer" role reused forever. A role named freelance-theme-dev-2026Q1 carries the start and end dates in the name, which forces the quarterly review to notice when Q1 is over and the role should be gone. Scope the role to the minimum: theme edit and online store publish for a front-end contractor, nothing in Orders, nothing in Customers, nothing in Apps, nothing in Settings. When the contract ends, revoke the role and suspend the staff account rather than deleting it, so the audit log continues to resolve the historical user ID to a real name. The Shopify-issued collaborator account each agency uses is separate; give each agency its own collaborator request code and revoke it at contract end.

Session timeout shortened to a workday. The default admin session lasts 24 hours of inactivity, which means a laptop left open at a coffee shop is a laptop logged into your Orders view until tomorrow. In organization settings, shorten the idle timeout to eight hours so the session dies overnight and forces a fresh login and 2FA challenge in the morning. Pair this with a policy that disables the "Remember me" checkbox on shared or kiosk devices; on staff laptops the policy is less important because the laptop itself is the trust boundary, but the default should be off.

Quarterly access review, with a real decision on every account. Once a quarter, the person responsible for store security opens the staff list and makes a yes or no call on every single account. Does this person still need access? Does this agency still have an active contract? Is this role still scoped correctly for the work they actually do now, or has the work shifted? Suspend anything that cannot be justified with a current sentence. This is the single highest-leverage control in the checklist because without it the other controls decay: custom roles accumulate permissions over time, contractors forget to tell you they are done, and the staff list quietly drifts from 12 to 47.

Network perimeter: the admin is not supposed to be on the open internet

IP allowlist on the admin. Shopify Plus lets you restrict admin login to a list of IP ranges in organization settings. Put your corporate VPN egress CIDRs in it, add the egress range of your managed WARP or Tailscale team if you use one, and add a break-glass bastion IP for incident recovery. Do not allowlist home IPs; they rotate and they create a support burden. The operational discipline is that admin access requires VPN, full stop. Test the allowlist with a secondary owner account logging in from outside the list to confirm the lockout actually works, then document which person holds the break-glass account and where its offline one-time-password backup is stored. A misconfigured allowlist that locks out every owner is the most common mistake; the break-glass procedure is the answer.

Break-glass account, documented and tested. One owner account sits outside the SSO and outside the normal 2FA rotation, with its credential stored in a sealed envelope or a dedicated secrets vault entry accessed by two people. This is the account you use when Okta is down, when the VPN is down, when the person with the IP allowlist admin rights just left the company. Test it once a quarter by logging in from the documented bastion and confirming it still works. Rotate its password every time you test.

The webhook and app layer: this is where code meets trust

Webhook HMAC verification, with timing-safe comparison. Every Shopify webhook arrives with an X-Shopify-Hmac-Sha256 header containing a base64-encoded HMAC-SHA256 signature of the raw request body, computed with your app's shared secret as the key. The verification is three lines of logic and one subtle trap. The trap is that the comparison between your computed HMAC and the header value must be timing-safe, which means crypto.timingSafeEqual in Node, hmac.compare_digest in Python, ActiveSupport::SecurityUtils.secure_compare in Ruby. A naive == or === comparison leaks information about how many bytes matched via response time, and while that is a narrow attack against your specific secret, the fix is so cheap that there is no reason to ship the naive version.

In Node, the correct shape is:

const crypto = require('crypto');

function verifyShopifyWebhook(rawBody, hmacHeader, secret) {
  const digest = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('base64');
  const a = Buffer.from(digest);
  const b = Buffer.from(hmacHeader);
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

The rawBody is the exact bytes Shopify sent. If your framework parses JSON before you get the body, the signature will not match, because the reserialized JSON differs from the original in whitespace and key order. In Express, mount express.raw({ type: 'application/json' }) on the webhook route, not express.json(), and feed req.body (now a Buffer) into the verifier. In Next.js API routes, disable the default body parser with export const config = { api: { bodyParser: false } } and read the stream yourself. This ordering bug is the single most common reason webhook verification silently fails and teams disable the check to "make it work."

GDPR webhooks registered and responding. Shopify mandates three privacy webhooks on every app: customers/data_request (merchant asking for a customer's data, 30-day SLA to respond), customers/redact (delete this customer's PII, 10-day SLA), and shop/redact (the merchant uninstalled your app 48 hours ago, delete their shop data within 30 days). These are not optional and they use the same HMAC verification as the business webhooks. Register them in the app configuration, wire them to actual deletion logic in your data store, and log every invocation. An app that never receives a customers/redact on a test store during QA is an app that is not actually registered.

App install policy, with owner approval. The "Apps" permission on staff roles is the permission that installs code with access to your orders and customers into the store. Keep it on the owner role and one deputy, nothing else. Any new app request goes through an approval step where the requester names the app, the business reason, the data the app will access (the install screen tells you), and whether the vendor has SOC2 or a data processing agreement. Cross-check the installed app list monthly against the approved list; orphan apps from a deprecated project are a common finding.

Collaborator access for agencies, named and time-bounded. Each agency gets its own collaborator request code, generated from the admin and given to one named person at the agency. Do not reuse a collaborator code across agencies or across engagements; each code is a credential. When the engagement ends, revoke the collaborator access in the admin, which is a separate action from revoking the custom role because collaborator accounts sit on the Shopify partner side and persist until you cut them.

Customer data egress: the exfiltration risk most teams ignore

Customer data export restricted to a privacy officer role. The customers/export capability pulls a CSV of your entire customer list including email, address, phone, order history, and marketing consent. Restrict this permission to a privacy officer role held by two people total. Every other support role that needs to look at a customer gets the view-customer permission, not export. When a legitimate export is needed for a migration or an analysis, the privacy officer runs it, delivers it through a DLP-scanned channel (signed S3 URL with 24-hour expiry, not an email attachment), and logs the purpose in a ticket. Scrub the export: if the downstream analysis needs only email and order count, drop the address and phone columns before handing it off.

Audit log visibility on every export. Customer data exports land in the Shopify audit log. The weekly review explicitly looks for export events and matches each one to a ticket. An export with no ticket is an incident until proven otherwise.

Operating rhythm: the cadence that makes the controls real

Weekly audit log scan, Monday morning. Every Monday, the on-call security owner opens the organization audit log and reads the last seven days with five specific questions. Were any new webhooks registered, and do the endpoint domains match known app vendors? Did any custom role gain a permission it did not have last week, especially Orders:edit, Customers:export, or Apps:install? Were any staff accounts added, reinstated, or had their role changed? Were any themes published outside the declared deploy windows? Were any apps installed, and are they on the approved list? The scan takes 20 minutes on a clean week and finds things worth finding on maybe one week in six. That one week pays for all the others.

Monthly audit log export to SIEM. At the end of each month, export the full audit log to your SIEM or a retention bucket. Shopify retains the log for 90 days; if you need longer (and SOC2 Type II needs a year), you own the retention. The export is available via the organization admin API; a scheduled job dumps it to S3 with object lock and a one-year retention policy. This also satisfies the "immutable audit trail" control auditors ask about.

Quarterly access review and permission audit. The quarterly cadence is where the custom roles, the staff list, the collaborator accounts, the installed apps, and the IP allowlist all get a deliberate look. Every role gets justified against current work, every staff account gets a yes/no decision, every installed app gets a reason, and the IP allowlist gets compared against current VPN egress ranges in case the network team changed something and forgot to tell the store team. Document the review in a dated file; that file is the evidence an auditor wants.

The 15 points, named

For the team running this checklist on a new onboarding, the 15 points in order are: organization-level 2FA with SSO preferred, custom role per contractor engagement, IP allowlist on the admin, session timeout shortened to eight hours, break-glass account documented and tested, webhook HMAC verification with timing-safe comparison, raw body capture before JSON parsing, GDPR webhooks registered and responding, app install policy with owner approval, named collaborator accounts per agency revoked at contract end, customer data export restricted to a privacy officer role, weekly audit log scan with the five specific questions, monthly audit log export to SIEM with one-year retention, quarterly access review with written justification per account, and theme publish windows with post-publish audit log diff. Fifteen items, each a paragraph of real operating detail, each something a Plus store can implement this quarter.

How WitsCode runs this engagement

We have implemented this checklist on 250-plus stores across performance, security, and CRO engagements, and the pattern is consistent: the controls are not hard, the cadence is what stores miss. Our Plus security audit and hardening engagement walks every one of the 15 points against your current configuration, fixes what is misconfigured, writes the runbooks for the weekly and monthly and quarterly rhythms, and hands you the audit log review template the on-call owner uses on Monday mornings. If your SOC2 Type II is on the calendar, the same engagement produces the evidence package your auditor will ask for.

Book a Shopify Plus security audit with WitsCode and we will run the 15-point hardening on your store, deliver the runbooks, and train your on-call owner on the weekly review in two weeks.

Get weekly field notes.

Practical writing on shipping products, straight to your inbox. No spam.

Need help with this?

Shopify Development

We design and build web apps, MVPs, and SaaS products. Talk to us about what you are working on.

Talk to us

Want to discuss ecom for your business?

Start a project and we'll talk through where you are, what's working, and the highest-leverage moves for the next 90 days.