diff --git a/app/experiments/require_verification_for_namespace_creation_experiment.rb b/app/experiments/require_verification_for_namespace_creation_experiment.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1cadac7e7d46fb2af9f147f164028045169bd773
--- /dev/null
+++ b/app/experiments/require_verification_for_namespace_creation_experiment.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class RequireVerificationForNamespaceCreationExperiment < ApplicationExperiment # rubocop:disable Gitlab/NamespacedClass
+  def control_behavior
+    false
+  end
+
+  def candidate_behavior
+    true
+  end
+
+  def candidate?
+    run
+  end
+
+  def record_conversion(namespace)
+    return unless should_track?
+
+    Experiment.by_name(name).record_conversion_event_for_subject(subject, namespace_id: namespace.id)
+  end
+
+  private
+
+  def subject
+    context.value[:user]
+  end
+end
diff --git a/app/models/experiment.rb b/app/models/experiment.rb
index cd0814c476a5620a52ca0f0be2c6c1f7bea9c3fe..2300ec2996d5d5473ce42b90cd3a8669a28bc243 100644
--- a/app/models/experiment.rb
+++ b/app/models/experiment.rb
@@ -7,7 +7,7 @@ class Experiment < ApplicationRecord
   validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
 
   def self.add_user(name, group_type, user, context = {})
-    find_or_create_by!(name: name).record_user_and_group(user, group_type, context)
+    by_name(name).record_user_and_group(user, group_type, context)
   end
 
   def self.add_group(name, variant:, group:)
@@ -15,11 +15,15 @@ def self.add_group(name, variant:, group:)
   end
 
   def self.add_subject(name, variant:, subject:)
-    find_or_create_by!(name: name).record_subject_and_variant!(subject, variant)
+    by_name(name).record_subject_and_variant!(subject, variant)
   end
 
   def self.record_conversion_event(name, user, context = {})
-    find_or_create_by!(name: name).record_conversion_event_for_user(user, context)
+    by_name(name).record_conversion_event_for_user(user, context)
+  end
+
+  def self.by_name(name)
+    find_or_create_by!(name: name)
   end
 
   # Create or update the recorded experiment_user row for the user in this experiment.
@@ -41,6 +45,16 @@ def record_conversion_event_for_user(user, context = {})
     experiment_user.update!(converted_at: Time.current, context: merged_context(experiment_user, context))
   end
 
+  def record_conversion_event_for_subject(subject, context = {})
+    raise 'Incompatible subject provided!' unless ExperimentSubject.valid_subject?(subject)
+
+    attr_name = subject.class.table_name.singularize.to_sym
+    experiment_subject = experiment_subjects.find_by(attr_name => subject)
+    return unless experiment_subject
+
+    experiment_subject.update!(converted_at: Time.current, context: merged_context(experiment_subject, context))
+  end
+
   def record_subject_and_variant!(subject, variant)
     raise 'Incompatible subject provided!' unless ExperimentSubject.valid_subject?(subject)
 
@@ -57,7 +71,7 @@ def record_subject_and_variant!(subject, variant)
 
   private
 
-  def merged_context(experiment_user, new_context)
-    experiment_user.context.deep_merge(new_context.deep_stringify_keys)
+  def merged_context(experiment_subject, new_context)
+    experiment_subject.context.deep_merge(new_context.deep_stringify_keys)
   end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 3f9f5b3992234d18c212ac870a0a725250a1a7b2..a5acde01116746620405f7ceacf7eb4617227cf3 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -331,6 +331,7 @@ def update_tracked_fields!(request)
   delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true
   delegate :pronunciation, :pronunciation=, to: :user_detail, allow_nil: true
   delegate :registration_objective, :registration_objective=, to: :user_detail, allow_nil: true
+  delegate :requires_credit_card_verification, :requires_credit_card_verification=, to: :user_detail, allow_nil: true
 
   accepts_nested_attributes_for :user_preference, update_only: true
   accepts_nested_attributes_for :user_detail, update_only: true
diff --git a/app/services/users/upsert_credit_card_validation_service.rb b/app/services/users/upsert_credit_card_validation_service.rb
index 61cf598f178830f16097bc45539b0279766e9708..7190c82bea35af5bd6422dfc9e3c5337ec9fe2b5 100644
--- a/app/services/users/upsert_credit_card_validation_service.rb
+++ b/app/services/users/upsert_credit_card_validation_service.rb
@@ -2,8 +2,9 @@
 
 module Users
   class UpsertCreditCardValidationService < BaseService
-    def initialize(params)
+    def initialize(params, user)
       @params = params.to_h.with_indifferent_access
+      @current_user = user
     end
 
     def execute
@@ -18,6 +19,8 @@ def execute
 
       ::Users::CreditCardValidation.upsert(@params)
 
+      ::Users::UpdateService.new(current_user, user: current_user, requires_credit_card_verification: false).execute!
+
       ServiceResponse.success(message: 'CreditCardValidation was set')
     rescue ActiveRecord::InvalidForeignKey, ActiveRecord::NotNullViolation => e
       ServiceResponse.error(message: "Could not set CreditCardValidation: #{e.message}")
diff --git a/config/feature_flags/experiment/require_verification_for_namespace_creation.yml b/config/feature_flags/experiment/require_verification_for_namespace_creation.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5772d3217b815561e0f6d65c52b177945042ec54
--- /dev/null
+++ b/config/feature_flags/experiment/require_verification_for_namespace_creation.yml
@@ -0,0 +1,8 @@
+---
+name: require_verification_for_namespace_creation
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77315
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350251
+milestone: '14.8'
+type: experiment
+group: group::activation
+default_enabled: false
diff --git a/db/migrate/20220112115413_add_requires_verification_to_user_details.rb b/db/migrate/20220112115413_add_requires_verification_to_user_details.rb
new file mode 100644
index 0000000000000000000000000000000000000000..01fe4f1d5cff6d81d91fdfcd244a1791c3912fe5
--- /dev/null
+++ b/db/migrate/20220112115413_add_requires_verification_to_user_details.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddRequiresVerificationToUserDetails < Gitlab::Database::Migration[1.0]
+  enable_lock_retries!
+
+  def change
+    add_column :user_details, :requires_credit_card_verification, :boolean, null: false, default: false
+  end
+end
diff --git a/db/schema_migrations/20220112115413 b/db/schema_migrations/20220112115413
new file mode 100644
index 0000000000000000000000000000000000000000..9c8c653f69b6dd8c326b78e0e5ebe238ff7c7448
--- /dev/null
+++ b/db/schema_migrations/20220112115413
@@ -0,0 +1 @@
+1199adba4c13e9234eabadefeb55ed3cfb19e9d5a87c07b90d438e4f48a973f7
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 0a791ffa1fb6264c4e31f17a089e9e8fd5edb64f..b1e68a41039aff842a100ef76ec877f5c2d19d5a 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -20256,6 +20256,7 @@ CREATE TABLE user_details (
     pronunciation text,
     registration_objective smallint,
     phone text,
+    requires_credit_card_verification boolean DEFAULT false NOT NULL,
     CONSTRAINT check_245664af82 CHECK ((char_length(webauthn_xid) <= 100)),
     CONSTRAINT check_a73b398c60 CHECK ((char_length(phone) <= 50)),
     CONSTRAINT check_b132136b01 CHECK ((char_length(other_role) <= 100)),
diff --git a/ee/app/assets/javascripts/registrations/groups_projects/new/components/credit_card_verification.vue b/ee/app/assets/javascripts/registrations/groups_projects/new/components/credit_card_verification.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6a60996f48b77b6ed2d94203bda1cba2df7df6db
--- /dev/null
+++ b/ee/app/assets/javascripts/registrations/groups_projects/new/components/credit_card_verification.vue
@@ -0,0 +1,85 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import Zuora from 'ee/billings/components/zuora.vue';
+import { I18N, IFRAME_MINIMUM_HEIGHT } from '../constants';
+import StaticToggle from './static_toggle.vue';
+
+export default {
+  components: {
+    GlButton,
+    StaticToggle,
+    Zuora,
+  },
+  inject: ['completed', 'iframeUrl', 'allowedOrigin'],
+  data() {
+    return {
+      verificationCompleted: this.completed,
+    };
+  },
+  watch: {
+    verificationCompleted() {
+      this.toggleProjectCreation();
+    },
+  },
+  mounted() {
+    this.toggleProjectCreation();
+  },
+  methods: {
+    submit() {
+      this.$refs.zuora.submit();
+    },
+    verified() {
+      this.verificationCompleted = true;
+    },
+    toggleProjectCreation() {
+      // Workaround until we refactor group and project creation into Vue
+      // https://gitlab.com/gitlab-org/gitlab/-/issues/339998
+      const el = document.querySelector('.js-toggle-container');
+      el.classList.toggle('gl-display-none', !this.verificationCompleted);
+    },
+  },
+  i18n: I18N,
+  iframeHeight: IFRAME_MINIMUM_HEIGHT,
+};
+</script>
+<template>
+  <div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-w-full">
+    <static-toggle
+      ref="verifyToggle"
+      :enabled="!verificationCompleted"
+      :completed="verificationCompleted"
+      :title="$options.i18n.verifyToggle"
+    />
+    <div
+      v-if="!verificationCompleted"
+      class="gl-border-gray-100 gl-border-solid gl-border-1 gl-rounded-base gl-px-2 gl-py-5 gl-text-left"
+    >
+      <div class="gl-px-4 gl-text-secondary gl-font-sm">
+        {{ $options.i18n.explanation }}
+      </div>
+      <zuora
+        ref="zuora"
+        :initial-height="$options.iframeHeight"
+        :iframe-url="iframeUrl"
+        :allowed-origin="allowedOrigin"
+        @success="verified"
+      />
+      <div class="gl-px-4">
+        <gl-button
+          ref="submitButton"
+          variant="confirm"
+          type="submit"
+          class="gl-w-full!"
+          @click="submit"
+        >
+          {{ $options.i18n.submitVerify }}
+        </gl-button>
+      </div>
+    </div>
+    <static-toggle
+      ref="createToggle"
+      :enabled="verificationCompleted"
+      :title="$options.i18n.createToggle"
+    />
+  </div>
+</template>
diff --git a/ee/app/assets/javascripts/registrations/groups_projects/new/components/static_toggle.vue b/ee/app/assets/javascripts/registrations/groups_projects/new/components/static_toggle.vue
new file mode 100644
index 0000000000000000000000000000000000000000..46a5ea2e9c91e9fe4c83836abf4f62ec92b86c9f
--- /dev/null
+++ b/ee/app/assets/javascripts/registrations/groups_projects/new/components/static_toggle.vue
@@ -0,0 +1,53 @@
+<script>
+import { GlIcon } from '@gitlab/ui';
+import { __ } from '~/locale';
+
+export default {
+  components: {
+    GlIcon,
+  },
+  props: {
+    enabled: {
+      type: Boolean,
+      required: true,
+    },
+    completed: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    title: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    icon() {
+      return this.enabled ? 'chevron-down' : 'chevron-right';
+    },
+  },
+  i18n: {
+    label: __('Completed'),
+  },
+};
+</script>
+<template>
+  <div class="gl-border-gray-100 gl-border-l-solid gl-border-1 gl-w-full gl-my-3 gl-pl-3">
+    <div class="gl-display-flex gl-align-items-center">
+      <span
+        class="gl-display-flex gl-align-items-center gl-flex-grow-1 gl-font-weight-bold gl-text-blue-500"
+        :class="{ 'gl-text-gray-400!': !enabled }"
+      >
+        <gl-icon :name="icon" :size="24" class="gl-text-gray-500 gl-mr-3" />
+        {{ title }}
+      </span>
+      <gl-icon
+        v-if="completed"
+        name="check-circle-filled"
+        :size="16"
+        class="gl-text-green-600"
+        :aria-label="$options.i18n.label"
+      />
+    </div>
+  </div>
+</template>
diff --git a/ee/app/assets/javascripts/registrations/groups_projects/new/constants.js b/ee/app/assets/javascripts/registrations/groups_projects/new/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..a54bf1c9ec9ef6bc0f59353db88a515749bb8b31
--- /dev/null
+++ b/ee/app/assets/javascripts/registrations/groups_projects/new/constants.js
@@ -0,0 +1,11 @@
+import { s__ } from '~/locale';
+
+export const IFRAME_MINIMUM_HEIGHT = 312;
+export const I18N = {
+  verifyToggle: s__('IdentityVerification|Verify your identity'),
+  createToggle: s__('IdentityVerification|Create a project'),
+  explanation: s__(
+    'IdentityVerification|Before you create your first project, we need you to verify your identity with a valid payment method. You will not be charged during this step. If we ever need to charge you, we will let you know.',
+  ),
+  submitVerify: s__('IdentityVerification|Verify your identity'),
+};
diff --git a/ee/app/assets/javascripts/registrations/groups_projects/new/index.js b/ee/app/assets/javascripts/registrations/groups_projects/new/index.js
index fabc6e54d1bb21521cc03b03440f344138bfeb63..04db199415b2229eac6e1635100176486b23ed5b 100644
--- a/ee/app/assets/javascripts/registrations/groups_projects/new/index.js
+++ b/ee/app/assets/javascripts/registrations/groups_projects/new/index.js
@@ -1,8 +1,11 @@
 import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
 import $ from 'jquery';
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
 import { bindHowToImport } from '~/projects/project_new';
 import { displayGroupPath, displayProjectPath } from './path_display';
 import showTooltip from './show_tooltip';
+import CreditCardVerification from './components/credit_card_verification.vue';
 
 const importButtonsSubmit = () => {
   const buttons = document.querySelectorAll('.js-import-project-buttons a');
@@ -33,6 +36,28 @@ const setAutofocus = () => {
 
 const mobileTooltipOpts = () => (bp.getBreakpointSize() === 'xs' ? { placement: 'bottom' } : {});
 
+const mountVerification = () => {
+  const el = document.querySelector('.js-credit-card-verification');
+
+  if (!el) {
+    return null;
+  }
+
+  const { completed, iframeUrl, allowedOrigin } = el.dataset;
+
+  return new Vue({
+    el,
+    provide: {
+      completed: parseBoolean(completed),
+      iframeUrl,
+      allowedOrigin,
+    },
+    render(createElement) {
+      return createElement(CreditCardVerification);
+    },
+  });
+};
+
 export default () => {
   displayGroupPath('.js-group-path-source', '.js-group-path-display');
   displayGroupPath('.js-import-group-path-source', '.js-import-group-path-display');
@@ -41,4 +66,5 @@ export default () => {
   importButtonsSubmit();
   bindHowToImport();
   setAutofocus();
+  mountVerification();
 };
diff --git a/ee/app/controllers/concerns/registrations/verification.rb b/ee/app/controllers/concerns/registrations/verification.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3b35bf796de6e6b56efa6baf582f98673b49a7e9
--- /dev/null
+++ b/ee/app/controllers/concerns/registrations/verification.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Registrations::Verification
+  extend ActiveSupport::Concern
+
+  included do
+    before_action :require_verification, if: :verification_required?
+
+    private
+
+    def verification_required?
+      html_request? &&
+        request.get? &&
+        current_user&.requires_credit_card_verification
+    end
+
+    def require_verification
+      redirect_to new_users_sign_up_groups_project_path
+    end
+
+    def set_requires_verification
+      ::Users::UpdateService.new(current_user, user: current_user, requires_credit_card_verification: true).execute!
+    end
+  end
+end
diff --git a/ee/app/controllers/ee/application_controller.rb b/ee/app/controllers/ee/application_controller.rb
index 4599e1b0771788fac331a53ecb87831d41715907..f36c0626f9857fa7da8d9f24dab9cb31aa0a113a 100644
--- a/ee/app/controllers/ee/application_controller.rb
+++ b/ee/app/controllers/ee/application_controller.rb
@@ -6,6 +6,8 @@ module ApplicationController
     extend ::Gitlab::Utils::Override
 
     prepended do
+      include ::Registrations::Verification
+
       around_action :set_current_ip_address
     end
 
diff --git a/ee/app/controllers/registrations/groups_projects_controller.rb b/ee/app/controllers/registrations/groups_projects_controller.rb
index 964ae3da8d9262e6aad6d60a9303c38aab7012ff..731db163ee2036cad56253958c1f515325431a3b 100644
--- a/ee/app/controllers/registrations/groups_projects_controller.rb
+++ b/ee/app/controllers/registrations/groups_projects_controller.rb
@@ -6,11 +6,17 @@ class GroupsProjectsController < ApplicationController
     include Registrations::CreateGroup
     include OneTrustCSP
 
+    skip_before_action :require_verification, only: :new
+    before_action :set_requires_verification, only: :new, if: -> { helpers.require_verification_experiment.candidate? }
+    before_action :require_verification, only: [:create, :import], if: -> { current_user.requires_credit_card_verification }
+
     layout 'minimal'
 
     feature_category :onboarding
 
     def new
+      helpers.require_verification_experiment.publish_to_database
+
       @group = Group.new(visibility_level: helpers.default_group_visibility)
       @project = Project.new(namespace: @group)
 
@@ -51,6 +57,7 @@ def create
               success_url = new_trial_path
             end
 
+            helpers.require_verification_experiment.record_conversion(@group)
             redirect_to success_url
           end
         else
@@ -66,6 +73,7 @@ def import
       @group = Groups::CreateService.new(current_user, modified_group_params).execute
       if @group.persisted?
         combined_registration_experiment.track(:create_group, namespace: @group)
+        helpers.require_verification_experiment.record_conversion(@group)
 
         import_url = URI.join(root_url, params[:import_url], "?namespace_id=#{@group.id}").to_s
         redirect_to import_url
diff --git a/ee/app/helpers/ee/registrations_helper.rb b/ee/app/helpers/ee/registrations_helper.rb
index f15c1d7848cd92f90bd71ddd392f57f93e9ff22e..3e9c6a4a0e0425ba0bff836f37422908c98846c6 100644
--- a/ee/app/helpers/ee/registrations_helper.rb
+++ b/ee/app/helpers/ee/registrations_helper.rb
@@ -38,6 +38,20 @@ def registration_verification_data
       { next_step_url: url }
     end
 
+    def require_verification_experiment
+      strong_memoize(:require_verification_experiment) do
+        experiment(:require_verification_for_namespace_creation, user: current_user)
+      end
+    end
+
+    def credit_card_verification_data
+      {
+        completed: current_user.credit_card_validation.present?.to_s,
+        iframe_url: ::Gitlab::SubscriptionPortal::REGISTRATION_VALIDATION_FORM_URL,
+        allowed_origin: ::Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL
+      }
+    end
+
     private
 
     def redirect_path
diff --git a/ee/app/views/registrations/groups_projects/new.html.haml b/ee/app/views/registrations/groups_projects/new.html.haml
index 7070ff2789ba7b102bd4df4138186efc75237ef3..35f8a62b127d435f0d1b2d8ea7adb6fff29a7089 100644
--- a/ee/app/views/registrations/groups_projects/new.html.haml
+++ b/ee/app/views/registrations/groups_projects/new.html.haml
@@ -18,7 +18,9 @@
 
       %p.gl-text-center= _('Projects help you organize your work. They contain your file repository, issues, merge requests, and so much more.')
 
-      .js-toggle-container.gl-w-full
+      - if (verify = require_verification_experiment.candidate?)
+        .js-credit-card-verification{ data: credit_card_verification_data }
+      .js-toggle-container.gl-w-full{ class: ('gl-display-none' if verify) }
         %ul.nav.nav-tabs.nav-links.gitlab-tabs.js-group-project-tabs{ role: 'tablist' }
           %li.nav-item{ role: 'presentation' }
             %a#blank-project-tab.nav-link.active{ href: '#blank-project-pane', data: { toggle: 'tab', track_label: 'blank_project', track_action: 'click_tab', track_value: '' }, role: 'tab' }
diff --git a/ee/spec/controllers/concerns/registrations/verification_spec.rb b/ee/spec/controllers/concerns/registrations/verification_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1ad4004ee7bce1a55ae6d95fb13b0abd9393af25
--- /dev/null
+++ b/ee/spec/controllers/concerns/registrations/verification_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Registrations::Verification do
+  controller(ActionController::Base) do
+    include Registrations::Verification
+
+    before_action :set_requires_verification, only: :new
+
+    def index
+      head :ok
+    end
+
+    def create
+      head :ok
+    end
+
+    def new
+      head :ok
+    end
+
+    def html_request?
+      request.format.html?
+    end
+  end
+
+  let_it_be(:user) { create(:user) }
+
+  before do
+    sign_in(user)
+  end
+
+  describe '#require_verification' do
+    describe 'verification is not required' do
+      it 'does not redirect' do
+        get :index
+
+        expect(response).to have_gitlab_http_status(:ok)
+      end
+    end
+
+    describe 'verification is required' do
+      let_it_be(:user) { create(:user, requires_credit_card_verification: true) }
+
+      it 'redirects to the new users sign_up groups_project path' do
+        get :index
+
+        expect(response).to redirect_to(new_users_sign_up_groups_project_path)
+      end
+
+      it 'does not redirect on JS requests' do
+        get :index, format: :js
+
+        expect(response).to have_gitlab_http_status(:ok)
+      end
+
+      it 'does not redirect on POST requests' do
+        post :create
+
+        expect(response).to have_gitlab_http_status(:ok)
+      end
+    end
+  end
+
+  describe '#set_requires_verification' do
+    it 'sets the requires_credit_card_verification attribute' do
+      expect { get :new }.to change { user.reload.requires_credit_card_verification }.to(true)
+    end
+  end
+end
diff --git a/ee/spec/controllers/registrations/groups_projects_controller_spec.rb b/ee/spec/controllers/registrations/groups_projects_controller_spec.rb
index 8bf8dbe2b79ea6808b15dab767a540fbecc6feff..ee2b0a6dc17a5eb45eb5cfc41b2b057ab6783992 100644
--- a/ee/spec/controllers/registrations/groups_projects_controller_spec.rb
+++ b/ee/spec/controllers/registrations/groups_projects_controller_spec.rb
@@ -28,6 +28,28 @@
 
         subject
       end
+
+      it 'publishes the required verification experiment to the database' do
+        expect_next_instance_of(RequireVerificationForNamespaceCreationExperiment) do |experiment|
+          expect(experiment).to receive(:publish_to_database)
+        end
+
+        subject
+      end
+    end
+  end
+
+  shared_context 'records a conversion event' do
+    let_it_be(:experiment) { create(:experiment, name: :require_verification_for_namespace_creation) }
+    let_it_be(:experiment_subject) { create(:experiment_subject, experiment: experiment, user: user) }
+
+    before do
+      stub_experiments(require_verification_for_namespace_creation: true)
+    end
+
+    it 'records a conversion event for the required verification experiment' do
+      expect { subject }.to change { experiment_subject.reload.converted_at }.from(nil)
+        .and change { experiment_subject.context }.to include('namespace_id')
     end
   end
 
@@ -55,6 +77,8 @@
 
       it_behaves_like 'hides email confirmation warning'
 
+      it_behaves_like 'records a conversion event'
+
       context 'when group and project can be created' do
         it 'creates a group' do
           expect { post_create }.to change { Group.count }.by(1)
@@ -234,6 +258,8 @@
 
       it_behaves_like 'hides email confirmation warning'
 
+      it_behaves_like 'records a conversion event'
+
       context "when a group can't be created" do
         before do
           allow_next_instance_of(::Groups::CreateService) do |service|
diff --git a/ee/spec/frontend/registrations/groups_projects/new/components/credit_card_verification_spec.js b/ee/spec/frontend/registrations/groups_projects/new/components/credit_card_verification_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..ff0b54bada86353f2b01f917182e6df5186f0b3e
--- /dev/null
+++ b/ee/spec/frontend/registrations/groups_projects/new/components/credit_card_verification_spec.js
@@ -0,0 +1,114 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import CreditCardVerification from 'ee/registrations/groups_projects/new/components/credit_card_verification.vue';
+import { IFRAME_MINIMUM_HEIGHT } from 'ee/registrations/groups_projects/new/constants';
+import { setHTMLFixture } from 'helpers/fixtures';
+
+describe('CreditCardVerification', () => {
+  let wrapper;
+  let zuoraSubmitSpy;
+
+  const IFRAME_URL = 'https://customers.gitlab.com/payment_forms/cc_registration_validation';
+  const ALLOWED_ORIGIN = 'https://customers.gitlab.com';
+
+  const createComponent = (completed = false) => {
+    wrapper = shallowMount(CreditCardVerification, {
+      provide: {
+        completed,
+        iframeUrl: IFRAME_URL,
+        allowedOrigin: ALLOWED_ORIGIN,
+      },
+      stubs: {
+        GlButton,
+      },
+    });
+  };
+
+  const verifyToggleEnabled = () =>
+    wrapper.find({ ref: 'verifyToggle' }).attributes('enabled') === 'true';
+  const createToggleEnabled = () =>
+    wrapper.find({ ref: 'createToggle' }).attributes('enabled') === 'true';
+  const findZuora = () => wrapper.find({ ref: 'zuora' });
+  const findSubmitButton = () => wrapper.find({ ref: 'submitButton' });
+  const toggleContainerHidden = () =>
+    document.querySelector('.js-toggle-container').classList.contains('gl-display-none');
+
+  beforeEach(() => {
+    setHTMLFixture('<div class="js-toggle-container gl-display-none" />');
+    createComponent();
+  });
+
+  afterEach(() => {
+    wrapper.destroy();
+  });
+
+  describe('when the component is mounted', () => {
+    it('enables the right toggles', () => {
+      expect(verifyToggleEnabled()).toBe(true);
+      expect(createToggleEnabled()).toBe(false);
+    });
+
+    it('hides the toggleContainer', () => {
+      expect(toggleContainerHidden()).toBe(true);
+    });
+
+    it('renders the Zuora component with the right attributes', () => {
+      expect(findZuora().exists()).toBe(true);
+      expect(findZuora().attributes()).toMatchObject({
+        iframeurl: IFRAME_URL,
+        allowedorigin: ALLOWED_ORIGIN,
+        initialheight: IFRAME_MINIMUM_HEIGHT.toString(),
+      });
+    });
+
+    describe('when verification is completed', () => {
+      beforeEach(() => {
+        createComponent(true);
+      });
+
+      it('enables the right toggles', () => {
+        expect(verifyToggleEnabled()).toBe(false);
+        expect(createToggleEnabled()).toBe(true);
+      });
+
+      it('shows the toggleContainer', () => {
+        expect(toggleContainerHidden()).toBe(false);
+      });
+
+      it('hides the Zuora component', () => {
+        expect(findZuora().exists()).toBe(false);
+      });
+    });
+  });
+
+  describe('when the submit button is clicked', () => {
+    beforeEach(() => {
+      zuoraSubmitSpy = jest.fn();
+      wrapper.vm.$refs.zuora = { submit: zuoraSubmitSpy };
+      findSubmitButton().trigger('click');
+    });
+
+    it('calls the submit method of the Zuora component', () => {
+      expect(zuoraSubmitSpy).toHaveBeenCalled();
+    });
+  });
+
+  describe('when the Zuora component emits a success event', () => {
+    beforeEach(() => {
+      findZuora().vm.$emit('success');
+    });
+
+    it('enables the right toggles', () => {
+      expect(verifyToggleEnabled()).toBe(false);
+      expect(createToggleEnabled()).toBe(true);
+    });
+
+    it('shows the toggleContainer', () => {
+      expect(toggleContainerHidden()).toBe(false);
+    });
+
+    it('hides the Zuora component', () => {
+      expect(findZuora().exists()).toBe(false);
+    });
+  });
+});
diff --git a/ee/spec/helpers/ee/registrations_helper_spec.rb b/ee/spec/helpers/ee/registrations_helper_spec.rb
index ac2ea90bb62627e97a3a8f13c35c79ebc63add25..332dd550a5f8a1d98952ef714a53c611166c9b89 100644
--- a/ee/spec/helpers/ee/registrations_helper_spec.rb
+++ b/ee/spec/helpers/ee/registrations_helper_spec.rb
@@ -120,4 +120,20 @@
       end
     end
   end
+
+  describe '#credit_card_verification_data' do
+    before do
+      allow(helper).to receive(:current_user).and_return(build(:user))
+    end
+
+    it 'returns the expected data' do
+      expect(helper.credit_card_verification_data).to eq(
+        {
+          completed: 'false',
+          iframe_url: ::Gitlab::SubscriptionPortal::REGISTRATION_VALIDATION_FORM_URL,
+          allowed_origin: ::Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL
+        }
+      )
+    end
+  end
 end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index efecc7593d046bb4783b47f8030cdf013d5e6fb2..eeb5244466ae3f12d39c548d7bf945e3c96cae21 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -1076,7 +1076,7 @@ def target_user
 
         attrs = declared_params(include_missing: false)
 
-        service = ::Users::UpsertCreditCardValidationService.new(attrs).execute
+        service = ::Users::UpsertCreditCardValidationService.new(attrs, user).execute
 
         if service.success?
           present user.credit_card_validation, with: Entities::UserCreditCardValidations
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9300c67805336c2eb9f75faeac9a9d535aa7d4cb..4c34ad7a623a16f153b4d14361244bdd9fbc8da9 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17832,9 +17832,15 @@ msgstr ""
 msgid "Identities"
 msgstr ""
 
+msgid "IdentityVerification|Before you create your first project, we need you to verify your identity with a valid payment method. You will not be charged during this step. If we ever need to charge you, we will let you know."
+msgstr ""
+
 msgid "IdentityVerification|Before you create your group, we need you to verify your identity with a valid payment method."
 msgstr ""
 
+msgid "IdentityVerification|Create a project"
+msgstr ""
+
 msgid "IdentityVerification|Verify your identity"
 msgstr ""
 
diff --git a/spec/experiments/require_verification_for_namespace_creation_experiment_spec.rb b/spec/experiments/require_verification_for_namespace_creation_experiment_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..87417fe1637dd15d158a607a5dac63ba5e9f70c3
--- /dev/null
+++ b/spec/experiments/require_verification_for_namespace_creation_experiment_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe RequireVerificationForNamespaceCreationExperiment, :experiment do
+  subject(:experiment) { described_class.new(user: user) }
+
+  let_it_be(:user) { create(:user) }
+
+  describe '#candidate?' do
+    context 'when experiment subject is candidate' do
+      before do
+        stub_experiments(require_verification_for_namespace_creation: :candidate)
+      end
+
+      it 'returns true' do
+        expect(experiment.candidate?).to eq(true)
+      end
+    end
+
+    context 'when experiment subject is control' do
+      before do
+        stub_experiments(require_verification_for_namespace_creation: :control)
+      end
+
+      it 'returns false' do
+        expect(experiment.candidate?).to eq(false)
+      end
+    end
+  end
+
+  describe '#record_conversion' do
+    let_it_be(:namespace) { create(:namespace) }
+
+    context 'when should_track? is false' do
+      before do
+        allow(experiment).to receive(:should_track?).and_return(false)
+      end
+
+      it 'does not record a conversion event' do
+        expect(experiment.publish_to_database).to be_nil
+        expect(experiment.record_conversion(namespace)).to be_nil
+      end
+    end
+
+    context 'when should_track? is true' do
+      before do
+        allow(experiment).to receive(:should_track?).and_return(true)
+      end
+
+      it 'records a conversion event' do
+        experiment_subject = experiment.publish_to_database
+
+        expect { experiment.record_conversion(namespace) }.to change { experiment_subject.reload.converted_at }.from(nil)
+          .and change { experiment_subject.context }.to include('namespace_id' => namespace.id)
+      end
+    end
+  end
+end
diff --git a/spec/models/experiment_spec.rb b/spec/models/experiment_spec.rb
index ea5d2b27028b33cb7327ce174a3b84bc11a5aebd..de6ce3ba05318495bbf5777b002c36cb335717c8 100644
--- a/spec/models/experiment_spec.rb
+++ b/spec/models/experiment_spec.rb
@@ -235,6 +235,54 @@
     end
   end
 
+  describe '#record_conversion_event_for_subject' do
+    let_it_be(:user) { create(:user) }
+    let_it_be(:experiment) { create(:experiment) }
+    let_it_be(:context) { { a: 42 } }
+
+    subject(:record_conversion) { experiment.record_conversion_event_for_subject(user, context) }
+
+    context 'when no existing experiment_subject record exists for the given user' do
+      it 'does not update or create an experiment_subject record' do
+        expect { record_conversion }.not_to change { ExperimentSubject.all.to_a }
+      end
+    end
+
+    context 'when an existing experiment_subject exists for the given user' do
+      context 'but it has already been converted' do
+        let(:experiment_subject) { create(:experiment_subject, experiment: experiment, user: user, converted_at: 2.days.ago) }
+
+        it 'does not update the converted_at value' do
+          expect { record_conversion }.not_to change { experiment_subject.converted_at }
+        end
+      end
+
+      context 'and it has not yet been converted' do
+        let(:experiment_subject) { create(:experiment_subject, experiment: experiment, user: user) }
+
+        it 'updates the converted_at value' do
+          expect { record_conversion }.to change { experiment_subject.reload.converted_at }
+        end
+      end
+
+      context 'with no existing context' do
+        let(:experiment_subject) { create(:experiment_subject, experiment: experiment, user: user) }
+
+        it 'updates the context' do
+          expect { record_conversion }.to change { experiment_subject.reload.context }.to('a' => 42)
+        end
+      end
+
+      context 'with an existing context' do
+        let(:experiment_subject) { create(:experiment_subject, experiment: experiment, user: user, converted_at: 2.days.ago, context: { b: 1 } ) }
+
+        it 'merges the context' do
+          expect { record_conversion }.to change { experiment_subject.reload.context }.to('a' => 42, 'b' => 1)
+        end
+      end
+    end
+  end
+
   describe '#record_subject_and_variant!' do
     let_it_be(:subject_to_record) { create(:group) }
     let_it_be(:variant) { :control }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index ad4873950e19b79641f4d8d81de5530935e84404..a860ccd4f530935d3980c88edf50d978d7495258 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -83,6 +83,9 @@
 
     it { is_expected.to delegate_method(:registration_objective).to(:user_detail).allow_nil }
     it { is_expected.to delegate_method(:registration_objective=).to(:user_detail).with_arguments(:args).allow_nil }
+
+    it { is_expected.to delegate_method(:requires_credit_card_verification).to(:user_detail).allow_nil }
+    it { is_expected.to delegate_method(:requires_credit_card_verification=).to(:user_detail).with_arguments(:args).allow_nil }
   end
 
   describe 'associations' do
diff --git a/spec/services/users/upsert_credit_card_validation_service_spec.rb b/spec/services/users/upsert_credit_card_validation_service_spec.rb
index 952d482f1bdecd0077ab0397be05398bca0e6b67..ac7e619612f532563f05a492af5a9940ab9df386 100644
--- a/spec/services/users/upsert_credit_card_validation_service_spec.rb
+++ b/spec/services/users/upsert_credit_card_validation_service_spec.rb
@@ -3,7 +3,7 @@
 require 'spec_helper'
 
 RSpec.describe Users::UpsertCreditCardValidationService do
-  let_it_be(:user) { create(:user) }
+  let_it_be(:user) { create(:user, requires_credit_card_verification: true) }
 
   let(:user_id) { user.id }
   let(:credit_card_validated_time) { Time.utc(2020, 1, 1) }
@@ -21,7 +21,7 @@
   end
 
   describe '#execute' do
-    subject(:service) { described_class.new(params) }
+    subject(:service) { described_class.new(params, user) }
 
     context 'successfully set credit card validation record for the user' do
       context 'when user does not have credit card validation record' do
@@ -42,6 +42,10 @@
             expiration_date: Date.new(expiration_year, 1, 31)
           )
         end
+
+        it 'sets the requires_credit_card_verification attribute on the user to false' do
+          expect { service.execute }.to change { user.reload.requires_credit_card_verification }.to(false)
+        end
       end
 
       context 'when user has credit card validation record' do