--- title: Privacy compliance description: >- Collect visitor consent and pass it to Checkout Kit so Shopify applies the right tracking and data collection rules in checkout. source_url: html: 'https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance' md: >- https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance.md --- # Privacy compliance Privacy regulations like the General Data Protection Regulation (GDPR) and the California Consumer Privacy Act (CCPA) require consent before tracking visitors or collecting personal data. Checkout Kit passes visitor consent from your app to Shopify, which applies the right tracking and data collection rules during checkout. Collect consent in your app and pass it through the Storefront API. *** ## How it works When you create a cart, include a `visitorConsent` parameter in the Storefront API's [`@inContext` directive](https://shopify.dev/docs/api/storefront#directives). Shopify encodes the consent into the [`checkoutUrl`](https://shopify.dev/docs/api/storefront/latest/objects/Cart#field-Cart.fields.checkoutUrl) and applies it during checkout. The Storefront API's [`VisitorConsent`](https://shopify.dev/docs/api/storefront/latest/input-objects/VisitorConsent) input object has four fields: * **`analytics`**: Data collection to understand buyer behavior. * **`preferences`**: Storing visitor preferences for their shopping experience. * **`marketing`**: Promotional communications and advertising. * **`saleOfData`**: Sharing or selling personal data to third parties. Each consent type is optional. If you omit a type, then Shopify treats it as not provided. *** ## Requirements * [Checkout Kit](https://shopify.dev/docs/storefronts/mobile/checkout-kit) installed in your app. * [Storefront API access](https://shopify.dev/docs/storefronts/headless/building-with-the-storefront-api/getting-started) configured for your app. * Storefront API version `2025-10` or later, which is required for `visitorConsent` on the `@inContext` directive. *** ## Step 1: Collect visitor consent iOS provides [App Tracking Transparency (ATT)](https://developer.apple.com/app-store/user-privacy-and-data-use/) and Android provides the [User Messaging Platform (UMP)](https://support.google.com/admob/answer/10107561). On React Native, libraries like [react-native-tracking-transparency](https://github.com/mrousavy/react-native-tracking-transparency) wrap ATT for iOS, but no comparable cross-platform wrapper exists for UMP on Android. For Android on React Native, use a [custom consent UI](#custom-consent-collection) instead. Use these frameworks to prompt buyers, then map their responses to Shopify's consent categories. ### Use platform-native consent collection Collect consent using each platform's built-in framework: ## Platform-native consent collection ##### Swift ```swift import AppTrackingTransparency import AdSupport class ConsentManager { func requestConsent() async -> VisitorConsent { // Request iOS App Tracking Transparency permission let status = await ATTrackingManager.requestTrackingAuthorization() return mapATTToVisitorConsent(status: status) } private func mapATTToVisitorConsent(status: ATTrackingManager.AuthorizationStatus) -> VisitorConsent { // Map ATT response to your specific consent requirements // ATT covers cross-app tracking - you decide how this maps to your data practices switch status { case .authorized: // Buyer allowed cross-app tracking return VisitorConsent( analytics: true, // Consider: Does this cover your analytics use? preferences: true, // Consider: Are preferences affected by ATT? marketing: true, // Consider: Does this cover your marketing practices? saleOfData: true // Consider: Does this cover your data sharing? ) case .denied, .restricted: // Buyer denied cross-app tracking return VisitorConsent( analytics: true, // Consider: Can you still do first-party analytics? preferences: true, // Consider: Are preferences still allowed? marketing: false, // Consider: What marketing is still allowed? saleOfData: false // Consider: What data sharing is affected? ) case .notDetermined: // Handle undetermined state based on your requirements return getDefaultConsent() @unknown default: return getDefaultConsent() } } private func getDefaultConsent() -> VisitorConsent { // Define your default consent state return VisitorConsent( analytics: false, preferences: true, // Usually considered essential marketing: false, saleOfData: false ) } } struct VisitorConsent { let analytics: Bool let preferences: Bool let marketing: Bool let saleOfData: Bool static let defaultConsent = VisitorConsent( analytics: false, preferences: true, marketing: false, saleOfData: false ) } // Usage example let consentManager = ConsentManager() func collectConsentAndCreateCart() async { let consent = await consentManager.requestConsent() createCartWithConsent(consent: consent) } ``` ##### Kotlin ```kotlin import com.google.android.ump.* class ConsentManager(private val context: Context) { fun requestConsent(completion: (VisitorConsent) -> Unit) { val consentInformation = UserMessagingPlatform.getConsentInformation(context) consentInformation.requestConsentInfoUpdate( ConsentRequestParameters.Builder().build(), { // Load consent form if needed UserMessagingPlatform.loadConsentForm(context) { form, error -> if (form != null) { form.show(context as Activity) { val consent = mapUMPToVisitorConsent(consentInformation) completion(consent) } } else { // Handle form load error completion(getDefaultConsent()) } } }, { error -> // Handle consent update error completion(getDefaultConsent()) } ) } private fun mapUMPToVisitorConsent(consentInfo: ConsentInformation): VisitorConsent { // Map UMP response to your specific consent requirements // UMP scope depends on what YOU configured in your AdMob setup return when (consentInfo.consentStatus) { ConsentInformation.ConsentStatus.OBTAINED -> { // Buyer provided consent based on YOUR UMP configuration VisitorConsent( analytics = true, // Consider: What did you ask consent for? preferences = true, // Consider: Did UMP cover preferences? marketing = true, // Consider: What marketing was included? saleOfData = true // Consider: Did you ask about data sales? ) } ConsentInformation.ConsentStatus.NOT_REQUIRED -> { // Consent not required for buyer's location VisitorConsent( analytics = true, preferences = true, marketing = true, saleOfData = true ) } else -> { // Required but not obtained, or unknown status getDefaultConsent() } } } private fun getDefaultConsent(): VisitorConsent { return VisitorConsent( analytics = false, preferences = true, // Usually considered essential marketing = false, saleOfData = false ) } } data class VisitorConsent( val analytics: Boolean, val preferences: Boolean, val marketing: Boolean, val saleOfData: Boolean ) ``` ##### React Native ```javascript import { ATTrackingManager } from 'react-native-tracking-transparency'; class ConsentManager { async requestConsent() { try { // Request iOS ATT permission const trackingStatus = await ATTrackingManager.requestTrackingAuthorization(); return this.mapPlatformToVisitorConsent(trackingStatus); } catch (error) { console.error('Consent request failed:', error); return this.getDefaultConsent(); } } mapPlatformToVisitorConsent(platformStatus) { // Map platform response to your specific consent requirements // Consider what each platform permission actually covers for YOUR app switch (platformStatus) { case 'authorized': // Buyer allowed tracking return { analytics: true, // Consider: Your analytics practices preferences: true, // Consider: Preference handling marketing: true, // Consider: Your marketing scope saleOfData: true // Consider: Your data sharing }; case 'denied': case 'restricted': // Buyer denied or restricted tracking return { analytics: true, // Consider: First-party analytics preferences: true, // Consider: Essential preferences marketing: false, // Consider: Limited marketing saleOfData: false // Consider: No data sharing }; case 'notDetermined': default: return this.getDefaultConsent(); } } getDefaultConsent() { // Define your app's default consent state return { analytics: false, preferences: true, // Usually essential for app function marketing: false, saleOfData: false }; } } // Usage example const consentManager = new ConsentManager(); const collectConsentAndCreateCart = async () => { const consent = await consentManager.requestConsent(); createCartWithConsent(consent); }; ``` ### Custom consent collection Only implement custom consent UIs if platform-native solutions don't meet your specific requirements, for example: * Your app needs more granular consent options than platform frameworks provide. * You're targeting enterprise/B2B scenarios with specific compliance requirements. * Platform-native solutions don't cover your specific use case or jurisdiction. * You need to collect consent outside platform advertising frameworks. ## Custom consent collection ##### Swift ```swift struct CustomConsentView: View { @State private var analyticsConsent = false @State private var preferencesConsent = true // Often essential @State private var marketingConsent = false @State private var saleOfDataConsent = false var body: some View { VStack(alignment: .leading, spacing: 16) { Text("Privacy Preferences") .font(.headline) Toggle("Analytics - Help us improve our services", isOn: $analyticsConsent) Toggle("Preferences - Remember your shopping preferences", isOn: $preferencesConsent) Toggle("Marketing - Receive promotional offers", isOn: $marketingConsent) Toggle("Sale of data - Allow sharing data with partners", isOn: $saleOfDataConsent) Button("Continue to Checkout") { let consent = VisitorConsent( analytics: analyticsConsent, preferences: preferencesConsent, marketing: marketingConsent, saleOfData: saleOfDataConsent ) createCartWithConsent(consent: consent) } } .padding() } } ``` ##### Kotlin ```kotlin class CustomConsentFragment : Fragment() { private var analyticsConsent = false private var preferencesConsent = true // Often essential private var marketingConsent = false private var saleOfDataConsent = false override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val view = inflater.inflate(R.layout.fragment_custom_consent, container, false) view.findViewById(R.id.analytics_checkbox).apply { setOnCheckedChangeListener { _, isChecked -> analyticsConsent = isChecked } } view.findViewById(R.id.preferences_checkbox).apply { isChecked = true // Often essential setOnCheckedChangeListener { _, isChecked -> preferencesConsent = isChecked } } view.findViewById(R.id.marketing_checkbox).apply { setOnCheckedChangeListener { _, isChecked -> marketingConsent = isChecked } } view.findViewById(R.id.sale_of_data_checkbox).apply { setOnCheckedChangeListener { _, isChecked -> saleOfDataConsent = isChecked } } view.findViewById