ParcelPilot
Help

Shopify public app deployment

This runbook covers the operator steps for deploying, configuring, installing, validating, uninstalling, and reinstalling the Parcel Pilot Shopify public app.

Use this document for operational setup and live validation.

For the architectural rationale, rollout policy, and migration history, see:

1) Architecture summary

Parcel Pilot's current Shopify public-app model is:

  • a non-embedded external Shopify app
  • one shared global public-app client ID and secret by default
  • one Shopify shop access token stored per Parcel Pilot ClientIntegration
  • optional encrypted per-integration app credential overrides for temporary custom-distribution or pilot apps
  • GraphQL or Bulk for Shopify business-data operations on the public-app path
  • REST kept only for explicitly retained legacy or custom-app compatibility modes
  • declarative Shopify app configuration for mandatory compliance topics
  • per-shop GraphQL reconciliation for lifecycle and operational topics

Important boundaries:

  • do not treat temporary custom-distribution app overrides as a replacement for the future shared public app
  • keep compliance topics declarative in shopify.app.toml
  • do not declare lifecycle or operational topics in shopify.app.toml while Parcel Pilot owns them through per-shop GraphQL reconciliation
  • keep architectural design decisions in Shopify Central App Architecture and Shopify Migration Coexistence Strategy

2) Shopify Dev Dashboard configuration

Use this baseline for the shared public app.

Replace https://parcelpilot.co.uk with the actual public hostname if APP_URL differs in the target environment.

App name: Parcel Pilot
App URL: https://parcelpilot.co.uk/shopify/app
Embed app in Shopify Admin: No
Preferences URL: https://parcelpilot.co.uk/shopify/preferences
Allowed redirection URL: https://parcelpilot.co.uk/shopify/auth/callback
Webhooks API version: 2026-04
Required scopes: read_orders,read_products,read_inventory,write_inventory,read_locations,read_merchant_managed_fulfillment_orders,write_merchant_managed_fulfillment_orders
Optional scopes: read_all_orders
Use legacy install flow: Enabled
Embed in Shopify POS: No
App proxy: not configured

Notes:

  • The public app remains non-embedded in the current rollout slice.
  • shopify.app.toml is now the source of truth for mandatory compliance topics only.
  • Lifecycle and operational topics must not also be declared in Shopify app configuration while Parcel Pilot reconciles them per shop through the GraphQL Admin API.
  • If you deploy to staging or another non-production host, every App URL, Preferences URL, redirect URL, and webhook endpoint in Shopify must match that environment exactly.

App configuration deploy workflow

The repository now includes a committed Shopify app config at shopify.app.toml.

Use it for compliance-topic deployment:

shopify app config link
shopify app deploy

Operator rules:

  • replace client_id and URLs in the linked app config when targeting a non-production app or host
  • deploy the app config before first install so the compliance topics exist before Shopify starts delivering them
  • keep compliance topics in TOML only; do not recreate them through Parcel Pilot runtime code

3) Webhook endpoints and topics

Current mandatory privacy endpoint:

  • https://parcelpilot.co.uk/api/webhooks/gdpr

Mandatory privacy topics:

  • customers/data_request
  • customers/redact
  • shop/redact

Current lifecycle and operational endpoint:

  • https://parcelpilot.co.uk/api/webhooks/shopify

Topics currently reconciled after OAuth:

  • app/uninstalled
  • app/scopes_update
  • orders/create
  • orders/updated
  • orders/cancelled
  • fulfillments/create
  • fulfillments/update
  • products/create
  • products/update
  • products/delete
  • inventory_levels/update
  • inventory_items/update

Ownership model:

  • Compliance topics are deployed from shopify.app.toml with shopify app deploy.
  • Parcel Pilot reconciles lifecycle and operational subscriptions after successful OAuth activation through the GraphQL Admin API.
  • Matching lifecycle and operational subscriptions are reused rather than recreated.
  • Duplicate lifecycle and operational subscriptions for the same topic are cleaned up during reconciliation.
  • Per-shop runtime reconciliation is authoritative only for lifecycle and operational topics.

Operator rule:

  • do not add lifecycle or operational topics to declarative app config unless the code and runbook are intentionally changed together

4) Environment and configuration inventory

Use placeholders only. Never commit or paste live secrets into documentation.

Key Purpose Required Example format Rebuild config cache Restart queue workers
APP_URL Canonical external base URL used for OAuth callbacks, app launch URLs, and webhook addresses Yes https://app.example.com Yes Yes
APP_KEY Laravel encryption key for encrypted secrets and privacy artifacts Yes base64:<app-key> Yes Yes
APP_PREVIOUS_KEYS Previous Laravel encryption keys kept during controlled key rotation Optional base64:<old-key-1>,base64:<old-key-2> Yes Yes
SHOPIFY_APP_KEY Legacy shared global Shopify app client ID used by existing global/default integrations Required only when legacy global integrations still exist <legacy-shopify-client-id> Yes Yes
SHOPIFY_APP_SECRET Legacy shared global Shopify app client secret used by existing global/default integrations Required only when legacy global integrations still exist <legacy-shopify-client-secret> Yes Yes
SHOPIFY_PUBLIC_APP_KEY Global shared Shopify public-app client ID Yes for the shared public app <shopify-public-client-id> Yes Yes
SHOPIFY_PUBLIC_APP_SECRET Global shared Shopify public-app client secret Yes for the shared public app <shopify-public-client-secret> Yes Yes
SHOPIFY_WEBHOOK_SECRET Shared legacy compatibility webhook HMAC override when a deployment intentionally uses a value different from the shared app Client Secret Optional legacy compatibility only <legacy-webhook-secret> Yes Yes
SHOPIFY_PUBLIC_APP_API_VERSION API version used by the public-app path Yes 2026-04 Yes Yes
SHOPIFY_PUBLIC_APP_WEBHOOK_API_VERSION Webhook subscription API version Recommended 2026-04 Yes Yes
QUEUE_CONNECTION Queue backend for webhook and sync jobs Yes database Yes Yes
SHOPIFY_IMPORT_QUEUE Queue name used for Shopify import work where configured Optional but operationally relevant imports Yes Yes
CACHE_STORE Shared cache backend for OAuth state and operational cache usage Yes database or redis Yes Yes
SESSION_DRIVER Session storage used for login and operator flows Yes database Yes No
SESSION_DOMAIN Cookie domain for authenticated operator flows when needed Optional .example.com Yes No
SESSION_SECURE_COOKIE Forces secure cookies on HTTPS deployments Recommended true Yes No
SHOPIFY_PRIVACY_ARTIFACT_DISK Storage disk for encrypted privacy export artifacts Optional local Yes Yes
company website config Used where Shopify-facing metadata or support context needs a canonical business site Optional but recommended https://www.example.com Yes No
company support config Used where support contact details are surfaced operationally Optional but recommended support@example.com Yes No

Environment rules:

  • OAuth state is stored in cache, expires quickly, and is consumed once.
  • Cache must be shared across all web nodes. A non-shared cache can break OAuth callbacks when the callback lands on a different node than the launch request.
  • Privacy artifacts are encrypted before storage and should remain on a non-public disk.
  • Shopify normally signs app webhooks with the app Client Secret. Normal public-app and custom-distribution app onboarding should not require a separate webhook secret.
  • Parcel Pilot resolves public-app installs and reconnects against SHOPIFY_PUBLIC_APP_KEY / SHOPIFY_PUBLIC_APP_SECRET.
  • Existing global/default integrations continue using SHOPIFY_APP_KEY / SHOPIFY_APP_SECRET unless they are explicitly migrated or switched to an integration-specific override.
  • Changing APP_KEY requires a planned key-rotation process. Use APP_PREVIOUS_KEYS so existing encrypted records can still be read during transition.
  • Temporary custom-distribution apps use encrypted per-integration overrides. Do not replace the global public-app credentials in .env just to onboard one temporary merchant.

When a value changes:

  • rebuild config cache for every key in the table above
  • restart queue workers for queue-processed webhook and sync paths when the effective runtime config changes

5) Deployment procedure

Use the repository-approved deployment sequence from the application root:

composer install --no-dev --prefer-dist --optimize-autoloader
npm ci
npm run build
php artisan migrate --force
php artisan optimize:clear
php artisan config:cache
php artisan view:cache
php artisan queue:restart

Do not add php artisan route:cache to this runbook.

Reason:

  • the repository currently contains closure routes, so route caching is not a safe default

If production uses the documented systemd queue workers, restart and inspect them after deploy:

sudo systemctl restart parcelpilot-queue-default parcelpilot-queue-imports
sudo systemctl status parcelpilot-queue-default --no-pager
sudo systemctl status parcelpilot-queue-imports --no-pager
sudo journalctl -u parcelpilot-queue-default -n 200 --no-pager
sudo journalctl -u parcelpilot-queue-imports -n 200 --no-pager

Scheduler verification:

php artisan schedule:list
php artisan schedule:run

Related worker setup guide:

6) Pre-install validation

Run the focused Shopify regression suites and require them to pass before the first live development-store installation.

Public-app foundation and webhook validation:

php artisan test tests/Feature/ShopifyWebhookCredentialVerificationTest.php tests/Feature/ShopifyLifecycleAndPrivacyWebhookTest.php tests/Feature/ShopifyPublicAppLaunchTest.php tests/Feature/ShopifyOAuthCredentialOverrideTest.php tests/Feature/ShopifyPublicAppConfigTest.php

GraphQL and Bulk transport validation:

php artisan test tests/Feature/ShopifyGraphqlTransportTest.php tests/Feature/ShopifyGraphqlMutationTransportTest.php tests/Feature/ShopifyBulkProductCatalogTransportTest.php tests/Feature/ShopifyLocationGraphqlTransportTest.php tests/Feature/ShopifyInventoryLevelGraphqlTransportTest.php tests/Feature/ShopifyInventoryItemGraphqlTransportTest.php

Fulfillment and tracking transport validation:

php artisan test tests/Feature/ShopifyFulfillmentGraphqlTransportTest.php tests/Feature/ShopifyFulfillmentCreateGraphqlTransportTest.php tests/Feature/ShopifyFulfillmentTrackingUpdateGraphqlTransportTest.php tests/Feature/ShopifyFulfillmentListGraphqlTransportTest.php tests/Feature/ShopifyFulfillmentTrackingDiscoveryTest.php tests/Feature/ShopifyFulfillmentMultiTrackingTest.php tests/Feature/ShopifyFulfillmentIdentityPersistenceTest.php

Smoke checks:

php artisan route:list --except-vendor | grep -E 'shopify/(app|preferences|auth/callback|auth/redirect)|webhooks/(shopify|gdpr)'
curl -I https://parcelpilot.co.uk/up

Manual reachability checks:

  • Confirm the App URL, Preferences URL, callback URL, and webhook URLs are externally reachable over HTTPS.
  • Confirm Shopify can hit the callback and webhook URLs without a Parcel Pilot operator session.
  • Confirm the hostname used in Shopify exactly matches APP_URL for the target environment.

7) Development-store fixture setup

Use synthetic test data only.

Minimum fixture set:

  • one primary merchant-managed Shopify location
  • preferably one secondary location so inactive or non-primary inventory behavior can be checked
  • at least one tracked inventory product
  • at least one untracked inventory product
  • one recent unfulfilled order
  • one partially fulfilled order
  • one already-fulfilled upstream order
  • one order with multiple fulfillments
  • one order or shipment path with multiple tracking numbers
  • synthetic customer contact and address data only
  • no production customer data

Recommended setup notes:

  • create the fixtures directly in a Shopify development store
  • keep one store dedicated to public-app rollout testing where possible
  • if testing historical-order behavior, document whether read_all_orders was granted and whether the store actually contains old-enough orders to prove the boundary

8) First installation checklist

  • Release the current Shopify app version in the Shopify Dev Dashboard or confirm the target draft version is the one being installed.
  • Confirm the App URL, Preferences URL, redirect URL, API version, and scope set match this runbook.
  • Open the install URL from Shopify or https://parcelpilot.co.uk/shopify/app?shop=<dev-store>.myshopify.com.
  • Complete OAuth and confirm Parcel Pilot redirects back into the expected integration settings flow.
  • Confirm the Shopify access token is stored on the correct ClientIntegration only.
  • Confirm the granted scopes match the required scope set and any intentionally approved optional scopes.
  • Run shopify app deploy for the linked app config and confirm the compliance topics point at /api/webhooks/gdpr.
  • Confirm lifecycle and operational webhook subscriptions exist after OAuth.
  • Open the app again from Shopify Admin and confirm the flow does not loop back into OAuth unnecessarily.
  • Open Preferences from Shopify Admin and confirm the operator reaches the intended integration settings page after authentication.
  • Uninstall the app and confirm the integration is disabled and marked uninstalled.
  • Reinstall the app and confirm the integration reactivates cleanly.
  • Confirm reinstall or reauthorize does not create duplicate webhook subscriptions.
  • Check queue workers, webhook processing, and relevant audit entries after install, uninstall, and reinstall.

9) Business workflow test checklist

Keep this section operational. Use it as a compact smoke-test list rather than a full engineering test script.

  • Confirm shop metadata can be read successfully with the installed token.
  • Confirm recent orders can be imported or resolved for the development store.
  • Confirm product sync and Bulk product sync complete successfully.
  • Confirm inventory items, inventory levels, and locations can be read.
  • Confirm an inventory quantity update succeeds through the supported Parcel Pilot flow.
  • Confirm merchant-managed fulfillment-order reads succeed.
  • Confirm fulfillment creation succeeds for a valid test order.
  • Confirm tracking updates succeed, including a case with multiple tracking numbers when supported by the selected scenario.
  • Confirm missing-identity GraphQL discovery can recover the fulfillment identity needed for downstream updates.
  • Confirm the public-app path stays on the GraphQL or Bulk route for the business operations it owns.
  • Confirm endpoint-mode rollback behavior is used only for explicit legacy or custom-app compatibility integrations, not for the normal public-app baseline.

10) Security and isolation checks

Run these checks during rollout validation:

  • invalid shop input is rejected
  • OAuth callback fails on shop and integration mismatch
  • expired or replayed OAuth state is rejected
  • webhook delivery signed with the wrong secret is rejected
  • the global secret is rejected for an override-bound integration when that integration is bound to a different app credential source
  • Preferences access is blocked across tenants
  • disabled or uninstalled integrations do not continue normal Shopify API behavior
  • two integrations bound to different Shopify app credentials stay isolated for OAuth and webhook verification

11) Temporary custom-distribution app procedure

Use this only when a merchant must temporarily connect through a custom-distribution Shopify app before the approved shared public app is the right path.

Procedure:

  1. Keep the future shared public-app credentials in global environment configuration.
  2. Configure the temporary custom app client ID and client secret as encrypted integration-level overrides on the specific ClientIntegration.
  3. Use those same integration-specific credentials for both OAuth and webhook HMAC verification on that integration.
  4. Store the resulting shop access token only on that integration.
  5. Do not overwrite global .env Shopify credentials to support one temporary merchant.
  6. When the merchant is ready for the approved public app, install the public app, reauthorize, verify scopes and webhooks, then remove the temporary overrides.

Operator warning:

  • temporary custom-distribution app overrides are additive compatibility tooling, not the long-term global public-app configuration model

12) Approval and rollout boundaries

The following can be tested before broader Shopify approval:

  • OAuth on Shopify development stores
  • webhook mechanics
  • uninstall and reinstall behavior
  • GraphQL business operations using synthetic development-store data
  • privacy webhook processing using test deliveries
  • credential isolation between global and override-bound integrations

The following requires or may require Shopify approval before public production rollout:

  • read_all_orders
  • protected customer-data access for public production stores
  • App Store publication

Additional note:

  • Built for Shopify status is not required for initial App Store approval

13) Troubleshooting

Common failures and first checks:

Invalid installation link

Check that the install URL was generated from the correct app client ID for that integration and that the shop parameter uses the real *.myshopify.com domain.

Callback URL mismatch

Check that Shopify's allowed redirect URL exactly matches APP_URL plus /shopify/auth/callback for the deployed environment.

Incorrect client ID or secret source

Check whether the integration is meant to use:

  • the shared global public-app credentials
  • an integration override
  • a preserved legacy or manual compatibility path

Missing OAuth state caused by non-shared cache

Check that CACHE_STORE is shared across every web node serving the public app.

Invalid webhook HMAC

Check that Shopify is signing with the secret that matches the integration's active credential source.

Duplicate webhook subscriptions

Check whether lifecycle or operational topics are being written by a second source. Parcel Pilot already reconciles those topics through GraphQL, so repeated duplicates usually mean another runtime or manual installer is still writing them.

For operator-safe inspection and repair from the Parcel Pilot app environment that owns the live integration record:

  • Inspect only: php artisan shopify:public-app-webhooks <integration_id>
  • Reconcile with the current public-app policy: php artisan shopify:public-app-webhooks <integration_id> --apply

This command uses the current config/shopify_public_app.php topic set, SHOPIFY_PUBLIC_APP_WEBHOOK_API_VERSION or its fallback, and the shared /api/webhooks/shopify callback for lifecycle and operational topics. Compliance topics are expected from shopify.app.toml and are reported separately rather than counted as per-shop missing subscriptions.

Missing required scope

Check the granted scopes stored on the integration. If a required scope is missing, reauthorize with the required set and confirm the merchant approved the missing scope.

Queued work continues after deploy

Check whether workers were restarted or received php artisan queue:restart after the deploy.

Wrong APP_URL

Check every Shopify-facing URL in the dashboard, then confirm the deployed environment's APP_URL, reverse-proxy configuration, and HTTPS termination are aligned.

App redirect loop

Check whether the integration already has a valid token and required scopes, and whether the callback or post-auth redirect hostname differs from APP_URL.

No trusted fulfillment identity or manual-review tracking outcome

Check the GraphQL fulfillment-identity discovery path, the persisted fulfillment identifiers on the order or shipment context, and whether the integration has fallen back to a legacy compatibility mode intentionally.

Related guides