Chapter 26: App Store Compliance

Shopify Requirements

Embedded App Mandate

As of late 2024, Shopify requires all new public apps to be embedded within the Shopify Admin. Standalone apps (opening in a separate browser tab) are no longer approved for the App Store.

AdPriority satisfies this requirement by rendering inside an App Bridge 4.1 iframe. The merchant never leaves the Shopify Admin to use AdPriority.

RequirementAdPriority Implementation
Embedded in Shopify AdminApp Bridge 4.1 iframe
Uses session tokens (not cookies)JWT validation with client secret
Polaris UI componentsPolaris v13 exclusively
Responsive designPolaris handles automatically
Accessibility (WCAG 2.0 AA)Polaris built-in a11y
Loading states on all async operationsSpinner and SkeletonPage
Empty states for all empty listsEmptyState with CTA
Error states with user-friendly messagesBanner with tone="critical"

GDPR Webhooks

Three mandatory webhook endpoints must be implemented and functional at all times. Shopify tests these during the review process.

WebhookEndpointBehavior
customers/data_requestPOST /api/webhooks/customers-data-requestReturn 200 OK. AdPriority stores no customer PII. Response includes a message confirming no personal data is held.
customers/redactPOST /api/webhooks/customers-redactReturn 200 OK. No customer data to delete.
shop/redactPOST /api/webhooks/shop-redactDelete all data for the specified shop within 30 days. Return 200 OK.

Implementation details for shop/redact:

SHOP DATA DELETION
==================

  Webhook received: shop/redact
         |
         v
  Verify HMAC signature
         |
         v
  Look up store by shop domain
         |
         v
  Mark store as is_active = false
         |
         v
  Schedule deletion job (30-day delay)
         |
         v
  After 30 days:
    DELETE FROM audit_logs WHERE store_id = ?
    DELETE FROM sync_logs WHERE store_id = ?
    DELETE FROM season_rules WHERE season_id IN (SELECT id FROM seasons WHERE store_id = ?)
    DELETE FROM seasons WHERE store_id = ?
    DELETE FROM rule_conditions WHERE rule_id IN (SELECT id FROM rules WHERE store_id = ?)
    DELETE FROM rules WHERE store_id = ?
    DELETE FROM products WHERE store_id = ?
    DELETE FROM subscriptions WHERE store_id = ?
    DELETE FROM stores WHERE id = ?
         |
         v
  Log deletion completion
         |
         v
  Return 200 OK

The 30-day grace period allows merchants who accidentally uninstall to reinstall without losing their configuration. After 30 days, all data is permanently and irrecoverably deleted.

Data Deletion on Uninstall

When a merchant uninstalls the app, the app/uninstalled webhook fires. AdPriority handles this by:

  1. Cancelling any active subscription via the Billing API
  2. Stopping all scheduled sync jobs for the store
  3. Marking the store as is_active = false
  4. Starting the 30-day data retention countdown
  5. If the merchant reinstalls within 30 days, data is restored and the countdown is cancelled

Session Token Authentication

AdPriority uses Shopify session tokens (not cookies) for authentication. Session tokens are JWTs issued by App Bridge and signed with the app’s client secret.

Validation requirements (enforced on every API request):

CheckDescription
SignatureVerify JWT signature using SHOPIFY_CLIENT_SECRET with HS256
Audienceaud claim must match SHOPIFY_CLIENT_ID
Expirationexp claim must be in the future (with 10-second clock tolerance)
Not-beforenbf claim must be in the past
Issueriss claim must be a valid .myshopify.com/admin URL
Shop resolutionExtract shop domain from iss, look up store record in database

If any check fails, the API returns 401 Unauthorized. The frontend (via App Bridge) automatically refreshes the session token and retries.

Polaris UI

Shopify mandates the use of Polaris for all UI components in embedded apps. Custom styling that overrides Polaris defaults may cause rejection.

Compliance checklist:

ElementRequirementAdPriority Status
Page layoutUse Page componentAll screens use Page
Content containersUse Card componentAll content sections in Card
NavigationUse NavigationMenu from App Bridge5 navigation items registered
Data tablesUse DataTable or IndexTableProducts list uses IndexTable
FormsUse Polaris form componentsAll forms use TextField, Select, Checkbox
ButtonsUse Button component with correct tonesPrimary, secondary, and destructive actions
ModalsUse Polaris Modal (not browser window.confirm)All confirmation dialogs
Toast notificationsUse Polaris ToastSuccess and error notifications
No custom CSS overriding PolarisStrictly prohibitedOnly additive custom styles for chart components

Google Requirements

AdPriority uses Google OAuth to access the Google Sheets API (for supplemental feed management) and, for Pro tier merchants, the Google Ads API.

Consent screen configuration:

FieldValue
App nameAdPriority
Support emailsupport@adpriority.com
Application typeWeb application
Authorized domainsadpriority.com
Scopes requestedhttps://www.googleapis.com/auth/spreadsheets (Sheets)
Additional scopes (Pro)https://www.googleapis.com/auth/adwords.readonly (Ads)
User typeExternal
Publishing statusTesting (initially), then Production after verification

Google OAuth verification: For the Sheets API scope, Google requires app verification if the app will be used by more than 100 users. Submit for verification early in Phase 2 to avoid blocking the App Store launch.

API Quotas

APIDefault QuotaAdPriority UsageRisk
Google Sheets API300 requests/minute per project~1-5 requests per sync per storeLow
Google Sheets API60 requests/minute per user1 request per sync per storeVery Low
Content API for Shopping2 product updates per day per productNot used in MVP (Sheets approach)N/A
Google Ads API15,000 operations per day (basic)Read-only queries, Pro tier onlyLow

Quota management strategy:

  • Batch all Sheet writes into a single values.update call per sync (one API call for entire store)
  • Monitor quota usage via the Google Cloud Console
  • Implement exponential backoff on 429 responses
  • Alert if daily quota usage exceeds 80%

Data Handling Policy

Google requires a clear statement of how user data accessed through their APIs is handled.

Policy PointAdPriority Practice
Data accessedGoogle Sheets (write custom label data), Google Ads metrics (read-only, Pro tier)
Data storedPriority scores and sync status stored in AdPriority database. Google Ads performance data cached for 90 days.
Data sharedCustom label data is written to a Google Sheet that the merchant has shared with GMC. No data is shared with third parties.
Data retentionDeleted within 30 days of app uninstall
Limited useData is used only for the stated purpose of managing product priority labels
SecurityOAuth tokens encrypted at rest (AES-256), transmitted only over HTTPS

Privacy

No Customer PII Stored

AdPriority’s core design principle is that it never stores customer personally identifiable information. The app works exclusively with product data.

Data AdPriority DOES store:

Data TypeExamplesSource
Product titles“Jordan Craig Stacked Jeans”Shopify product data
Product types“Men-Bottoms-Pants-Jeans”Shopify product data
Vendor names“Jordan Craig”, “New Era”Shopify product data
Product tags“NAME BRAND”, “Sale”, “in-stock”Shopify product data
Product IDsShopify product ID, variant IDShopify product data
Priority scores0-5 integerCalculated by AdPriority
Custom labels“priority-5”, “winter”, “jeans-pants”Generated by AdPriority
Sync logsTimestamps, counts, errorsGenerated by AdPriority
Shop domain“nexus-clothes.myshopify.com”Shopify OAuth
Merchant email“will@nexusclothing.com” (for notifications only)Shopify OAuth

Data AdPriority NEVER stores:

Data TypeReason
Customer namesNot accessed, not needed
Customer emailsNot accessed, not needed
Customer addressesNot accessed, not needed
Order detailsNot accessed, not needed
Payment informationHandled by Shopify Billing API
Customer browsing dataNot collected

Encrypted Credentials

All OAuth tokens and API credentials are encrypted at rest using AES-256.

CredentialStorageEncryption
Shopify access tokenstores.shopify_access_tokenAES-256-GCM
Google refresh tokenstores.google_refresh_tokenAES-256-GCM
Google Sheets service account keyEnvironment variableBase64-encoded, not in database

The encryption key is stored as an environment variable (ENCRYPTION_KEY) and is never committed to source control or written to logs.

Privacy Policy Summary

ADPRIORITY PRIVACY SUMMARY
===========================

  What we collect:
    - Product data (titles, types, tags, vendor, IDs)
    - Shop domain and merchant notification email
    - Priority scores and sync status

  What we DO NOT collect:
    - Customer personal information (names, emails, addresses)
    - Order data
    - Payment data
    - Browsing data

  How we use data:
    - Calculate priority scores for products
    - Sync custom labels to Google Merchant Center
    - Display dashboard and analytics

  Who we share data with:
    - Google Merchant Center (custom labels only, via Google Sheets)
    - No other third parties

  How long we keep data:
    - Active while app is installed
    - Deleted within 30 days of uninstall

  Your rights:
    - Export your data at any time (via GDPR data request)
    - Delete your data by uninstalling the app
    - Contact support@adpriority.com for any privacy questions

Compliance Checklist

Pre-Submission Verification

#RequirementCategoryVerified
1HTTPS on all endpointsSecurity
2OAuth token exchange flow (not implicit)Auth
3Session token JWT validation on every API callAuth
4HMAC verification on all webhook endpointsAuth
5customers/data_request webhook returns 200GDPR
6customers/redact webhook returns 200GDPR
7shop/redact webhook deletes all store dataGDPR
8All UI uses Polaris v13 componentsUI
9Loading states on all async operationsUI
10Empty states on all empty listsUI
11Error states with descriptive messagesUI
12Mobile-responsive layoutUI
13WCAG 2.0 AA accessibilityUI
14Each OAuth scope justifiedScopes
15Privacy policy URL accessibleLegal
16Terms of service URL accessibleLegal
17Support URL accessibleSupport
18No test or debug data in submissionQuality
19App installs cleanly on fresh storeQuality
20App handles store with 0 productsQuality
21App handles store with 50,000+ productsQuality
22Billing flow completes (test mode)Billing
23Uninstall cleans up all resourcesLifecycle
24OAuth tokens encrypted at restSecurity
25No customer PII storedPrivacy