Shopify
This guide covers what ParcelPilot needs to connect to Shopify, where to find it in Shopify, and where to enter it in ParcelPilot.
What ParcelPilot needs
You’ll typically provide:
- Store domain (e.g.
your-store.myshopify.com) - API Key (Shopify app API key)
- Admin API Secret (Shopify app API secret key)
- Access Token (Admin API access token)
For normal public-app and custom-distribution app connections, Shopify webhook deliveries are normally signed with the app Client Secret or API secret key. ParcelPilot does not require a separate webhook secret for those app-based flows.
Only legacy or manual compatibility connections may need an explicit webhook-secret override.
If ParcelPilot is configured with one shared Shopify app centrally, the merchant may only need:
- Store domain
- Click Connect Shopify (OAuth)
In that setup, ParcelPilot handles the shared app credentials and stores the shop-specific access token after the merchant approves the connection.
For the external public-app path now implemented in ParcelPilot, the merchant-facing Shopify app surfaces are:
- App URL:
https://parcelpilot.co.uk/shopify/appin production, or your current ParcelPilot app URL plus/shopify/app - Preferences URL:
https://parcelpilot.co.uk/shopify/preferencesin production, or your current ParcelPilot app URL plus/shopify/preferences
The current public-app callback URL is your ParcelPilot app URL plus /shopify/auth/callback.
Keep Use legacy install flow enabled in Shopify for this first non-embedded public-app slice.
ParcelPilot also supports a temporary per-integration Shopify app credential override for controlled custom-distribution or pilot apps. That override is additive and does not replace the long-term shared-app model.
Credential resolution order is:
- integration-specific override when configured or explicitly selected
- public-app integrations use the shared public-app global credentials from ParcelPilot config
- other global/default integrations use the legacy shared Shopify app credentials from ParcelPilot config
- legacy per-integration manual credentials for already-connected integrations where still applicable
- explicit configuration failure if no usable credentials exist
Global environment variables are now split by purpose:
SHOPIFY_APP_KEY/SHOPIFY_APP_SECRET: legacy shared global app credentials for existing global/default integrationsSHOPIFY_PUBLIC_APP_KEY/SHOPIFY_PUBLIC_APP_SECRET: shared public-app credentials used by the/shopify/appand/shopify/preferencesinstall flow- integration-specific overrides in
credentials.shopify.app_client_idandcredentials.shopify.app_client_secretstill take precedence whenever the integration is configured to use them
ParcelPilot's current shared public-app OAuth scope set is:
- Required:
read_orders,read_products,read_inventory,write_inventory,read_locations,read_merchant_managed_fulfillment_orders,write_merchant_managed_fulfillment_orders - Optional:
read_all_orders
ParcelPilot uses the merchant-managed fulfillment-order scope model for the public-app foundation.
When shared Shopify app mode is enabled, ParcelPilot normally hides the merchant-facing manual Shopify credential fields because the merchant usually only needs the store domain plus Connect Shopify (OAuth). Super admins can still configure an integration-specific app override when a client must temporarily use a custom-distribution app.
This guide primarily describes the current live Shopify integration workflow in ParcelPilot.
During central-app development and early rollout, the current per-client or private-app connection method remains a supported live onboarding path. Existing clients are not expected to reconnect unless Parcel Pilot intentionally migrates that client.
For the authoritative future Shopify central-app architecture, including the distribution decision, GraphQL prerequisite, phased rollout plan, and coexistence strategy, see docs/architecture/shopify-public-app-migration.md and docs/architecture/shopify-migration-strategy.md.
For the operator deployment, environment, webhook, install, uninstall, and development-store validation checklist, see Shopify public app deployment.
Where to find it in Shopify
Create or open the app
- Shopify Admin → Apps
- Develop apps
- Create a new app (or open your existing ParcelPilot app)
Configure Admin API scopes (permissions)
In the app:
- Go to Configuration
- Under Admin API integration, select the scopes ParcelPilot requires
Current ParcelPilot public-app scopes:
- Orders:
read_orders - Products:
read_products - Inventory sync:
read_inventory,write_inventory - Locations:
read_locations - Merchant-managed fulfillment orders:
read_merchant_managed_fulfillment_orders,write_merchant_managed_fulfillment_orders - Optional wider order history:
read_all_orders
If you see a 403 about read_locations, the merchant must approve the scope (and you may need to reinstall / re-authorize the app).
Install the app to generate an access token
- In the app, click Install app
- After install, Shopify will show the Admin API access token
Get API credentials
In the app:
- Go to API credentials
- Copy:
- API key
- API secret key
- Admin API access token
Store domain
Your store domain is the *.myshopify.com domain for the store, e.g.:
olsson-scandinavia.myshopify.com
Where to enter it in ParcelPilot
ParcelPilot Admin → Integrations → Client Integrations → create/edit an integration with Platform = Shopify.
Fill these fields:
- Store domain →
store_domain - API Key →
credentials.shopify.api_key(optional if ParcelPilot uses a centrally configured Shopify app) - Admin API Secret →
credentials.shopify.api_secret(optional if ParcelPilot uses a centrally configured Shopify app) - Access Token →
credentials.shopify.access_token(usually filled after OAuth connect) - Advanced legacy webhook-secret override (legacy/manual compatibility only) →
credentials.shopify.webhook_secret
Super-admin-only Shopify app override fields:
- Credential source → shared global app, integration override, or legacy/manual mode
- Distribution type → documents whether the override is a temporary custom-distribution app or another compatibility mode
- App client ID →
credentials.shopify.app_client_id - App client secret →
credentials.shopify.app_client_secret
Notes:
- The shop access token always remains per integration even when the app client ID and secret come from global config.
- Existing global/default integrations continue using
SHOPIFY_APP_KEY/SHOPIFY_APP_SECRETautomatically unless they are explicitly moved to another credential source. - Public-app launch and reconnect flows use
SHOPIFY_PUBLIC_APP_KEY/SHOPIFY_PUBLIC_APP_SECRET. - Normal public-app and custom-distribution app webhook validation uses the resolved app Client Secret for that integration.
- Leave the legacy webhook-secret override blank for normal public-app and custom-distribution app onboarding.
- Saved secrets are masked in the UI and are never rendered back in plaintext.
- Leaving the masked override secret untouched preserves the stored encrypted secret.
- Existing connected legacy integrations are preserved and are not forced to re-authorize just because global credentials are now available.
Optional display settings:
- Order number prefix →
settings.order_number_prefix - Order number suffix →
settings.order_number_suffix
Operational settings:
- Push shipment confirmations (tracking) back to ecommerce platform →
settings.push_ship_confirms - Mirror tracking status from Shopify (only if needed) →
settings.shopify_tracking_mirror_enabled
Product Data / Units
Shopify now uses the shared Product Data / Units section for product weight handling.
- Auto-detect or use store default keeps the existing Shopify behavior: ParcelPilot uses Shopify variant
gramswhen present, otherwise it uses the per-variant upstream weight unit supplied by Shopify. - Fixed override stores a shared weight-unit override at
settings.product_units.weight_mode = fixedandsettings.product_units.weight_unit. ParcelPilot uses that override when Shopify supplies a numeric weight without a usable unit.
Shopify dimension handling remains metafield-driven. ParcelPilot continues to read centimetre-based dimension metafields where present rather than using a global integration-level dimension unit.
Super-admin targeted SKU check
On the Client Integration edit page, super admins have a Check SKU tool for Shopify.
Use it when you need to inspect one upstream Shopify variant and, if it is valid, import that exact item immediately.
Current behavior:
- the modal keeps the lookup result visible instead of showing only a transient notification
- Parcel Pilot scans Shopify products for an exact variant SKU match up to the limit you provide
- if one exact match is found, the modal shows the product ID, variant ID, titles, and current upstream status
- if multiple Shopify variants share the SKU, Parcel Pilot reports the ambiguity and does not guess which variant to import
- if the match is valid, Import Item imports the exact checked product and variant using the checked external identifiers rather than repeating a fresh SKU search
Notes:
- this tool is super-admin-only
- the scan limit affects the lookup only; the import step uses the checked stable product and variant identifiers
- existing importer rules still apply, including any upstream validation or duplicate protection already enforced by the Shopify importer
Recommended setup approach
- Start by connecting with the minimum required scopes.
- For the current public-app path, use the required scope set above and only add
read_all_ordersif historical order access is truly needed. - If you want ParcelPilot to push tracking/fulfillments, keep Push shipment confirmations enabled and approve the merchant-managed fulfillment-order scopes.
- If you want inventory syncing, ensure
read_locationsis approved and configure inventory-related scopes.
Webhooks and lifecycle
ParcelPilot's current shared public-app webhook setup is:
- Lifecycle webhook URL: your ParcelPilot app URL plus
/webhooks/shopify - Privacy webhook URL: your ParcelPilot app URL plus
/api/webhooks/gdpr - API and webhook version:
2026-04
ParcelPilot currently subscribes these lifecycle topics:
app/uninstalledapp/scopes_update
ParcelPilot currently subscribes these mandatory privacy topics:
customers/data_requestcustomers/redactshop/redact
Operational order, fulfillment, product, and inventory topics continue to use the existing shared Shopify webhook receiver and are reconciled automatically after OAuth activation.
If the app is uninstalled, ParcelPilot removes the stored Shopify credentials for that integration, disables the integration, and stops future Shopify API work for that connection.
If Shopify reports a scope reduction and any required scope is missing, ParcelPilot marks the integration as requiring reauthorization and disables it until the merchant reconnects with the required scope set.
Protected customer data and privacy handling
ParcelPilot's Shopify integration currently stores or processes protected customer data in order records and related operational metadata, including:
- customer names
- email addresses
- phone numbers when supplied by Shopify
- shipping addresses
- billing addresses
- order notes and custom attributes when included in the order payload
- upstream Shopify order payload fragments retained for operational traceability
Current privacy behavior:
customers/data_requestprepares a local export artifact for the matching integration and customer/order referencescustomers/redactanonymises matching Shopify-linked order PII inside ParcelPilotshop/redactanonymises Shopify-linked order PII for the entire connected shop inside ParcelPilot- uninstall and redact flows do not delete the operational order record itself; they remove or anonymise protected personal data while preserving fulfilment and audit history that ParcelPilot still needs operationally
Troubleshooting
403 Forbidden: missing read_locations scope
If ParcelPilot logs show an error like “merchant approval required for read_locations scope”, the Shopify merchant must approve it.
Fix:
- Add
read_locationsto the app scopes - Reinstall / re-authorize the app on the store
(There is also an internal workaround using an explicit location id if configured, but the recommended fix is to approve read_locations.)
Access token not found
If Shopify doesn’t show an Admin API access token, ensure the app is installed on the store.
“The installation link for this app is invalid” during OAuth
This usually means the OAuth authorize URL was built with the wrong Shopify app client ID for that integration.
Check:
- the integration's Store domain is the correct
*.myshopify.comhost - the integration is using the intended credential source
- if a temporary custom-distribution app is required for that client, the super-admin override client ID and secret are present on that integration
- the shared global Shopify app credentials in ParcelPilot are still correct for non-override integrations
ParcelPilot binds OAuth callback validation to the integration, shop domain, and credential profile that started the install flow, so mismatched app credentials will fail rather than silently connecting the wrong app.
Missing orders?
If orders are missing in ParcelPilot, you can manually backfill using Pull Orders on the integration:
Security notes
- Treat the Admin API access token like a password.
- Only share credentials through a secure channel.
- Rotate tokens if you suspect exposure.
See also
- For deployment, OAuth, webhook, uninstall, reinstall, and dev-store validation steps, see Shopify public app deployment.
- If inventory sync is blocked by Shopify location permissions, use the troubleshooting steps above for the
read_locationsscope issue and re-authorize the app if needed. - For derived parent bundle quantities, see Virtual Bundle Stock Sync.