ParcelPilot
Help

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:

  1. Store domain (e.g. your-store.myshopify.com)
  2. API Key (Shopify app API key)
  3. Admin API Secret (Shopify app API secret key)
  4. 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:

  1. Store domain
  2. 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/app in production, or your current ParcelPilot app URL plus /shopify/app
  • Preferences URL: https://parcelpilot.co.uk/shopify/preferences in 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:

  1. integration-specific override when configured or explicitly selected
  2. public-app integrations use the shared public-app global credentials from ParcelPilot config
  3. other global/default integrations use the legacy shared Shopify app credentials from ParcelPilot config
  4. legacy per-integration manual credentials for already-connected integrations where still applicable
  5. 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 integrations
  • SHOPIFY_PUBLIC_APP_KEY / SHOPIFY_PUBLIC_APP_SECRET: shared public-app credentials used by the /shopify/app and /shopify/preferences install flow
  • integration-specific overrides in credentials.shopify.app_client_id and credentials.shopify.app_client_secret still 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

  1. Shopify Admin → Apps
  2. Develop apps
  3. Create a new app (or open your existing ParcelPilot app)

Configure Admin API scopes (permissions)

In the app:

  1. Go to Configuration
  2. 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

  1. In the app, click Install app
  2. After install, Shopify will show the Admin API access token

Get API credentials

In the app:

  1. Go to API credentials
  2. 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 domainstore_domain
  • API Keycredentials.shopify.api_key (optional if ParcelPilot uses a centrally configured Shopify app)
  • Admin API Secretcredentials.shopify.api_secret (optional if ParcelPilot uses a centrally configured Shopify app)
  • Access Tokencredentials.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 IDcredentials.shopify.app_client_id
  • App client secretcredentials.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_SECRET automatically 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 prefixsettings.order_number_prefix
  • Order number suffixsettings.order_number_suffix

Operational settings:

  • Push shipment confirmations (tracking) back to ecommerce platformsettings.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 grams when 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 = fixed and settings.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_orders if 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_locations is 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/uninstalled
  • app/scopes_update

ParcelPilot currently subscribes these mandatory privacy topics:

  • customers/data_request
  • customers/redact
  • shop/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_request prepares a local export artifact for the matching integration and customer/order references
  • customers/redact anonymises matching Shopify-linked order PII inside ParcelPilot
  • shop/redact anonymises 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:

  1. Add read_locations to the app scopes
  2. 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:

  1. the integration's Store domain is the correct *.myshopify.com host
  2. the integration is using the intended credential source
  3. if a temporary custom-distribution app is required for that client, the super-admin override client ID and secret are present on that integration
  4. 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_locations scope issue and re-authorize the app if needed.
  • For derived parent bundle quantities, see Virtual Bundle Stock Sync.