--- title: Build privacy-compliant checkouts with visitor consent description: >- Learn how to collect and pass visitor consent information to Checkout Kit for privacy-compliant checkout experiences. 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 --- ExpandOn this page * [How it works](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance.md#how-it-works) * [Requirements](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance.md#requirements) * [Step 1: Understand consent types](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance.md#step-1-understand-consent-types) * [Step 2: Collect visitor consent](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance.md#step-2-collect-visitor-consent) * [Step 3: Pass consent when interacting with the cart](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance.md#step-3-pass-consent-when-interacting-with-the-cart) * [Step 4: Present the checkout](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance.md#step-4-present-the-checkout) * [Visitor consent with preloaded checkouts](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance.md#visitor-consent-with-preloaded-checkouts) * [Best Practices to ensure full Privacy compliance.](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance.md#best-practices-to-ensure-full-privacy-compliance) # Build privacy-compliant checkouts with visitor consent In many jurisdictions, regulations require that you obtain explicit consent from visitors before collecting certain types of data or performing specific tracking activities. Checkout Kit enables you to pass visitor consent information from your mobile app to the Shopify checkout, ensuring privacy compliance throughout the purchase flow. This guide shows you how to collect visitor consent in your mobile app, pass it through the Storefront API using the `@inContext` directive, and present a privacy-compliant checkout experience. This is essential for compliance with privacy regulations like GDPR, CCPA, and other data protection laws. *** ## How it works Checkout Kit supports visitor consent collection through the Storefront API's [`@inContext` directive](https://shopify.dev/docs/api/storefront#directives). When you create a cart, you can include a `visitorConsent` parameter that specifies what types of data collection and processing the visitor has consented to. This consent information is automatically encoded into the [checkout URL](https://shopify.dev/docs/api/storefront/latest/objects/Cart#field-Cart.fields.checkoutUrl) and applied throughout the checkout process to ensure compliance. *** ## Requirements * You have an app using Checkout Kit * Your app has [Storefront API access](https://shopify.dev/docs/storefronts/headless/building-with-the-storefront-api/getting-started#step-1-enable-storefront-api-access) * You're using [Storefront API version 2025-10](https://shopify.dev/docs/api/storefront) or later *** ## Step 1: Understand consent types Checkout Kit supports four types of visitor consent that align with common privacy regulations: * **Analytics**: Consent for collecting data to understand customer behavior and improve services * **Preferences**: Consent for storing visitor preferences to enhance their shopping experience * **Marketing**: Consent for marketing communications and promotional activities * **Sale of data**: Consent for selling personal data to third parties Each consent type is optional and can be granted or denied independently, giving visitors granular control over their privacy preferences. The encoded consent information will automatically take into account which consent types are provided and their values. *** ## Step 2: Collect visitor consent Mobile platforms provide built-in consent mechanisms that users recognize and trust. Map the platform responses to Shopify's consent categories based on your specific data practices. Important **Consent mapping is your responsibility**: Platform consent frameworks have specific scopes that may not directly correspond to Shopify's four consent categories. You must determine the appropriate mapping based on your app's data collection practices, applicable regulations, and legal counsel guidance. ### Use platform-native consent collection ## Platform-native consent collection ```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: // User 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: // User 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 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 -> { // User 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 user'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 ) ``` ```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': // User 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': // User 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); }; ``` ##### iOS ``` 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: // User 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: // User 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) } ``` ##### Android ``` 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 -> { // User 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 user'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 ``` 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': // User 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': // User 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); }; ``` ### Understanding platform consent scope | Platform | Covers | Additional notes | Documentation | | - | - | - | - | | **iOS App Tracking Transparency (ATT)** | Cross-app tracking for advertising, data broker sharing, third-party advertising networks | Does not cover first-party analytics within your app, essential app preferences, non-tracking marketing | [Apple's App Tracking Transparency guide](https://developer.apple.com/app-store/user-privacy-and-data-use/) | | **Android User Messaging Platform (UMP)** | Whatever you configured in your Google AdMob Privacy and messaging setup | Scope varies based on your message configuration, selected partners, and consent purposes | [Google's UMP documentation](https://support.google.com/admob/answer/10107561) | When you map platform specific consent scope to Shopify's Visitor consent types, consider the following: * What specific data practices each platform permission covers * Your app's actual data collection and usage * Applicable privacy regulations in your markets * Your privacy policy commitments * Legal counsel guidance for your specific situation ### Advanced: Custom consent collection Only implement custom consent UIs if platform-native solutions don't meet your specific requirements, for example when: * 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 consent collection that happens outside of platform advertising frameworks. ## Custom consent collection (fallback approach) ```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 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