From 92fcc505e3e13bdc7a5a92295d641e3c51fee508 Mon Sep 17 00:00:00 2001
From: Alex Timmermann <alexander.timmermann@holi.social>
Date: Fri, 28 Mar 2025 12:35:15 +0000
Subject: [PATCH] HOLI-11044: feat(tracking): add Meta SDK

---
 .env.local                                    |  7 +-
 .env.production                               |  5 +
 .env.staging                                  |  5 +
 apps/mobile/app.config.ts                     | 13 +++
 apps/mobile/eas.json                          | 10 ++
 apps/mobile/package.json                      |  2 +
 .../ManageOptionsContent.tsx                  | 17 +++-
 .../TrackingConsentModalDesktop.tsx           |  4 +
 .../TrackingConsentModalMobile.tsx            |  4 +
 .../TrackingConsentModalDesktop.test.tsx      | 42 +++++++-
 .../TrackingConsentModalMobile.test.tsx       | 47 ++++++++-
 .../__tests__/useConsentModal.test.ts         |  1 +
 .../__tests__/useConsentModalState.test.ts    | 76 ++++++++++++++-
 .../useConsentModalState.ts                   | 30 +++++-
 .../__tests__/useSortedUserTerms.test.ts      |  1 +
 core/domain/shared/queries.ts                 |  2 +
 core/domain/shared/types.ts                   |  2 +
 core/i18n/locales/de.json                     |  3 +
 core/i18n/locales/en.json                     |  3 +
 core/package.json                             |  2 +
 core/screens/onboarding/Onboarding.tsx        |  3 +
 core/screens/userprofile/TrackingSettings.tsx | 18 +++-
 .../__tests__/UserTermsRow.test.tsx           |  1 +
 core/screens/userprofile/queries.ts           |  1 +
 core/tracking/FacebookSdkCrossPlatform.ts     | 97 +++++++++++++++++++
 core/tracking/FacebookSdkCrossPlatform.web.ts | 15 +++
 core/tracking/TrackingInitializer.tsx         | 33 ++++++-
 .../__tests__/TrackingInitializer.test.tsx    | 84 +++++++++++++---
 core/tracking/types.ts                        | 13 +++
 .../__tests__/VolunteeringRecos.test.tsx      |  1 +
 packages/api/graphql/graphql-codegen.ts       |  3 +
 packages/ui/helper/fakeUsers.ts               |  1 +
 yarn.lock                                     | 26 +++++
 33 files changed, 533 insertions(+), 39 deletions(-)
 create mode 100644 core/tracking/FacebookSdkCrossPlatform.ts
 create mode 100644 core/tracking/FacebookSdkCrossPlatform.web.ts

diff --git a/.env.local b/.env.local
index 40519e1afd..21a665d112 100644
--- a/.env.local
+++ b/.env.local
@@ -77,4 +77,9 @@ export TYPESENSE_HOST=dev-search.holi.social
 export TYPESENSE_PORT=443
 export TYPESENSE_PROTOCOL=https
 export TYPESENSE_API_KEY=WAxFP8Gv9RrNvG18RhYgXCNNj39LfAu2
-export TYPESENSE_COLLECTION=holi_search_staging
\ No newline at end of file
+export TYPESENSE_COLLECTION=holi_search_staging
+
+# Facebook Tracking
+export FACEBOOK_APP_ID=655863986950215
+export FACEBOOK_CLIENT_TOKEN="095ea0ea5f60749ee3fb10bec46da095"
+export FACEBOOK_APP_DISPLAY_NAME="holi (dev)"
\ No newline at end of file
diff --git a/.env.production b/.env.production
index 1dd0264884..314e978a4c 100644
--- a/.env.production
+++ b/.env.production
@@ -50,3 +50,8 @@ export TYPESENSE_PORT=443
 export TYPESENSE_PROTOCOL=https
 export TYPESENSE_API_KEY=jtOdwRfhEFW7UmCpDKjnQaS71uU28YOq
 export TYPESENSE_COLLECTION=holi_search_production
+
+# Facebook Tracking
+export FACEBOOK_APP_ID=655863986950215
+export FACEBOOK_CLIENT_TOKEN="095ea0ea5f60749ee3fb10bec46da095"
+export FACEBOOK_APP_DISPLAY_NAME="holi"
\ No newline at end of file
diff --git a/.env.staging b/.env.staging
index 36f11ebb7c..21f56be775 100644
--- a/.env.staging
+++ b/.env.staging
@@ -50,3 +50,8 @@ export TYPESENSE_PORT=443
 export TYPESENSE_PROTOCOL=https
 export TYPESENSE_API_KEY=WAxFP8Gv9RrNvG18RhYgXCNNj39LfAu2
 export TYPESENSE_COLLECTION=holi_search_staging
+
+# Facebook Tracking
+export FACEBOOK_APP_ID=655863986950215
+export FACEBOOK_CLIENT_TOKEN="095ea0ea5f60749ee3fb10bec46da095"
+export FACEBOOK_APP_DISPLAY_NAME="holi (dev)"
diff --git a/apps/mobile/app.config.ts b/apps/mobile/app.config.ts
index 0b9a58a954..d8d9e7ecbe 100644
--- a/apps/mobile/app.config.ts
+++ b/apps/mobile/app.config.ts
@@ -143,6 +143,7 @@ const config: ExpoConfig = {
     softwareKeyboardLayoutMode: 'pan',
   },
   plugins: [
+    'expo-tracking-transparency',
     'expo-localization',
     'expo-secure-store',
     [
@@ -172,6 +173,18 @@ const config: ExpoConfig = {
         calendarPermission: 'Allow access to your calendar',
       },
     ],
+    [
+      'react-native-fbsdk-next',
+      {
+        appID: requiredEnvVar('FACEBOOK_APP_ID'),
+        displayName: process.env.FACEBOOK_APP_DISPLAY_NAME ?? 'holi',
+        clientToken: requiredEnvVar('FACEBOOK_CLIENT_TOKEN'),
+        scheme: `fb${requiredEnvVar('FACEBOOK_APP_ID')}`,
+        autoLogAppEventsEnabled: false,
+        isAutoInitEnabled: false,
+        advertiserIDCollectionEnabled: false,
+      },
+    ],
     // HOLI-9792: Create a new development build to bundle fonts
     // [
     //   'expo-font',
diff --git a/apps/mobile/eas.json b/apps/mobile/eas.json
index f739b79f89..c2b7a42460 100644
--- a/apps/mobile/eas.json
+++ b/apps/mobile/eas.json
@@ -17,6 +17,8 @@
         "APP_VARIANT": "development",
         "E2E_USER_ID": "5dbeb15e-1fac-4292-96fc-9fbe2e6edbb0",
         "ENVIRONMENT_ID": "staging",
+        "FACEBOOK_APP_DISPLAY_NAME": "holi (dev)",
+        "FACEBOOK_APP_ID": "655863986950215",
         "HOLI_API_GATEWAY": "https://staging.unified.apis.holi.social",
         "HOLI_API_URL": "https://staging.unified.apis.holi.social/graphql",
         "HOLI_CHAT_SERVER_NAME": "development-chat.holi.social",
@@ -50,6 +52,8 @@
         "APP_VARIANT": "development",
         "E2E_USER_ID": "5dbeb15e-1fac-4292-96fc-9fbe2e6edbb0",
         "ENVIRONMENT_ID": "staging",
+        "FACEBOOK_APP_DISPLAY_NAME": "holi (dev)",
+        "FACEBOOK_APP_ID": "655863986950215",
         "HOLI_API_GATEWAY": "https://staging.unified.apis.holi.social",
         "HOLI_API_URL": "https://staging.unified.apis.holi.social/graphql",
         "HOLI_CHAT_SERVER_NAME": "development-chat.holi.social",
@@ -86,6 +90,8 @@
         "APP_VARIANT": "development",
         "E2E_USER_ID": "5dbeb15e-1fac-4292-96fc-9fbe2e6edbb0",
         "ENVIRONMENT_ID": "staging",
+        "FACEBOOK_APP_DISPLAY_NAME": "holi (staging)",
+        "FACEBOOK_APP_ID": "655863986950215",
         "HOLI_API_GATEWAY": "https://staging.unified.apis.holi.social",
         "HOLI_API_URL": "https://staging.unified.apis.holi.social/graphql",
         "HOLI_CHAT_SERVER_NAME": "development-chat.holi.social",
@@ -117,6 +123,8 @@
       "env": {
         "APP_VARIANT": "production",
         "ENVIRONMENT_ID": "production",
+        "FACEBOOK_APP_DISPLAY_NAME": "holi",
+        "FACEBOOK_APP_ID": "655863986950215",
         "HOLI_API_GATEWAY": "https://production.unified.apis.holi.social",
         "HOLI_API_URL": "https://production.unified.apis.holi.social/graphql",
         "HOLI_CHAT_SERVER_NAME": "holi.social",
@@ -155,6 +163,8 @@
         "APP_VARIANT": "development",
         "E2E_USER_ID": "5dbeb15e-1fac-4292-96fc-9fbe2e6edbb0",
         "ENVIRONMENT_ID": "staging",
+        "FACEBOOK_APP_DISPLAY_NAME": "holi (dev)",
+        "FACEBOOK_APP_ID": "655863986950215",
         "HOLI_API_GATEWAY": "https://staging.unified.apis.holi.social",
         "HOLI_API_URL": "https://staging.unified.apis.holi.social/graphql",
         "HOLI_CHAT_SERVER_NAME": "development-chat.holi.social",
diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index a2c2281edf..9ed7bff44b 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -42,6 +42,7 @@
     "expo-splash-screen": "~0.27.6",
     "expo-status-bar": "~1.12.1",
     "expo-task-manager": "~11.8.2",
+    "expo-tracking-transparency": "~4.0.2",
     "expo-updates": "~0.25.27",
     "fast-text-encoding": "^1.0.6",
     "global": "^4.4.0",
@@ -63,6 +64,7 @@
     "react-native-animated-pagination-dot": "^0.4.0",
     "react-native-calendars": "^1.1306.0",
     "react-native-controlled-mentions": "^2.2.5",
+    "react-native-fbsdk-next": "^13.4.1",
     "react-native-gesture-handler": "~2.16.1",
     "react-native-get-random-values": "^1.11.0",
     "react-native-image-crop-picker": "^0.41.2",
diff --git a/core/components/TrackingConsentModal/ManageOptionsContent.tsx b/core/components/TrackingConsentModal/ManageOptionsContent.tsx
index c0e7fdffa9..e9c115fe36 100644
--- a/core/components/TrackingConsentModal/ManageOptionsContent.tsx
+++ b/core/components/TrackingConsentModal/ManageOptionsContent.tsx
@@ -4,7 +4,7 @@ import { StyleSheet, View } from 'react-native'
 
 import OptionSection from '@holi/core/components/OptionSection'
 import { HoliGap } from '@holi/ui/components/atoms/HoliGap'
-import { HoliTheme, useTheme } from '@holi/ui/styles/theme'
+import { type HoliTheme, useTheme } from '@holi/ui/styles/theme'
 import { Text } from 'holi-bricks/components/text'
 
 interface Props {
@@ -12,6 +12,8 @@ interface Props {
   onTogglePersonalization: () => void
   allowProductAnalytics: boolean
   onToggleProductAnalytics: () => void
+  allowAdPartnerAnalytics: boolean
+  onToggleAdPartnerAnalytics: () => void
 }
 
 const ManageOptionsContent = ({
@@ -19,6 +21,8 @@ const ManageOptionsContent = ({
   onTogglePersonalization,
   allowProductAnalytics,
   onToggleProductAnalytics,
+  allowAdPartnerAnalytics,
+  onToggleAdPartnerAnalytics,
 }: Props) => {
   const { t } = useTranslation()
 
@@ -47,6 +51,17 @@ const ManageOptionsContent = ({
         />
       </View>
 
+      <HoliGap size="sm" />
+
+      <View style={styles.optionBox}>
+        <OptionSection
+          title={t('tracking.consentModal.manageOptions.adPartnerAnalyticsBox.title')}
+          description={t('tracking.consentModal.manageOptions.adPartnerAnalyticsBox.description')}
+          value={allowAdPartnerAnalytics}
+          onToggle={onToggleAdPartnerAnalytics}
+        />
+      </View>
+
       <HoliGap size="m" />
 
       <Text size="md">{t('tracking.consentModal.manageOptions.note')}</Text>
diff --git a/core/components/TrackingConsentModal/TrackingConsentModalDesktop.tsx b/core/components/TrackingConsentModal/TrackingConsentModalDesktop.tsx
index c2282bf04e..25e428e172 100644
--- a/core/components/TrackingConsentModal/TrackingConsentModalDesktop.tsx
+++ b/core/components/TrackingConsentModal/TrackingConsentModalDesktop.tsx
@@ -26,8 +26,10 @@ export const TrackingConsentModalDesktop = () => {
   const {
     allowPersonalization,
     allowProductAnalytics,
+    allowAdPartnerAnalytics,
     onTogglePersonalization,
     onToggleProductAnalytics,
+    onToggleAdPartnerAnalytics,
     acceptAll,
     denyAll,
     acceptMyChoices,
@@ -46,6 +48,8 @@ export const TrackingConsentModalDesktop = () => {
             onTogglePersonalization={onTogglePersonalization}
             allowProductAnalytics={allowProductAnalytics}
             onToggleProductAnalytics={onToggleProductAnalytics}
+            allowAdPartnerAnalytics={allowAdPartnerAnalytics}
+            onToggleAdPartnerAnalytics={onToggleAdPartnerAnalytics}
           />
         </HoliBox>
       </ScrollView>
diff --git a/core/components/TrackingConsentModal/TrackingConsentModalMobile.tsx b/core/components/TrackingConsentModal/TrackingConsentModalMobile.tsx
index 0d084c0389..9d42ab2498 100644
--- a/core/components/TrackingConsentModal/TrackingConsentModalMobile.tsx
+++ b/core/components/TrackingConsentModal/TrackingConsentModalMobile.tsx
@@ -42,8 +42,10 @@ export const TrackingConsentModalMobile = () => {
   const {
     allowPersonalization,
     allowProductAnalytics,
+    allowAdPartnerAnalytics,
     onTogglePersonalization,
     onToggleProductAnalytics,
+    onToggleAdPartnerAnalytics,
     acceptAll,
     denyAll,
     acceptMyChoices,
@@ -99,6 +101,8 @@ export const TrackingConsentModalMobile = () => {
               onTogglePersonalization={onTogglePersonalization}
               allowProductAnalytics={allowProductAnalytics}
               onToggleProductAnalytics={onToggleProductAnalytics}
+              allowAdPartnerAnalytics={allowAdPartnerAnalytics}
+              onToggleAdPartnerAnalytics={onToggleAdPartnerAnalytics}
             />
 
             <HoliGap size="ml" />
diff --git a/core/components/TrackingConsentModal/__tests__/TrackingConsentModalDesktop.test.tsx b/core/components/TrackingConsentModal/__tests__/TrackingConsentModalDesktop.test.tsx
index e818f012e4..bd34cdbd1e 100644
--- a/core/components/TrackingConsentModal/__tests__/TrackingConsentModalDesktop.test.tsx
+++ b/core/components/TrackingConsentModal/__tests__/TrackingConsentModalDesktop.test.tsx
@@ -42,10 +42,12 @@ describe('TrackingConsentModalDesktop', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Given,
         [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Given,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_accepted_all')
     })
   })
+
   describe('when denying tracking for all purposes', () => {
     it('updates consent state and calls hooks', async () => {
       const user = userEvent.setup()
@@ -60,12 +62,14 @@ describe('TrackingConsentModalDesktop', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Denied,
         [TrackingPurpose.Personalization]: Consent.Denied,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_denied_all')
     })
   })
+
   describe('when confirming tracking choices', () => {
-    it('[given personalization + denied analytics] updates consent state and calls hooks', async () => {
+    it('[given personalization + denied analytics + denied ad partners] updates consent state and calls hooks', async () => {
       const user = userEvent.setup()
 
       render(<TrackingConsentModalDesktop />)
@@ -79,10 +83,12 @@ describe('TrackingConsentModalDesktop', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Denied,
         [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_accepted_personalization')
     })
-    it('[denied personalization + given analytics] updates consent state and calls hooks', async () => {
+
+    it('[denied personalization + given analytics + denied ad partners] updates consent state and calls hooks', async () => {
       const user = userEvent.setup()
 
       render(<TrackingConsentModalDesktop />)
@@ -96,10 +102,12 @@ describe('TrackingConsentModalDesktop', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Given,
         [TrackingPurpose.Personalization]: Consent.Denied,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_accepted_analytics')
     })
-    it('[given personalization + given analytics] updates consent state and calls hooks', async () => {
+
+    it('[given personalization + given analytics + denied ad partners] updates consent state and calls hooks', async () => {
       const user = userEvent.setup()
 
       render(<TrackingConsentModalDesktop />)
@@ -114,10 +122,35 @@ describe('TrackingConsentModalDesktop', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Given,
         [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_accepted_analytics_personalization')
     })
-    it('[denied personalization + denied analytics] updates consent state and calls hooks', async () => {
+
+    it('[given personalization + given analytics + given ad partners] updates consent state and calls hooks', async () => {
+      const user = userEvent.setup()
+
+      render(<TrackingConsentModalDesktop />)
+      act(jest.runAllTimers)
+
+      expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_shown')
+
+      await user.press(screen.getByText('tracking.consentModal.manageOptions.personalizationBox.title'))
+      await user.press(screen.getByText('tracking.consentModal.manageOptions.productAnalyticsBox.title'))
+      await user.press(screen.getByText('tracking.consentModal.manageOptions.adPartnerAnalyticsBox.title'))
+      await user.press(screen.getByLabelText('tracking.consentModal.manageOptions.button.confirmMyChoices'))
+
+      expect(mockUpdateConsentState).toHaveBeenCalledWith({
+        [TrackingPurpose.Analytics]: Consent.Given,
+        [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Given,
+      })
+      expect(mockAnonCount).toHaveBeenCalledWith(
+        'tracking_consent_modal_options_accepted_analytics_personalization_ad_partners'
+      )
+    })
+
+    it('[denied personalization + denied analytics + denied ad partners] updates consent state and calls hooks', async () => {
       const user = userEvent.setup()
 
       render(<TrackingConsentModalDesktop />)
@@ -130,6 +163,7 @@ describe('TrackingConsentModalDesktop', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Denied,
         [TrackingPurpose.Personalization]: Consent.Denied,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_denied_all')
     })
diff --git a/core/components/TrackingConsentModal/__tests__/TrackingConsentModalMobile.test.tsx b/core/components/TrackingConsentModal/__tests__/TrackingConsentModalMobile.test.tsx
index 468893f248..65b87b1b7b 100644
--- a/core/components/TrackingConsentModal/__tests__/TrackingConsentModalMobile.test.tsx
+++ b/core/components/TrackingConsentModal/__tests__/TrackingConsentModalMobile.test.tsx
@@ -42,6 +42,7 @@ describe('TrackingConsentModalMobile', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Given,
         [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Given,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_accepted_all')
     })
@@ -60,12 +61,13 @@ describe('TrackingConsentModalMobile', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Denied,
         [TrackingPurpose.Personalization]: Consent.Denied,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_denied_all')
     })
   })
   describe('when confirming tracking choices', () => {
-    it('[given personalization + denied analytics] updates consent state and calls hooks', async () => {
+    it('[given personalization + denied analytics + denied ad partners] updates consent state and calls hooks', async () => {
       const user = userEvent.setup()
 
       render(<TrackingConsentModalMobile />)
@@ -82,10 +84,12 @@ describe('TrackingConsentModalMobile', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Denied,
         [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_accepted_personalization')
     })
-    it('[denied personalization + given analytics] updates consent state and calls hooks', async () => {
+
+    it('[denied personalization + given analytics + denied ad partners] updates consent state and calls hooks', async () => {
       const user = userEvent.setup()
 
       render(<TrackingConsentModalMobile />)
@@ -102,10 +106,12 @@ describe('TrackingConsentModalMobile', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Given,
         [TrackingPurpose.Personalization]: Consent.Denied,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_accepted_analytics')
     })
-    it('[given personalization + given analytics] updates consent state and calls hooks', async () => {
+
+    it('[given personalization + given analytics + denied ad partners] updates consent state and calls hooks', async () => {
       const user = userEvent.setup()
 
       render(<TrackingConsentModalMobile />)
@@ -123,10 +129,12 @@ describe('TrackingConsentModalMobile', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Given,
         [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_accepted_analytics_personalization')
     })
-    it('[denied personalization + denied analytics] updates consent state and calls hooks', async () => {
+
+    it('[denied personalization + denied analytics + denied ad partners] updates consent state and calls hooks', async () => {
       const user = userEvent.setup()
 
       render(<TrackingConsentModalMobile />)
@@ -142,8 +150,37 @@ describe('TrackingConsentModalMobile', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Denied,
         [TrackingPurpose.Personalization]: Consent.Denied,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
+      })
+      expect(mockAnonCount).toHaveBeenCalledWith(
+        'tracking_consent_modal_options_denied_analytics_personalization_ad_partners'
+      )
+    })
+
+    it('[given personalization + given analytics + given ad partners] updates consent state and calls hooks', async () => {
+      const user = userEvent.setup()
+
+      render(<TrackingConsentModalMobile />)
+      act(jest.runAllTimers)
+
+      expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_shown')
+
+      await user.press(screen.getByLabelText('tracking.consentModal.general.button.manageOptions'))
+      act(jest.runAllTimers)
+
+      await user.press(screen.getByText('tracking.consentModal.manageOptions.personalizationBox.title'))
+      await user.press(screen.getByText('tracking.consentModal.manageOptions.productAnalyticsBox.title'))
+      await user.press(screen.getByText('tracking.consentModal.manageOptions.adPartnerAnalyticsBox.title'))
+      await user.press(screen.getByLabelText('tracking.consentModal.manageOptions.button.confirmMyChoices'))
+
+      expect(mockUpdateConsentState).toHaveBeenCalledWith({
+        [TrackingPurpose.Analytics]: Consent.Given,
+        [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Given,
       })
-      expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_denied_analytics_personalization')
+      expect(mockAnonCount).toHaveBeenCalledWith(
+        'tracking_consent_modal_options_accepted_analytics_personalization_ad_partners'
+      )
     })
   })
 })
diff --git a/core/components/TrackingConsentModal/__tests__/useConsentModal.test.ts b/core/components/TrackingConsentModal/__tests__/useConsentModal.test.ts
index ebd5a16798..418a25aedb 100644
--- a/core/components/TrackingConsentModal/__tests__/useConsentModal.test.ts
+++ b/core/components/TrackingConsentModal/__tests__/useConsentModal.test.ts
@@ -24,6 +24,7 @@ const mockUseModalState = jest.mocked(useModalState)
 const randomConsent = () => (Math.random() > 0.5 ? Consent.Given : Consent.Denied)
 const randomKnownConsentState = () => ({
   [TrackingPurpose.Analytics]: randomConsent(),
+  [TrackingPurpose.AdPartners]: randomConsent(),
   [TrackingPurpose.Personalization]: randomConsent(),
 })
 
diff --git a/core/components/TrackingConsentModal/__tests__/useConsentModalState.test.ts b/core/components/TrackingConsentModal/__tests__/useConsentModalState.test.ts
index c1d2028629..2ebbae3e36 100644
--- a/core/components/TrackingConsentModal/__tests__/useConsentModalState.test.ts
+++ b/core/components/TrackingConsentModal/__tests__/useConsentModalState.test.ts
@@ -25,8 +25,11 @@ describe('useConsentModalState', () => {
     expect(mockUpdateConsentState).toHaveBeenCalledWith({
       [TrackingPurpose.Analytics]: Consent.Denied,
       [TrackingPurpose.Personalization]: Consent.Denied,
+      [TrackingPurpose.AdPartners]: Consent.Denied,
     })
-    expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_denied_analytics_personalization')
+    expect(mockAnonCount).toHaveBeenCalledWith(
+      'tracking_consent_modal_options_denied_analytics_personalization_ad_partners'
+    )
     expect(onFinish).toHaveBeenCalled()
   })
   describe('when accepting tracking for all purposes', () => {
@@ -38,6 +41,7 @@ describe('useConsentModalState', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Given,
         [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Given,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_accepted_all')
       expect(onFinish).toHaveBeenCalled()
@@ -52,11 +56,13 @@ describe('useConsentModalState', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Denied,
         [TrackingPurpose.Personalization]: Consent.Denied,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_denied_all')
       expect(onFinish).toHaveBeenCalled()
     })
   })
+
   describe('when confirming tracking choices', () => {
     it('[personalization only] updates consent state and calls hooks', async () => {
       const { result } = renderHook(() => useConsentModalState(onFinish))
@@ -67,10 +73,12 @@ describe('useConsentModalState', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Denied,
         [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_accepted_personalization')
       expect(onFinish).toHaveBeenCalled()
     })
+
     it('[analytics only] updates consent state and calls hooks', async () => {
       const { result } = renderHook(() => useConsentModalState(onFinish))
 
@@ -80,10 +88,25 @@ describe('useConsentModalState', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Given,
         [TrackingPurpose.Personalization]: Consent.Denied,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_accepted_analytics')
       expect(onFinish).toHaveBeenCalled()
     })
+
+    it('[ad partners only] updates consent state and calls hooks', async () => {
+      const { result } = renderHook(() => useConsentModalState(onFinish))
+
+      act(() => result.current.onToggleAdPartnerAnalytics())
+      act(() => result.current.acceptMyChoices())
+
+      expect(mockUpdateConsentState).toHaveBeenCalledWith({
+        [TrackingPurpose.Analytics]: Consent.Denied,
+        [TrackingPurpose.Personalization]: Consent.Denied,
+        [TrackingPurpose.AdPartners]: Consent.Given,
+      })
+    })
+
     it('[personalization + analytics] updates consent state and calls hooks', async () => {
       const { result } = renderHook(() => useConsentModalState(onFinish))
 
@@ -94,9 +117,60 @@ describe('useConsentModalState', () => {
       expect(mockUpdateConsentState).toHaveBeenCalledWith({
         [TrackingPurpose.Analytics]: Consent.Given,
         [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Denied,
       })
       expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_accepted_analytics_personalization')
       expect(onFinish).toHaveBeenCalled()
     })
+
+    it('[personalization + ad partners] updates consent state and calls hooks', async () => {
+      const { result } = renderHook(() => useConsentModalState(onFinish))
+
+      act(() => result.current.onTogglePersonalization())
+      act(() => result.current.onToggleAdPartnerAnalytics())
+      act(() => result.current.acceptMyChoices())
+
+      expect(mockUpdateConsentState).toHaveBeenCalledWith({
+        [TrackingPurpose.Analytics]: Consent.Denied,
+        [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Given,
+      })
+      expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_accepted_personalization_ad_partners')
+      expect(onFinish).toHaveBeenCalled()
+    })
+    it('[analytics + ad partners] updates consent state and calls hooks', async () => {
+      const { result } = renderHook(() => useConsentModalState(onFinish))
+
+      act(() => result.current.onToggleProductAnalytics())
+      act(() => result.current.onToggleAdPartnerAnalytics())
+      act(() => result.current.acceptMyChoices())
+
+      expect(mockUpdateConsentState).toHaveBeenCalledWith({
+        [TrackingPurpose.Analytics]: Consent.Given,
+        [TrackingPurpose.Personalization]: Consent.Denied,
+        [TrackingPurpose.AdPartners]: Consent.Given,
+      })
+      expect(mockAnonCount).toHaveBeenCalledWith('tracking_consent_modal_options_accepted_analytics_ad_partners')
+      expect(onFinish).toHaveBeenCalled()
+    })
+
+    it('[personalization + analytics + ad partners] updates consent state and calls hooks', async () => {
+      const { result } = renderHook(() => useConsentModalState(onFinish))
+
+      act(() => result.current.onTogglePersonalization())
+      act(() => result.current.onToggleProductAnalytics())
+      act(() => result.current.onToggleAdPartnerAnalytics())
+      act(() => result.current.acceptMyChoices())
+
+      expect(mockUpdateConsentState).toHaveBeenCalledWith({
+        [TrackingPurpose.Analytics]: Consent.Given,
+        [TrackingPurpose.Personalization]: Consent.Given,
+        [TrackingPurpose.AdPartners]: Consent.Given,
+      })
+      expect(mockAnonCount).toHaveBeenCalledWith(
+        'tracking_consent_modal_options_accepted_analytics_personalization_ad_partners'
+      )
+      expect(onFinish).toHaveBeenCalled()
+    })
   })
 })
diff --git a/core/components/TrackingConsentModal/useConsentModalState.ts b/core/components/TrackingConsentModal/useConsentModalState.ts
index 01ea7e1f64..58c52c019b 100644
--- a/core/components/TrackingConsentModal/useConsentModalState.ts
+++ b/core/components/TrackingConsentModal/useConsentModalState.ts
@@ -5,9 +5,10 @@ import { Consent, TrackingPurpose } from '@holi/core/tracking/types'
 import { useAnonCount } from '@holi/core/domain/shared/hooks'
 
 export const useConsentModalState = (onFinish: () => void) => {
-  const [{ allowPersonalization, allowProductAnalytics }, setState] = useState({
+  const [{ allowPersonalization, allowProductAnalytics, allowAdPartnerAnalytics }, setState] = useState({
     allowPersonalization: false,
     allowProductAnalytics: false,
+    allowAdPartnerAnalytics: false,
   })
   const anonCount = useAnonCount()
 
@@ -23,22 +24,30 @@ export const useConsentModalState = (onFinish: () => void) => {
       allowProductAnalytics: !allowProductAnalytics,
     }))
 
+  const onToggleAdPartnerAnalytics = () =>
+    setState((prevState) => ({
+      ...prevState,
+      allowAdPartnerAnalytics: !allowAdPartnerAnalytics,
+    }))
+
   // Event handlers
   const acceptAll = () => {
-    setState({ allowPersonalization: true, allowProductAnalytics: true })
+    setState({ allowPersonalization: true, allowProductAnalytics: true, allowAdPartnerAnalytics: true })
     updateConsentState({
       [TrackingPurpose.Analytics]: Consent.Given,
       [TrackingPurpose.Personalization]: Consent.Given,
+      [TrackingPurpose.AdPartners]: Consent.Given,
     })
     anonCount('tracking_consent_modal_accepted_all')
     onFinish()
   }
 
   const denyAll = () => {
-    setState({ allowPersonalization: false, allowProductAnalytics: false })
+    setState({ allowPersonalization: false, allowProductAnalytics: false, allowAdPartnerAnalytics: true })
     updateConsentState({
       [TrackingPurpose.Analytics]: Consent.Denied,
       [TrackingPurpose.Personalization]: Consent.Denied,
+      [TrackingPurpose.AdPartners]: Consent.Denied,
     })
     anonCount('tracking_consent_modal_denied_all')
     onFinish()
@@ -48,16 +57,25 @@ export const useConsentModalState = (onFinish: () => void) => {
     const cs = {
       [TrackingPurpose.Analytics]: allowProductAnalytics ? Consent.Given : Consent.Denied,
       [TrackingPurpose.Personalization]: allowPersonalization ? Consent.Given : Consent.Denied,
+      [TrackingPurpose.AdPartners]: allowAdPartnerAnalytics ? Consent.Given : Consent.Denied,
     }
     updateConsentState(cs)
-    if (allowPersonalization && allowProductAnalytics) {
+    if (allowPersonalization && allowProductAnalytics && allowAdPartnerAnalytics) {
+      anonCount('tracking_consent_modal_options_accepted_analytics_personalization_ad_partners')
+    } else if (allowPersonalization && allowProductAnalytics) {
       anonCount('tracking_consent_modal_options_accepted_analytics_personalization')
+    } else if (allowPersonalization && allowAdPartnerAnalytics) {
+      anonCount('tracking_consent_modal_options_accepted_personalization_ad_partners')
+    } else if (allowProductAnalytics && allowAdPartnerAnalytics) {
+      anonCount('tracking_consent_modal_options_accepted_analytics_ad_partners')
     } else if (allowProductAnalytics) {
       anonCount('tracking_consent_modal_options_accepted_analytics')
     } else if (allowPersonalization) {
       anonCount('tracking_consent_modal_options_accepted_personalization')
+    } else if (allowAdPartnerAnalytics) {
+      anonCount('tracking_consent_modal_options_accepted_ad_partners')
     } else {
-      anonCount('tracking_consent_modal_options_denied_analytics_personalization')
+      anonCount('tracking_consent_modal_options_denied_analytics_personalization_ad_partners')
     }
     onFinish()
   }
@@ -65,8 +83,10 @@ export const useConsentModalState = (onFinish: () => void) => {
   return {
     allowPersonalization,
     allowProductAnalytics,
+    allowAdPartnerAnalytics,
     onTogglePersonalization,
     onToggleProductAnalytics,
+    onToggleAdPartnerAnalytics,
     acceptAll,
     denyAll,
     acceptMyChoices,
diff --git a/core/domain/shared/__tests__/useSortedUserTerms.test.ts b/core/domain/shared/__tests__/useSortedUserTerms.test.ts
index 6573c79316..3cd4243c32 100644
--- a/core/domain/shared/__tests__/useSortedUserTerms.test.ts
+++ b/core/domain/shared/__tests__/useSortedUserTerms.test.ts
@@ -17,6 +17,7 @@ describe('useSortedUserTerms', () => {
       user: {
         isEmployee: false,
         id: '',
+        email: '',
         identity: '',
         avatarDefaultColor: '',
         avatarLabel: '',
diff --git a/core/domain/shared/queries.ts b/core/domain/shared/queries.ts
index 92e4edf640..db1b6a13ed 100644
--- a/core/domain/shared/queries.ts
+++ b/core/domain/shared/queries.ts
@@ -73,7 +73,9 @@ export const FullAuthenticatedUserFragment = gql`
     connectionStatusToMyself
     trackingConsentAnalytics
     trackingConsentPersonalization
+    trackingConsentAdPartners
     isEmployee
+    email
   }
 `
 export const authenticatedUserV2Query = gql`
diff --git a/core/domain/shared/types.ts b/core/domain/shared/types.ts
index 7d06ef0649..be8509a85c 100644
--- a/core/domain/shared/types.ts
+++ b/core/domain/shared/types.ts
@@ -177,7 +177,9 @@ export interface User {
 }
 
 export interface AuthenticatedUser extends User {
+  email: string
   trackingConsentAnalytics?: boolean | null
   trackingConsentPersonalization?: boolean | null
+  trackingConsentAdPartners?: boolean | null
   isEmployee: boolean
 }
diff --git a/core/i18n/locales/de.json b/core/i18n/locales/de.json
index 42e52b715a..f6cb810f9e 100644
--- a/core/i18n/locales/de.json
+++ b/core/i18n/locales/de.json
@@ -723,6 +723,7 @@
   "profile.settings.resetOnboardingState.title": "Onboarding-Status zurücksetzen",
   "profile.settings.tracking.dataManagement.description": "Du kannst deine Daten aus unseren Systemen löschen, indem du eine E-Mail an <0>support@holi.social</0> sendest",
   "profile.settings.tracking.dataManagement.title": "Datenmanagement",
+  "profile.settings.tracking.purpose.adPartners": "Werbepartneranalysen erlauben",
   "profile.settings.tracking.purpose.all": "Für alle Zwecke erlauben",
   "profile.settings.tracking.purpose.analytics": "Produktanalyse erlauben",
   "profile.settings.tracking.purpose.personalization": "Personalisierung erlauben",
@@ -1555,6 +1556,8 @@
   "tracking.consentModal.general.description.check_2": "Hilfst du uns das Nutzungserlebnis zu verbessern",
   "tracking.consentModal.general.description.check_3": "Deine Zustimmung kannst du in den Einstellungen jederzeit ändern oder widerrufen",
   "tracking.consentModal.general.headline": "Deine Daten sind bei uns sicher. Was möchtest du teilen?",
+  "tracking.consentModal.manageOptions.adPartnerAnalyticsBox.description": "Hilf uns zu Wachsen! Um möglichst passende Menschen auf externen Seiten und Netzwerken (wie Facebook oder Instragram) mit holi Werbung ansprechen zu können, analysieren wir mit deiner Zustimmung dein Nutzungsverhalten und deine Interaktionen. Mit Hilfe des daraus abgeleiteten Nutzungsprofils können wir dich in passende Zielgruppen und Werbekategorien einordnen, die verwendet werden, um unsere Werbung auf den externen Seiten zielgerichteter auszuspielen. Daher freuen wir uns, wenn du uns mit deinen Daten dabei hilfst, holi wachsen zu lassen.",
+  "tracking.consentModal.manageOptions.adPartnerAnalyticsBox.title": "Werbepartneranalysen erlauben",
   "tracking.consentModal.manageOptions.button.acceptAll": "Gesamte Verarbeitung akzeptieren",
   "tracking.consentModal.manageOptions.button.confirmMyChoices": "Auswahl bestätigen",
   "tracking.consentModal.manageOptions.headline": "Optionen anpassen",
diff --git a/core/i18n/locales/en.json b/core/i18n/locales/en.json
index b3ec15c7bc..cf71898c6c 100644
--- a/core/i18n/locales/en.json
+++ b/core/i18n/locales/en.json
@@ -722,6 +722,7 @@
   "profile.settings.resetOnboardingState.title": "Reset onboarding state",
   "profile.settings.tracking.dataManagement.description": "You can delete your data from our systems by sending an email to <0>support@holi.social</0>",
   "profile.settings.tracking.dataManagement.title": "Data management",
+  "profile.settings.tracking.purpose.adPartners": "Allow advertising partner analyses",
   "profile.settings.tracking.purpose.all": "Allow all purposes",
   "profile.settings.tracking.purpose.analytics": "Allow product analytics",
   "profile.settings.tracking.purpose.personalization": "Allow personalization",
@@ -1551,6 +1552,8 @@
   "topic.sports-recreation": "Sports & Recreation",
   "topic.technology-innovation": "Technology & Innovation",
   "topic.youth-development": "Youth Development",
+  "tracking.consentModal.manageOptions.adPartnerAnalyticsBox.description": "Help us grow! In order to address people on external sites and networks (such as Facebook or Instagram) with holi advertising that are as suitable as possible, we analyse your usage behaviour and interactions with your consent. With the help of the usage profile derived from this, we can classify you into suitable target groups and advertising categories that are used to display our advertising on external sites in a more targeted manner. We would therefore be grateful if you would help us with your data to grow holi.",
+  "tracking.consentModal.manageOptions.adPartnerAnalyticsBox.title": "Allow advertising partner analyses",
   "tracking.consentModal.general.button.acceptAll": "Accept full data processing",
   "tracking.consentModal.general.button.denyAll": "Decline all",
   "tracking.consentModal.general.button.manageOptions": "Manage options",
diff --git a/core/package.json b/core/package.json
index 893d8870b9..eb43de4285 100644
--- a/core/package.json
+++ b/core/package.json
@@ -41,7 +41,9 @@
     "expo-calendar": "^13.0.5",
     "expo-image-picker": "~15.0.7",
     "expo-linking": "~6.3.1",
+    "expo-tracking-transparency": "^5.1.1",
     "graphql-request": "^7.1.0",
+    "react-native-fbsdk-next": "^13.4.1",
     "react-native-reanimated": "~3.10.1",
     "react-native-svg": "15.2.0",
     "react-zoom-pan-pinch": "^2.1.3"
diff --git a/core/screens/onboarding/Onboarding.tsx b/core/screens/onboarding/Onboarding.tsx
index 4820740f36..8530d042c6 100644
--- a/core/screens/onboarding/Onboarding.tsx
+++ b/core/screens/onboarding/Onboarding.tsx
@@ -37,6 +37,7 @@ import { useRedirect } from '@holi/core/navigation/hooks/useRedirect'
 import { clearOnboardingUserDataAsyncStore } from './helpers/onboardingUserDataAsyncStore'
 import { useCallback, useEffect } from 'react'
 import { showOnboardingFinishModal } from '@holi/core/screens/onboarding/hooks/useOnboardingFinishModalState'
+import { useFacebookSdkCrossPlatform } from '@holi/core/tracking/FacebookSdkCrossPlatform'
 
 const Onboarding = () => {
   const { t } = useTranslation()
@@ -44,6 +45,7 @@ const Onboarding = () => {
   const { top: safeAreaTop, bottom: safeAreaBottom } = useSafeAreaInsets()
   const onboardingState = useOnboardingState()
   const { track } = useTracking()
+  const facebook = useFacebookSdkCrossPlatform()
   const { isLoggedIn } = useLoggedInUser()
 
   const [currentStep, setCurrentStep] = useOnboardingCurrentStepKey()
@@ -181,6 +183,7 @@ const Onboarding = () => {
             }}
             onRegister={() => {
               track(SignupStepCompleted({ step: 'emailAndPassword', answer: 'signUp' }))
+              facebook.trackSignUp()
               setCurrentStep('EMAIL_VERIFICATION')
             }}
             testID="onboarding-account-creation"
diff --git a/core/screens/userprofile/TrackingSettings.tsx b/core/screens/userprofile/TrackingSettings.tsx
index 568bf791e5..2fc2ebc4f3 100644
--- a/core/screens/userprofile/TrackingSettings.tsx
+++ b/core/screens/userprofile/TrackingSettings.tsx
@@ -23,13 +23,16 @@ const logger = getLogger('Tracking')
 const updateUserConsentState = ({
   personalization = false,
   productAnalytics = false,
+  adPartnerAnalytics = false,
 }: {
   personalization: boolean | null
   productAnalytics: boolean | null
+  adPartnerAnalytics: boolean | null
 }) => {
   updateConsentState({
     [TrackingPurpose.Personalization]: personalization ? Consent.Given : Consent.Denied,
     [TrackingPurpose.Analytics]: productAnalytics ? Consent.Given : Consent.Denied,
+    [TrackingPurpose.AdPartners]: adPartnerAnalytics ? Consent.Given : Consent.Denied,
   })
 }
 
@@ -53,22 +56,28 @@ const TrackingSettings = () => {
   const {
     [TrackingPurpose.Personalization]: personalizationConsent = Consent.Unknown,
     [TrackingPurpose.Analytics]: productAnalyticsConsent = Consent.Unknown,
+    [TrackingPurpose.AdPartners]: adPartnerAnalyticsConsent = Consent.Unknown,
   } = consentState ?? {}
   const personalization = Consent.toBooleanOrNull(personalizationConsent)
   const productAnalytics = Consent.toBooleanOrNull(productAnalyticsConsent)
+  const adPartnerAnalytics = Consent.toBooleanOrNull(adPartnerAnalyticsConsent)
 
   const onAllPurposesChange = (value: boolean) => {
-    updateUserConsentState({ personalization: value, productAnalytics: value })
+    updateUserConsentState({ personalization: value, productAnalytics: value, adPartnerAnalytics: value })
     logger.debug('TrackingSettings', 'updated personalization and analytics consent')
   }
   const onPersonalizationChange = (value: boolean) => {
-    updateUserConsentState({ personalization: value, productAnalytics })
+    updateUserConsentState({ personalization: value, productAnalytics, adPartnerAnalytics })
     logger.debug('TrackingSettings', 'updated personalization consent')
   }
   const onProductAnalyticsChange = (value: boolean) => {
-    updateUserConsentState({ personalization, productAnalytics: value })
+    updateUserConsentState({ personalization, productAnalytics: value, adPartnerAnalytics })
     logger.debug('TrackingSettings', 'updated analytics consent')
   }
+  const onAdPartnerAnalyticsChange = (value: boolean) => {
+    updateUserConsentState({ personalization, productAnalytics, adPartnerAnalytics: value })
+    logger.debug('TrackingSettings', 'updated ad partner consent')
+  }
 
   return (
     <ScrollView style={{ backgroundColor: colors.background20 }}>
@@ -92,11 +101,12 @@ const TrackingSettings = () => {
                 [
                   [
                     t('profile.settings.tracking.purpose.all'),
-                    !!(personalization && productAnalytics),
+                    !!(personalization && productAnalytics && adPartnerAnalytics),
                     onAllPurposesChange,
                   ],
                   [t('profile.settings.tracking.purpose.personalization'), !!personalization, onPersonalizationChange],
                   [t('profile.settings.tracking.purpose.analytics'), !!productAnalytics, onProductAnalyticsChange],
+                  [t('profile.settings.tracking.purpose.adPartners'), !!adPartnerAnalytics, onAdPartnerAnalyticsChange],
                 ] as [string, boolean, (value: boolean) => void][]
               ).map(([text, value, onChange], idx) => (
                 <View style={[styles.optionBox, idx !== 0 && styles.underlined]} key={idx}>
diff --git a/core/screens/userprofile/components/__tests__/UserTermsRow.test.tsx b/core/screens/userprofile/components/__tests__/UserTermsRow.test.tsx
index 33ac995287..467543803a 100644
--- a/core/screens/userprofile/components/__tests__/UserTermsRow.test.tsx
+++ b/core/screens/userprofile/components/__tests__/UserTermsRow.test.tsx
@@ -18,6 +18,7 @@ describe('UserTermsRow', () => {
       user: {
         isEmployee: false,
         id: '',
+        email: '',
         identity: '',
         avatarDefaultColor: '',
         avatarLabel: '',
diff --git a/core/screens/userprofile/queries.ts b/core/screens/userprofile/queries.ts
index 4f75263120..a413023934 100644
--- a/core/screens/userprofile/queries.ts
+++ b/core/screens/userprofile/queries.ts
@@ -24,6 +24,7 @@ export const UpdateAuthenticatedUserInputSchema = z.object({
   skillsV2: z.array(z.string()).optional(),
   trackingConsentAnalytics: z.boolean().optional().nullable(),
   trackingConsentPersonalization: z.boolean().optional().nullable(),
+  trackingConsentAdPartners: z.boolean().optional().nullable(),
 })
 
 export type UpdateAuthenticatedUserInput = z.infer<typeof UpdateAuthenticatedUserInputSchema>
diff --git a/core/tracking/FacebookSdkCrossPlatform.ts b/core/tracking/FacebookSdkCrossPlatform.ts
new file mode 100644
index 0000000000..d63c1d147d
--- /dev/null
+++ b/core/tracking/FacebookSdkCrossPlatform.ts
@@ -0,0 +1,97 @@
+import { AppEventsLogger, Settings } from 'react-native-fbsdk-next'
+import { Consent, type ConsentState, TrackingPurpose, type FacebookSdkCrossPlatform } from '@holi/core/tracking/types'
+import {
+  getTrackingPermissionsAsync,
+  type PermissionResponse,
+  PermissionStatus,
+  requestTrackingPermissionsAsync,
+} from 'expo-tracking-transparency'
+import { getConsentState } from '@holi/core/tracking/TrackingInitializer'
+import { getLogger } from '@holi/core/helpers/logging'
+import { AuthenticatedUser } from '@holi/core/domain/shared/types'
+
+export class FacebookSdkReactNative implements FacebookSdkCrossPlatform {
+  private readonly logger = getLogger('FacebookSdk')
+
+  async enabled(): Promise<boolean> {
+    const [trackingPermission, trackingConsent] = await this.getTrackingPermissionStatus()
+
+    return (
+      trackingConsent[TrackingPurpose.AdPartners] === Consent.Given &&
+      trackingPermission.status === PermissionStatus.GRANTED
+    )
+  }
+
+  async initialize(user?: AuthenticatedUser): Promise<void> {
+    const [trackingPermission, trackingConsent] = await this.getTrackingPermissionStatus()
+
+    if (trackingConsent[TrackingPurpose.AdPartners] !== Consent.Given) {
+      // don't do anything if the user has not given us their consent
+      this.logger.debug('initialize', 'ad partner tracking consent not given')
+      return
+    }
+
+    // from here we can assume consent has been given
+
+    if (trackingPermission.status === PermissionStatus.GRANTED) {
+      this.logger.debug('initialize', 'tracking permission and consent granted, initializing SDK')
+      await this.optIn(user)
+      return
+    }
+
+    if (trackingPermission.status === PermissionStatus.UNDETERMINED || trackingPermission.canAskAgain) {
+      this.logger.debug('initialize', 'requesting tracking permission')
+      const { granted } = await requestTrackingPermissionsAsync()
+      if (granted) {
+        this.logger.debug('initialize', 'tracking permission and consent granted, initializing SDK')
+        await this.optIn(user)
+        return
+      }
+    }
+
+    this.logger.debug('initialize', 'Tracking permission not granted')
+  }
+
+  async optIn(user?: AuthenticatedUser): Promise<void> {
+    Settings.initializeSDK()
+
+    Settings.setAdvertiserIDCollectionEnabled(true)
+    Settings.setAutoLogAppEventsEnabled(true)
+    await Settings.setAdvertiserTrackingEnabled(true)
+
+    if (!user) {
+      return
+    }
+
+    AppEventsLogger.setUserData({
+      email: user.email,
+      firstName: user.firstName,
+      lastName: user.lastName,
+    })
+  }
+
+  async optOut(): Promise<void> {
+    Settings.setAdvertiserIDCollectionEnabled(false)
+    Settings.setAutoLogAppEventsEnabled(false)
+    await Settings.setAdvertiserTrackingEnabled(false)
+  }
+
+  async trackSignUp(): Promise<void> {
+    if (!(await this.enabled())) {
+      return
+    }
+
+    AppEventsLogger.logEvent(AppEventsLogger.AppEvents.CompletedRegistration, {
+      action_source: 'app',
+    })
+  }
+
+  private async getTrackingPermissionStatus(): Promise<[PermissionResponse, ConsentState]> {
+    this.logger.debug('getTrackingPermissionStatus', 'getting tracking permission status')
+    return [await getTrackingPermissionsAsync(), getConsentState()]
+  }
+}
+
+export const useFacebookSdkCrossPlatform = (): FacebookSdkCrossPlatform => {
+  return new FacebookSdkReactNative()
+}
diff --git a/core/tracking/FacebookSdkCrossPlatform.web.ts b/core/tracking/FacebookSdkCrossPlatform.web.ts
new file mode 100644
index 0000000000..608a1e2eed
--- /dev/null
+++ b/core/tracking/FacebookSdkCrossPlatform.web.ts
@@ -0,0 +1,15 @@
+import type { FacebookSdkCrossPlatform } from '@holi/core/tracking/types'
+
+export class FacebookSdkWeb implements FacebookSdkCrossPlatform {
+  async initialize(): Promise<void> {}
+  async optIn(): Promise<void> {}
+  async optOut(): Promise<void> {}
+  async trackSignUp(): Promise<void> {}
+  async enabled(): Promise<boolean> {
+    return false
+  }
+}
+
+export const useFacebookSdkCrossPlatform = (): FacebookSdkCrossPlatform => {
+  return new FacebookSdkWeb()
+}
diff --git a/core/tracking/TrackingInitializer.tsx b/core/tracking/TrackingInitializer.tsx
index 0d284d6c2a..ee83587896 100644
--- a/core/tracking/TrackingInitializer.tsx
+++ b/core/tracking/TrackingInitializer.tsx
@@ -19,15 +19,23 @@ import {
 } from '@holi/core/screens/userprofile/queries'
 import { consentStore } from '@holi/core/tracking/ConsentStore'
 import { usePosthogCrossPlatform } from '@holi/core/tracking/PosthogCrossPlatform'
-import { Consent, ConsentState, type PosthogCrossPlatform, TrackingPurpose } from '@holi/core/tracking/types'
+import {
+  Consent,
+  ConsentState,
+  type FacebookSdkCrossPlatform,
+  type PosthogCrossPlatform,
+  TrackingPurpose,
+} from '@holi/core/tracking/types'
 import { TrackingEvent } from '@holi/core/tracking/events'
 import useTracking from '@holi/core/tracking/hooks/useTracking'
+import { useFacebookSdkCrossPlatform } from '@holi/core/tracking/FacebookSdkCrossPlatform'
 
 const logger = getLogger('Tracking')
 
 class Tracking {
   constructor(
     private posthog: PosthogCrossPlatform,
+    private facebook: FacebookSdkCrossPlatform,
     private apollo: ApolloClient<object>
   ) {}
 
@@ -53,6 +61,15 @@ class Tracking {
         await this.posthog.optOut()
         break
     }
+
+    if (consentState[TrackingPurpose.AdPartners] !== Consent.Given) {
+      logger.debug('TrackingInitializer.initWithLoggedInUser', 'ad partner consent not given - opting out')
+      await this.facebook.optOut()
+    } else {
+      // the Facebook SDK wrapper's `initialize` method handles consent and permissions internally
+      logger.debug('TrackingInitializer.initWithLoggedInUser', 'initializing facebook sdk')
+      await this.facebook.initialize(user)
+    }
   }
 
   public async initWithLoggedOutUser(consentState: ConsentState): Promise<void> {
@@ -73,6 +90,14 @@ class Tracking {
         await this.posthog.optOut()
         break
     }
+
+    if (consentState[TrackingPurpose.AdPartners] !== Consent.Given) {
+      logger.debug('TrackingInitializer.initWithLoggedOutUser', 'ad partner consent not given - opting out')
+      await this.facebook.optOut()
+    } else {
+      // the Facebook SDK wrapper's `initialize` method handles consent and permissions internally
+      await this.facebook.initialize()
+    }
   }
 
   public async clearConsentAfterUserLoggedOut(): Promise<void> {
@@ -82,6 +107,8 @@ class Tracking {
     } finally {
       this.posthog.reset()
       logger.debug('TrackingInitializer.clearConsentAfterUserLoggedOut', 'posthog reset')
+      await this.facebook.optOut()
+      logger.debug('TrackingInitializer.clearConsentAfterUserLoggedOut', 'facebook opted out')
     }
   }
 
@@ -118,6 +145,7 @@ class Tracking {
         input: {
           trackingConsentAnalytics: Consent.toBooleanOrNull(consentState[TrackingPurpose.Analytics]),
           trackingConsentPersonalization: Consent.toBooleanOrNull(consentState[TrackingPurpose.Personalization]),
+          trackingConsentAdPartners: Consent.toBooleanOrNull(consentState[TrackingPurpose.AdPartners]),
         },
       },
     })
@@ -243,7 +271,8 @@ const TrackingInitializerInner = ({
 }: TrackingInitializerInnerProps) => {
   const posthog: PosthogCrossPlatform = usePosthogCrossPlatform()
   const apollo: ApolloClient<object> = useApolloClient()
-  const tracking = useMemo(() => new Tracking(posthog, apollo), [posthog, apollo])
+  const facebook: FacebookSdkCrossPlatform = useFacebookSdkCrossPlatform()
+  const tracking = useMemo(() => new Tracking(posthog, facebook, apollo), [posthog, facebook, apollo])
   const state = useReactiveVar(initializerStateVar)
   const { track } = useTracking()
 
diff --git a/core/tracking/__tests__/TrackingInitializer.test.tsx b/core/tracking/__tests__/TrackingInitializer.test.tsx
index 90495100e3..a0facf9177 100644
--- a/core/tracking/__tests__/TrackingInitializer.test.tsx
+++ b/core/tracking/__tests__/TrackingInitializer.test.tsx
@@ -46,6 +46,34 @@ const posthogCrossPlatformMock = {
 } as PosthogCrossPlatform
 const usePosthogCrossPlatformMock = usePosthogCrossPlatform as jest.Mock
 
+jest.mock('expo-tracking-transparency', () => ({
+  PermissionStatus: {
+    GRANTED: 'granted',
+  },
+  getTrackingPermissionsAsync: async () => ({
+    granted: true,
+    expires: 'never',
+    canAskAgain: true,
+    status: 'granted',
+  }),
+}))
+
+jest.mock('react-native-fbsdk-next', () => ({
+  Settings: {
+    initializeSDK: jest.fn(),
+    setAdvertiserIDCollectionEnabled: jest.fn(),
+    setAutoLogAppEventsEnabled: jest.fn(),
+    setAdvertiserTrackingEnabled: jest.fn(),
+  },
+  AppEventsLogger: {
+    setUserData: jest.fn(),
+    logEvent: jest.fn(),
+    AppEvents: {
+      CompletedRegistration: 'SomeThing',
+    },
+  },
+}))
+
 jest.mock('next/router', () => ({
   useRouter: () => ({
     events: {
@@ -61,6 +89,7 @@ const mockUser = (consentState: ConsentState): AuthenticatedUser => {
     id: 'some-user-identity-id',
     trackingConsentAnalytics: Consent.toBooleanOrNull(consentState[TrackingPurpose.Analytics]),
     trackingConsentPersonalization: Consent.toBooleanOrNull(consentState[TrackingPurpose.Personalization]),
+    trackingConsentAdPartners: Consent.toBooleanOrNull(consentState[TrackingPurpose.AdPartners]),
     isEmployee: faker.datatype.boolean(),
   } as AuthenticatedUser
 }
@@ -77,22 +106,14 @@ jest.mock('@react-native-async-storage/async-storage', () => {
 })
 
 const mockLocalStorage = (consentState?: ConsentState): void => {
-  const consentToString = (consent: Consent): string => {
-    switch (consent) {
-      case Consent.Given:
-        return 'given'
-      case Consent.Denied:
-        return 'denied'
-      case Consent.Unknown:
-        return 'unknown'
-    }
-  }
   const key = 'HOLI_TRACKING_CONSENT'
-  const value = consentState
-    ? `{"ANALYTICS":"${consentToString(consentState[TrackingPurpose.Analytics])}","PERSONALIZATION":"${consentToString(
-        consentState[TrackingPurpose.Personalization]
-      )}"}`
-    : null
+  const value =
+    consentState &&
+    JSON.stringify({
+      [TrackingPurpose.Analytics]: consentState[TrackingPurpose.Analytics],
+      [TrackingPurpose.Personalization]: consentState[TrackingPurpose.Personalization],
+      [TrackingPurpose.AdPartners]: consentState[TrackingPurpose.AdPartners],
+    })
   mockStorageGetItem.mockImplementation((getKey) => {
     if (getKey === key) {
       return Promise.resolve(value)
@@ -132,6 +153,7 @@ const mockUpdateAuthenticatedUserResponse = (user: AuthenticatedUser, consentSta
         input: {
           trackingConsentAnalytics: Consent.toBooleanOrNull(consentState[TrackingPurpose.Analytics]),
           trackingConsentPersonalization: Consent.toBooleanOrNull(consentState[TrackingPurpose.Personalization]),
+          trackingConsentAdPartners: Consent.toBooleanOrNull(consentState[TrackingPurpose.AdPartners]),
         },
       },
     },
@@ -141,6 +163,7 @@ const mockUpdateAuthenticatedUserResponse = (user: AuthenticatedUser, consentSta
           ...user,
           trackingConsentAnalytics: Consent.toBooleanOrNull(consentState[TrackingPurpose.Analytics]),
           trackingConsentPersonalization: Consent.toBooleanOrNull(consentState[TrackingPurpose.Personalization]),
+          trackingConsentAdPartners: Consent.toBooleanOrNull(consentState[TrackingPurpose.AdPartners]),
           __typename: 'AuthenticatedUser',
         },
       },
@@ -194,10 +217,12 @@ describe('TrackingInitializer', () => {
         mockLocalStorage({
           [TrackingPurpose.Analytics]: Consent.Denied,
           [TrackingPurpose.Personalization]: Consent.Denied,
+          [TrackingPurpose.AdPartners]: Consent.Denied,
         })
         const user = mockUser({
           [TrackingPurpose.Analytics]: Consent.Given,
           [TrackingPurpose.Personalization]: Consent.Given,
+          [TrackingPurpose.AdPartners]: Consent.Given,
         })
 
         const onUserLoggedOutCompleted = jest.fn()
@@ -247,6 +272,7 @@ describe('TrackingInitializer', () => {
         const expectedConsentState = {
           [TrackingPurpose.Analytics]: Consent.Given,
           [TrackingPurpose.Personalization]: Consent.Denied,
+          [TrackingPurpose.AdPartners]: Consent.Denied,
         }
         mockEnv()
         mockLocalStorage(expectedConsentState)
@@ -273,6 +299,7 @@ describe('TrackingInitializer', () => {
         const expectedConsentState = {
           [TrackingPurpose.Analytics]: Consent.Denied,
           [TrackingPurpose.Personalization]: Consent.Given,
+          [TrackingPurpose.AdPartners]: Consent.Denied,
         }
         mockEnv()
         mockLocalStorage(expectedConsentState)
@@ -299,6 +326,7 @@ describe('TrackingInitializer', () => {
         const expectedConsentState = {
           [TrackingPurpose.Analytics]: Consent.Given,
           [TrackingPurpose.Personalization]: Consent.Given,
+          [TrackingPurpose.AdPartners]: Consent.Denied,
         }
         mockEnv()
         mockLocalStorage(expectedConsentState)
@@ -351,6 +379,7 @@ describe('TrackingInitializer', () => {
         const expectedConsentState = {
           [TrackingPurpose.Analytics]: Consent.Given,
           [TrackingPurpose.Personalization]: Consent.Denied,
+          [TrackingPurpose.AdPartners]: Consent.Denied,
         }
         mockEnv()
         const user = mockUser(expectedConsentState)
@@ -384,6 +413,7 @@ describe('TrackingInitializer', () => {
         const expectedConsentState = {
           [TrackingPurpose.Analytics]: Consent.Denied,
           [TrackingPurpose.Personalization]: Consent.Given,
+          [TrackingPurpose.AdPartners]: Consent.Denied,
         }
         mockEnv()
         const user = mockUser(expectedConsentState)
@@ -411,6 +441,7 @@ describe('TrackingInitializer', () => {
         const expectedConsentState = {
           [TrackingPurpose.Analytics]: Consent.Given,
           [TrackingPurpose.Personalization]: Consent.Given,
+          [TrackingPurpose.AdPartners]: Consent.Denied,
         }
         mockEnv()
         const user = mockUser(expectedConsentState)
@@ -443,10 +474,12 @@ describe('TrackingInitializer', () => {
           const consentState = {
             [TrackingPurpose.Analytics]: Consent.Given,
             [TrackingPurpose.Personalization]: Consent.Given,
+            [TrackingPurpose.AdPartners]: Consent.Denied,
           }
           const updatedConsentState = {
             [TrackingPurpose.Analytics]: Consent.Given,
             [TrackingPurpose.Personalization]: Consent.Denied,
+            [TrackingPurpose.AdPartners]: Consent.Denied,
           }
           mockEnv()
           const user = mockUser(consentState)
@@ -494,10 +527,12 @@ describe('TrackingInitializer', () => {
           const consentState = {
             [TrackingPurpose.Analytics]: Consent.Given,
             [TrackingPurpose.Personalization]: Consent.Given,
+            [TrackingPurpose.AdPartners]: Consent.Denied,
           }
           const updatedConsentState = {
             [TrackingPurpose.Analytics]: Consent.Denied,
             [TrackingPurpose.Personalization]: Consent.Given,
+            [TrackingPurpose.AdPartners]: Consent.Denied,
           }
           mockEnv()
           const user = mockUser(consentState)
@@ -550,10 +585,12 @@ describe('TrackingInitializer', () => {
         const initialConsentState: ConsentState = {
           [TrackingPurpose.Analytics]: Consent.Denied,
           [TrackingPurpose.Personalization]: Consent.Denied,
+          [TrackingPurpose.AdPartners]: Consent.Denied,
         }
         const updatedConsentState: ConsentState = {
           [TrackingPurpose.Analytics]: Consent.Given,
           [TrackingPurpose.Personalization]: Consent.Given,
+          [TrackingPurpose.AdPartners]: Consent.Denied,
         }
         const user = mockUser(initialConsentState)
 
@@ -604,6 +641,7 @@ describe('TrackingInitializer', () => {
         const loggedInConsentState = {
           [TrackingPurpose.Analytics]: Consent.Given,
           [TrackingPurpose.Personalization]: Consent.Unknown,
+          [TrackingPurpose.AdPartners]: Consent.Denied,
         }
         mockEnv()
         mockLocalStorage()
@@ -652,6 +690,7 @@ describe('TrackingInitializer', () => {
           const localStorageConsentState: ConsentState = {
             [TrackingPurpose.Analytics]: Consent.Given,
             [TrackingPurpose.Personalization]: Consent.Denied,
+            [TrackingPurpose.AdPartners]: Consent.Denied,
           }
           mockLocalStorage(localStorageConsentState)
           const user = mockUser(ConsentState.unknown)
@@ -709,10 +748,12 @@ describe('TrackingInitializer', () => {
           const localStorageConsentState = {
             [TrackingPurpose.Analytics]: Consent.Given,
             [TrackingPurpose.Personalization]: Consent.Given,
+            [TrackingPurpose.AdPartners]: Consent.Given,
           }
           const userProfileConsentState = {
             [TrackingPurpose.Analytics]: Consent.Denied,
             [TrackingPurpose.Personalization]: Consent.Denied,
+            [TrackingPurpose.AdPartners]: Consent.Denied,
           }
           mockEnv()
           mockLocalStorage(localStorageConsentState)
@@ -760,10 +801,12 @@ describe('TrackingInitializer', () => {
           const localStorageConsentState = {
             [TrackingPurpose.Analytics]: Consent.Given,
             [TrackingPurpose.Personalization]: Consent.Unknown,
+            [TrackingPurpose.AdPartners]: Consent.Unknown,
           }
           const userProfileConsentState = {
             [TrackingPurpose.Analytics]: Consent.Denied,
             [TrackingPurpose.Personalization]: Consent.Unknown,
+            [TrackingPurpose.AdPartners]: Consent.Unknown,
           }
           mockEnv()
           mockLocalStorage(localStorageConsentState)
@@ -811,10 +854,12 @@ describe('TrackingInitializer', () => {
           const localStorageConsentState = {
             [TrackingPurpose.Analytics]: Consent.Given,
             [TrackingPurpose.Personalization]: Consent.Unknown,
+            [TrackingPurpose.AdPartners]: Consent.Unknown,
           }
           const userProfileConsentState = {
             [TrackingPurpose.Analytics]: Consent.Given,
             [TrackingPurpose.Personalization]: Consent.Denied,
+            [TrackingPurpose.AdPartners]: Consent.Unknown,
           }
           mockEnv()
           mockLocalStorage(localStorageConsentState)
@@ -866,6 +911,7 @@ describe('TrackingInitializer', () => {
         const localStorageConsentState = {
           [TrackingPurpose.Analytics]: Consent.Denied,
           [TrackingPurpose.Personalization]: Consent.Unknown,
+          [TrackingPurpose.AdPartners]: Consent.Unknown,
         }
         mockEnv()
         mockLocalStorage(localStorageConsentState)
@@ -894,6 +940,7 @@ describe('TrackingInitializer', () => {
         const updatedConsentState = {
           [TrackingPurpose.Analytics]: Consent.Given,
           [TrackingPurpose.Personalization]: Consent.Denied,
+          [TrackingPurpose.AdPartners]: Consent.Unknown,
         }
         await act(() => {
           updateConsentState(updatedConsentState)
@@ -904,7 +951,7 @@ describe('TrackingInitializer', () => {
         expect(posthogCrossPlatformMock.identify).not.toHaveBeenCalled()
         expect(mockStorageSetItem).toHaveBeenCalledWith(
           'HOLI_TRACKING_CONSENT',
-          '{"ANALYTICS":"given","PERSONALIZATION":"denied"}'
+          '{"ANALYTICS":"given","PERSONALIZATION":"denied","AD_PARTNERS":"unknown"}'
         )
         const [consentStateAfter] = trackingInitializerTesting.consentStateVar()
         expect(consentStateAfter).toEqual(updatedConsentState)
@@ -917,6 +964,7 @@ describe('TrackingInitializer', () => {
           const localStorageConsentState = {
             [TrackingPurpose.Analytics]: Consent.Denied,
             [TrackingPurpose.Personalization]: Consent.Unknown,
+            [TrackingPurpose.AdPartners]: Consent.Unknown,
           }
           mockLocalStorage(localStorageConsentState)
           const user = mockUser(ConsentState.unknown)
@@ -969,10 +1017,12 @@ describe('TrackingInitializer', () => {
           const localStorageConsentState = {
             [TrackingPurpose.Analytics]: Consent.Denied,
             [TrackingPurpose.Personalization]: Consent.Unknown,
+            [TrackingPurpose.AdPartners]: Consent.Unknown,
           }
           const userProfileConsentState = {
             [TrackingPurpose.Analytics]: Consent.Given,
             [TrackingPurpose.Personalization]: Consent.Unknown,
+            [TrackingPurpose.AdPartners]: Consent.Unknown,
           }
           mockEnv()
           mockLocalStorage(localStorageConsentState)
@@ -1020,10 +1070,12 @@ describe('TrackingInitializer', () => {
           const localStorageConsentState = {
             [TrackingPurpose.Analytics]: Consent.Denied,
             [TrackingPurpose.Personalization]: Consent.Unknown,
+            [TrackingPurpose.AdPartners]: Consent.Unknown,
           }
           const userProfileConsentState = {
             [TrackingPurpose.Analytics]: Consent.Denied,
             [TrackingPurpose.Personalization]: Consent.Denied,
+            [TrackingPurpose.AdPartners]: Consent.Unknown,
           }
           mockEnv()
           mockLocalStorage(localStorageConsentState)
diff --git a/core/tracking/types.ts b/core/tracking/types.ts
index 0ed72c81b3..d653f71218 100644
--- a/core/tracking/types.ts
+++ b/core/tracking/types.ts
@@ -4,6 +4,7 @@ import type { TrackingEvent } from '@holi/core/tracking/events'
 export enum TrackingPurpose {
   Analytics = 'ANALYTICS',
   Personalization = 'PERSONALIZATION',
+  AdPartners = 'AD_PARTNERS',
 }
 
 export enum Consent {
@@ -41,6 +42,7 @@ export namespace Consent {
 export interface ConsentState extends Record<TrackingPurpose, Consent> {
   [TrackingPurpose.Analytics]: Consent
   [TrackingPurpose.Personalization]: Consent
+  [TrackingPurpose.AdPartners]: Consent
 }
 
 // eslint-disable-next-line @typescript-eslint/no-namespace
@@ -48,12 +50,14 @@ export namespace ConsentState {
   export const unknown: ConsentState = {
     [TrackingPurpose.Analytics]: Consent.Unknown,
     [TrackingPurpose.Personalization]: Consent.Unknown,
+    [TrackingPurpose.AdPartners]: Consent.Unknown,
   }
   export const isUnknown = (cs: ConsentState): boolean => Object.values(cs).every((value) => value === Consent.Unknown)
 
   export const fromUser = (user: AuthenticatedUser): ConsentState => ({
     [TrackingPurpose.Analytics]: Consent.fromBooleanOrNullOrUndefined(user.trackingConsentAnalytics),
     [TrackingPurpose.Personalization]: Consent.fromBooleanOrNullOrUndefined(user.trackingConsentPersonalization),
+    [TrackingPurpose.AdPartners]: Consent.fromBooleanOrNullOrUndefined(user.trackingConsentAdPartners),
   })
 
   // can be used to attach the current consent state for every purpose to events sent to PostHog. This way, when
@@ -62,6 +66,7 @@ export namespace ConsentState {
   export const toEventProperties = (cs: ConsentState): Record<string, boolean | null> => ({
     trackingConsentAnalytics: Consent.toBooleanOrNull(cs[TrackingPurpose.Analytics]),
     trackingConsentPersonalization: Consent.toBooleanOrNull(cs[TrackingPurpose.Personalization]),
+    trackingConsentAdPartners: Consent.toBooleanOrNull(cs[TrackingPurpose.AdPartners]),
   })
 }
 
@@ -94,6 +99,14 @@ export interface PosthogCrossPlatform {
   flush: () => Promise<void>
 }
 
+export interface FacebookSdkCrossPlatform {
+  initialize(user?: AuthenticatedUser): Promise<void>
+  optIn(user?: AuthenticatedUser): Promise<void>
+  optOut(): Promise<void>
+  enabled(): Promise<boolean>
+  trackSignUp(): Promise<void>
+}
+
 export type SignupModalPressedTrigger =
   | 'JOIN_SPACE'
   | 'FOLLOW_SPACE'
diff --git a/holi-apps/volunteering/components/__tests__/VolunteeringRecos.test.tsx b/holi-apps/volunteering/components/__tests__/VolunteeringRecos.test.tsx
index 22879dd3c5..7049794179 100644
--- a/holi-apps/volunteering/components/__tests__/VolunteeringRecos.test.tsx
+++ b/holi-apps/volunteering/components/__tests__/VolunteeringRecos.test.tsx
@@ -33,6 +33,7 @@ const testUser1 = (
   lastName: faker.person.lastName(),
   fullName: faker.person.firstName(),
   avatarDefaultColor: faker.color.rgb(),
+  email: faker.internet.email(),
   avatarLabel: faker.person.jobTitle(),
   interestsV2: interests,
   skillsV2: skills,
diff --git a/packages/api/graphql/graphql-codegen.ts b/packages/api/graphql/graphql-codegen.ts
index 22cc17ee99..1155ff0db9 100644
--- a/packages/api/graphql/graphql-codegen.ts
+++ b/packages/api/graphql/graphql-codegen.ts
@@ -325,6 +325,7 @@ export type AuthenticatedUser = {
   avatarLabel: Scalars['String']['output']
   connectionStatusToMyself?: Maybe<UserConnectionStatus>
   connectionStatusToSpace: Array<Maybe<SpaceUserConnectionType>>
+  email: Scalars['String']['output']
   engagementLevel?: Maybe<Scalars['String']['output']>
   firstName?: Maybe<Scalars['String']['output']>
   /** Can be an empty string */
@@ -349,6 +350,7 @@ export type AuthenticatedUser = {
   /** @deprecated Deprecated since 1.52. Use skills_v2 instead. */
   skills: Array<Maybe<Skill>>
   skillsV2: Array<Maybe<Scalars['String']['output']>>
+  trackingConsentAdPartners?: Maybe<Scalars['Boolean']['output']>
   trackingConsentAnalytics?: Maybe<Scalars['Boolean']['output']>
   trackingConsentPersonalization?: Maybe<Scalars['Boolean']['output']>
 }
@@ -2211,6 +2213,7 @@ export type UpdateAuthenticatedUserInput = {
   sdgs?: InputMaybe<Array<InputMaybe<Scalars['UUID']['input']>>>
   skills?: InputMaybe<Array<InputMaybe<Scalars['UUID']['input']>>>
   skillsV2?: InputMaybe<Array<Scalars['String']['input']>>
+  trackingConsentAdPartners?: InputMaybe<Scalars['Boolean']['input']>
   trackingConsentAnalytics?: InputMaybe<Scalars['Boolean']['input']>
   trackingConsentPersonalization?: InputMaybe<Scalars['Boolean']['input']>
 }
diff --git a/packages/ui/helper/fakeUsers.ts b/packages/ui/helper/fakeUsers.ts
index 7bdec832a5..85c00e51e8 100644
--- a/packages/ui/helper/fakeUsers.ts
+++ b/packages/ui/helper/fakeUsers.ts
@@ -5,6 +5,7 @@ import { UserConnectionStatus } from '@holi/core/screens/userprofile/types'
 
 export const getRandomAuthenticatedUser = (): AuthenticatedUser => ({
   ...getRandomUser(),
+  email: faker.internet.email(),
   isEmployee: faker.datatype.boolean(),
 })
 
diff --git a/yarn.lock b/yarn.lock
index d9d0682c58..cb354907ae 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5550,7 +5550,9 @@ __metadata:
     expo-calendar: ^13.0.5
     expo-image-picker: ~15.0.7
     expo-linking: ~6.3.1
+    expo-tracking-transparency: ^5.1.1
     graphql-request: ^7.1.0
+    react-native-fbsdk-next: ^13.4.1
     react-native-reanimated: ~3.10.1
     react-native-svg: 15.2.0
     react-zoom-pan-pinch: ^2.1.3
@@ -5659,6 +5661,7 @@ __metadata:
     expo-splash-screen: "npm:~0.27.6"
     expo-status-bar: "npm:~1.12.1"
     expo-task-manager: "npm:~11.8.2"
+    expo-tracking-transparency: "npm:~4.0.2"
     expo-updates: "npm:~0.25.27"
     fast-text-encoding: "npm:^1.0.6"
     find-yarn-workspace-root: "npm:^2.0.0"
@@ -5684,6 +5687,7 @@ __metadata:
     react-native-animated-pagination-dot: "npm:^0.4.0"
     react-native-calendars: "npm:^1.1306.0"
     react-native-controlled-mentions: "npm:^2.2.5"
+    react-native-fbsdk-next: "npm:^13.4.1"
     react-native-gesture-handler: "npm:~2.16.1"
     react-native-get-random-values: "npm:^1.11.0"
     react-native-image-crop-picker: "npm:^0.41.2"
@@ -19636,6 +19640,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"expo-tracking-transparency@npm:~4.0.2":
+  version: 4.0.2
+  resolution: "expo-tracking-transparency@npm:4.0.2"
+  peerDependencies:
+    expo: "*"
+  checksum: 10c0/86092b53f42000b956a6a8cdd5df0ca71e84dd918614ff71eae8b61ff233e170f1cc77738be59da5c0dd60d2937b3871b8870e9f3303aa56ff5a4c2a9c8cc898
+  languageName: node
+  linkType: hard
+
 "expo-updates-interface@npm:~0.16.2":
   version: 0.16.2
   resolution: "expo-updates-interface@npm:0.16.2"
@@ -28652,6 +28665,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react-native-fbsdk-next@npm:^13.4.1":
+  version: 13.4.1
+  resolution: "react-native-fbsdk-next@npm:13.4.1"
+  peerDependencies:
+    expo: ">=47.0.0"
+    react-native: ">=0.63.3"
+  peerDependenciesMeta:
+    expo:
+      optional: true
+  checksum: 10c0/f38b8d360986e47dede9b5bcd6166e750f93c4f6d38b85013499ce6fa7d0a181b20cc009b6a05167930df0f56ed8dc79223f9cc4bee9969c8d928405fc750576
+  languageName: node
+  linkType: hard
+
 "react-native-fit-image@npm:^1.5.5":
   version: 1.5.5
   resolution: "react-native-fit-image@npm:1.5.5"
-- 
GitLab