diff --git a/lib/optimizely.rb b/lib/optimizely.rb
index 86d405f8..63753a32 100644
--- a/lib/optimizely.rb
+++ b/lib/optimizely.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 #
-#    Copyright 2016-2022, Optimizely and contributors
+#    Copyright 2016-2023, Optimizely and contributors
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
 require_relative 'optimizely/helpers/variable_type'
 require_relative 'optimizely/logger'
 require_relative 'optimizely/notification_center'
+require_relative 'optimizely/notification_center_registry'
 require_relative 'optimizely/optimizely_config'
 require_relative 'optimizely/optimizely_user_context'
 require_relative 'optimizely/odp/lru_cache'
@@ -105,19 +106,7 @@ def initialize( # rubocop:disable Metrics/ParameterLists
 
       @notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
 
-      setup_odp!
-
-      @odp_manager = OdpManager.new(
-        disable: @sdk_settings.odp_disabled,
-        segment_manager: @sdk_settings.odp_segment_manager,
-        event_manager: @sdk_settings.odp_event_manager,
-        segments_cache: @sdk_settings.odp_segments_cache,
-        fetch_segments_timeout: @sdk_settings.fetch_segments_timeout,
-        odp_event_timeout: @sdk_settings.odp_event_timeout,
-        logger: @logger
-      )
-
-      @config_manager = if config_manager.respond_to?(:config)
+      @config_manager = if config_manager.respond_to?(:config) && config_manager.respond_to?(:sdk_key)
                           config_manager
                         elsif sdk_key
                           HTTPProjectConfigManager.new(
@@ -132,9 +121,7 @@ def initialize( # rubocop:disable Metrics/ParameterLists
                           StaticProjectConfigManager.new(datafile, @logger, @error_handler, skip_json_validation)
                         end
 
-      # must call this even if it's scheduled as a listener
-      # in case the config manager was initialized before the listener was added
-      update_odp_config_on_datafile_update unless @sdk_settings.odp_disabled
+      setup_odp!(@config_manager.sdk_key)
 
       @decision_service = DecisionService.new(@logger, @user_profile_service)
 
@@ -1171,7 +1158,7 @@ def project_config
     end
 
     def update_odp_config_on_datafile_update
-      # if datafile isn't ready, expects to be called again by the notification_center
+      # if datafile isn't ready, expects to be called again by the internal notification_center
       return if @config_manager.respond_to?(:ready?) && !@config_manager.ready?
 
       config = @config_manager&.config
@@ -1180,19 +1167,12 @@ def update_odp_config_on_datafile_update
       @odp_manager.update_odp_config(config.public_key_for_odp, config.host_for_odp, config.all_segments)
     end
 
-    def setup_odp!
+    def setup_odp!(sdk_key)
       unless @sdk_settings.is_a? Optimizely::Helpers::OptimizelySdkSettings
         @logger.log(Logger::DEBUG, 'Provided sdk_settings is not an OptimizelySdkSettings instance.') unless @sdk_settings.nil?
         @sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new
       end
 
-      return if @sdk_settings.odp_disabled
-
-      @notification_center.add_notification_listener(
-        NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE],
-        -> { update_odp_config_on_datafile_update }
-      )
-
       if !@sdk_settings.odp_segment_manager.nil? && !Helpers::Validator.segment_manager_valid?(@sdk_settings.odp_segment_manager)
         @logger.log(Logger::ERROR, 'Invalid ODP segment manager, reverting to default.')
         @sdk_settings.odp_segment_manager = nil
@@ -1203,17 +1183,39 @@ def setup_odp!
         @sdk_settings.odp_event_manager = nil
       end
 
-      return if @sdk_settings.odp_segment_manager
-
       if !@sdk_settings.odp_segments_cache.nil? && !Helpers::Validator.segments_cache_valid?(@sdk_settings.odp_segments_cache)
         @logger.log(Logger::ERROR, 'Invalid ODP segments cache, reverting to default.')
         @sdk_settings.odp_segments_cache = nil
       end
 
-      @sdk_settings.odp_segments_cache ||= LRUCache.new(
-        @sdk_settings.segments_cache_size,
-        @sdk_settings.segments_cache_timeout_in_secs
+      # no need to instantiate a cache if a custom cache or segment manager is provided.
+      if !@sdk_settings.odp_disabled && @sdk_settings.odp_segment_manager.nil?
+        @sdk_settings.odp_segments_cache ||= LRUCache.new(
+          @sdk_settings.segments_cache_size,
+          @sdk_settings.segments_cache_timeout_in_secs
+        )
+      end
+
+      @odp_manager = OdpManager.new(
+        disable: @sdk_settings.odp_disabled,
+        segment_manager: @sdk_settings.odp_segment_manager,
+        event_manager: @sdk_settings.odp_event_manager,
+        segments_cache: @sdk_settings.odp_segments_cache,
+        fetch_segments_timeout: @sdk_settings.fetch_segments_timeout,
+        odp_event_timeout: @sdk_settings.odp_event_timeout,
+        logger: @logger
       )
+
+      return if @sdk_settings.odp_disabled
+
+      Optimizely::NotificationCenterRegistry
+        .get_notification_center(sdk_key, @logger)
+        &.add_notification_listener(
+          NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE],
+          method(:update_odp_config_on_datafile_update)
+        )
+
+      update_odp_config_on_datafile_update
     end
   end
 end
diff --git a/lib/optimizely/config_manager/http_project_config_manager.rb b/lib/optimizely/config_manager/http_project_config_manager.rb
index 790353ab..0da73c1f 100644
--- a/lib/optimizely/config_manager/http_project_config_manager.rb
+++ b/lib/optimizely/config_manager/http_project_config_manager.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 #
-#    Copyright 2019-2020, 2022, Optimizely and contributors
+#    Copyright 2019-2020, 2022-2023, Optimizely and contributors
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -33,12 +33,12 @@ module Optimizely
   class HTTPProjectConfigManager < ProjectConfigManager
     # Config manager that polls for the datafile and updated ProjectConfig based on an update interval.
 
-    attr_reader :stopped
+    attr_reader :stopped, :sdk_key
 
     # Initialize config manager. One of sdk_key or url has to be set to be able to use.
     #
-    # sdk_key - Optional string uniquely identifying the datafile. It's required unless a URL is passed in.
-    # datafile: Optional JSON string representing the project.
+    # sdk_key - Optional string uniquely identifying the datafile. It's required unless a datafile with sdk_key is passed in.
+    # datafile - Optional JSON string representing the project. If nil, sdk_key is required.
     # polling_interval - Optional floating point number representing time interval in seconds
     #                  at which to request datafile and set ProjectConfig.
     # blocking_timeout - Optional Time in seconds to block the config call until config object has been initialized.
@@ -83,6 +83,10 @@ def initialize(
       @notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
       @optimizely_config = nil
       @config = datafile.nil? ? nil : DatafileProjectConfig.create(datafile, @logger, @error_handler, @skip_json_validation)
+      @sdk_key = sdk_key || @config&.sdk_key
+
+      raise MissingSdkKeyError if @sdk_key.nil?
+
       @mutex = Mutex.new
       @resource = ConditionVariable.new
       @async_scheduler = AsyncScheduler.new(method(:fetch_datafile_config), @polling_interval, auto_update, @logger)
@@ -222,6 +226,10 @@ def set_config(config)
 
       @notification_center.send_notifications(NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE])
 
+      NotificationCenterRegistry
+        .get_notification_center(@sdk_key, @logger)
+        &.send_notifications(NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE])
+
       @logger.log(Logger::DEBUG, 'Received new datafile and updated config. ' \
         "Old revision number: #{previous_revision}. New revision number: #{@config.revision}.")
     end
diff --git a/lib/optimizely/config_manager/project_config_manager.rb b/lib/optimizely/config_manager/project_config_manager.rb
index e0a3f8e8..220df9ae 100644
--- a/lib/optimizely/config_manager/project_config_manager.rb
+++ b/lib/optimizely/config_manager/project_config_manager.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 #
-#    Copyright 2019, Optimizely and contributors
+#    Copyright 2019, 2023, Optimizely and contributors
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -20,5 +20,6 @@ class ProjectConfigManager
     # Interface for fetching ProjectConfig instance.
 
     def config; end
+    def sdk_key; end
   end
 end
diff --git a/lib/optimizely/config_manager/static_project_config_manager.rb b/lib/optimizely/config_manager/static_project_config_manager.rb
index 281beb3d..38829ce4 100644
--- a/lib/optimizely/config_manager/static_project_config_manager.rb
+++ b/lib/optimizely/config_manager/static_project_config_manager.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 #
-#    Copyright 2019-2020, 2022, Optimizely and contributors
+#    Copyright 2019-2020, 2022-2023, Optimizely and contributors
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
 module Optimizely
   class StaticProjectConfigManager < ProjectConfigManager
     # Implementation of ProjectConfigManager interface.
-    attr_reader :config
+    attr_reader :config, :sdk_key
 
     def initialize(datafile, logger, error_handler, skip_json_validation)
       # Looks up and sets datafile and config based on response body.
@@ -41,6 +41,7 @@ def initialize(datafile, logger, error_handler, skip_json_validation)
         error_handler,
         skip_json_validation
       )
+      @sdk_key = @config&.sdk_key
       @optimizely_config = nil
     end
 
diff --git a/lib/optimizely/exceptions.rb b/lib/optimizely/exceptions.rb
index 51cb5098..50ef62c0 100644
--- a/lib/optimizely/exceptions.rb
+++ b/lib/optimizely/exceptions.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 #
-#    Copyright 2016-2020, 2022, Optimizely and contributors
+#    Copyright 2016-2020, 2022-2023, Optimizely and contributors
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -32,6 +32,13 @@ def initialize(msg = 'Provided URI was invalid.')
     end
   end
 
+  class MissingSdkKeyError < Error
+    # Raised when a provided URI is invalid.
+    def initialize(msg = 'SDK key not provided/cannot be found in the datafile.')
+      super
+    end
+  end
+
   class InvalidAudienceError < Error
     # Raised when an invalid audience is provided
 
diff --git a/lib/optimizely/notification_center_registry.rb b/lib/optimizely/notification_center_registry.rb
new file mode 100644
index 00000000..aea0ade0
--- /dev/null
+++ b/lib/optimizely/notification_center_registry.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+#
+#    Copyright 2023, Optimizely and contributors
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+require_relative 'notification_center'
+require_relative 'exceptions'
+
+module Optimizely
+  class NotificationCenterRegistry
+    private_class_method :new
+    # Class managing internal notification centers.
+    # @api no-doc
+    @notification_centers = {}
+    @mutex = Mutex.new
+
+    # Returns an internal notification center for the given sdk_key, creating one
+    # if none exists yet.
+    #
+    # Args:
+    # sdk_key: A string sdk key to uniquely identify the notification center.
+    # logger: Optional logger.
+
+    # Returns:
+    # nil or NotificationCenter
+    def self.get_notification_center(sdk_key, logger)
+      unless sdk_key
+        logger&.log(Logger::ERROR, "#{MissingSdkKeyError.new.message} ODP may not work properly without it.")
+        return nil
+      end
+
+      notification_center = nil
+
+      @mutex.synchronize do
+        if @notification_centers.key?(sdk_key)
+          notification_center = @notification_centers[sdk_key]
+        else
+          notification_center = NotificationCenter.new(logger, nil)
+          @notification_centers[sdk_key] = notification_center
+        end
+      end
+
+      notification_center
+    end
+
+    # Remove a previously added notification center and clear all its listeners.
+
+    # Args:
+    # sdk_key: The sdk_key of the notification center to remove.
+    def self.remove_notification_center(sdk_key)
+      @mutex.synchronize do
+        @notification_centers
+          .delete(sdk_key)
+          &.clear_all_notification_listeners
+      end
+      nil
+    end
+  end
+end
diff --git a/spec/config_manager/http_project_config_manager_spec.rb b/spec/config_manager/http_project_config_manager_spec.rb
index c786f38e..3c048e9f 100644
--- a/spec/config_manager/http_project_config_manager_spec.rb
+++ b/spec/config_manager/http_project_config_manager_spec.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 #
-#    Copyright 2019-2020, 2022, Optimizely and contributors
+#    Copyright 2019-2020, 2022-2023, Optimizely and contributors
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -57,6 +57,7 @@
   describe '.project_config_manager' do
     it 'should get project config when valid url is given' do
       @http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
+        sdk_key: 'valid_sdk_key',
         url: 'https://cdn.optimizely.com/datafiles/valid_sdk_key.json'
       )
 
@@ -75,6 +76,7 @@
         .to_return(status: 200, body: VALID_SDK_KEY_CONFIG_JSON, headers: {})
 
       @http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
+        sdk_key: 'valid_sdk_key',
         url: 'http://cdn.optimizely.com/datafiles/valid_sdk_key.json'
       )
 
diff --git a/spec/notification_center_registry_spec.rb b/spec/notification_center_registry_spec.rb
new file mode 100644
index 00000000..5a691059
--- /dev/null
+++ b/spec/notification_center_registry_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+#
+#    Copyright 2017-2019, 2022-2023, Optimizely and contributors
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+require 'spec_helper'
+require 'optimizely/error_handler'
+require 'optimizely/event_builder'
+require 'optimizely/exceptions'
+require 'optimizely/logger'
+require 'optimizely/notification_center'
+require 'optimizely/notification_center_registry'
+describe Optimizely::NotificationCenter do
+  let(:spy_logger) { spy('logger') }
+  let(:config_body) { OptimizelySpec::VALID_CONFIG_BODY }
+  let(:config_body_JSON) { OptimizelySpec::VALID_CONFIG_BODY_JSON }
+  let(:error_handler) { Optimizely::NoOpErrorHandler.new }
+  let(:logger) { Optimizely::NoOpLogger.new }
+  let(:notification_center) { Optimizely::NotificationCenter.new(spy_logger, error_handler) }
+
+  describe '#NotificationCenterRegistry' do
+    describe 'test get notification center' do
+      it 'should log error with no sdk_key' do
+        Optimizely::NotificationCenterRegistry.get_notification_center(nil, spy_logger)
+        expect(spy_logger).to have_received(:log).with(Logger::ERROR, "#{Optimizely::MissingSdkKeyError.new.message} ODP may not work properly without it.")
+      end
+
+      it 'should return notification center with odp callback' do
+        sdk_key = 'VALID'
+        stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
+          .to_return(status: 200, body: config_body_JSON)
+
+        project = Optimizely::Project.new(nil, nil, spy_logger, nil, false, nil, sdk_key)
+
+        notification_center = Optimizely::NotificationCenterRegistry.get_notification_center(sdk_key, spy_logger)
+        expect(notification_center).to be_a Optimizely::NotificationCenter
+
+        config_notifications = notification_center.instance_variable_get('@notifications')[Optimizely::NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE]]
+        expect(config_notifications).to include({notification_id: anything, callback: project.method(:update_odp_config_on_datafile_update)})
+        expect(spy_logger).not_to have_received(:log).with(Logger::ERROR, anything)
+
+        project.close
+      end
+
+      it 'should only create one notification center per sdk_key' do
+        sdk_key = 'single'
+        stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
+          .to_return(status: 200, body: config_body_JSON)
+
+        notification_center = Optimizely::NotificationCenterRegistry.get_notification_center(sdk_key, spy_logger)
+        project = Optimizely::Project.new(nil, nil, spy_logger, nil, false, nil, sdk_key)
+
+        expect(notification_center).to eq(Optimizely::NotificationCenterRegistry.get_notification_center(sdk_key, spy_logger))
+        expect(spy_logger).not_to have_received(:log).with(Logger::ERROR, anything)
+
+        project.close
+      end
+    end
+
+    describe 'test remove notification center' do
+      it 'should remove notification center and callbacks' do
+        sdk_key = 'segments-test'
+        stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
+          .to_return(status: 200, body: config_body_JSON)
+
+        notification_center = Optimizely::NotificationCenterRegistry.get_notification_center(sdk_key, spy_logger)
+        expect(notification_center).to receive(:send_notifications).once
+
+        project = Optimizely::Project.new(nil, nil, spy_logger, nil, false, nil, sdk_key)
+        project.config_manager.config
+
+        Optimizely::NotificationCenterRegistry.remove_notification_center(sdk_key)
+        expect(Optimizely::NotificationCenterRegistry.instance_variable_get('@notification_centers').values).not_to include(notification_center)
+
+        revised_datafile = config_body.dup
+        revised_datafile['revision'] = (revised_datafile['revision'].to_i + 1).to_s
+        revised_datafile = Optimizely::DatafileProjectConfig.create(JSON.dump(revised_datafile), spy_logger, nil, nil)
+
+        # trigger notification
+        project.config_manager.send(:set_config, revised_datafile)
+        expect(notification_center).not_to receive(:send_notifications)
+        expect(notification_center).not_to eq(Optimizely::NotificationCenterRegistry.get_notification_center(sdk_key, spy_logger))
+
+        expect(spy_logger).not_to have_received(:log).with(Logger::ERROR, anything)
+
+        project.close
+      end
+    end
+  end
+end
diff --git a/spec/notification_center_spec.rb b/spec/notification_center_spec.rb
index 978de5ac..7ac4e808 100644
--- a/spec/notification_center_spec.rb
+++ b/spec/notification_center_spec.rb
@@ -313,7 +313,7 @@ def call; end
         notification_type = Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE]
         @inner_notification_center.clear_notification_listeners(notification_type)
         expect { @inner_notification_center.clear_notification_listeners(notification_type) }
-          .to_not raise_error(Optimizely::InvalidNotificationType)
+          .to_not raise_error
         expect(
           @inner_notification_center.notifications[
             Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE]
diff --git a/spec/optimizely_factory_spec.rb b/spec/optimizely_factory_spec.rb
index c875fec1..65c8d4d5 100644
--- a/spec/optimizely_factory_spec.rb
+++ b/spec/optimizely_factory_spec.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 #
-#    Copyright 2019, 2022, Optimizely and contributors
+#    Copyright 2019, 2022-2023, Optimizely and contributors
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -70,7 +70,7 @@
   describe '.default_instance_with_manager' do
     it 'should take provided custom config manager' do
       class CustomConfigManager # rubocop:disable Lint/ConstantDefinitionInBlock
-        attr_reader :config
+        attr_reader :config, :sdk_key
       end
 
       custom_config_manager = CustomConfigManager.new
diff --git a/spec/optimizely_user_context_spec.rb b/spec/optimizely_user_context_spec.rb
index 88a915a7..0f2a3d3c 100644
--- a/spec/optimizely_user_context_spec.rb
+++ b/spec/optimizely_user_context_spec.rb
@@ -382,7 +382,7 @@
         expect(decision.user_context.forced_decisions).to eq(context => forced_decision)
         expect(decision.reasons).to eq(['Variation (3324490633) is mapped to flag (feature_1), rule (exp_with_audience) and user (tester) in the forced decision map.'])
       end
-      expected.to raise_error
+      expected.to raise_error Optimizely::InvalidVariationError
     end
 
     it 'should return correct variation if rule in forced decision is deleted' do
diff --git a/spec/project_spec.rb b/spec/project_spec.rb
index b5690e06..36ca3363 100644
--- a/spec/project_spec.rb
+++ b/spec/project_spec.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 #
-#    Copyright 2016-2020, 2022, Optimizely and contributors
+#    Copyright 2016-2020, 2022-2023, Optimizely and contributors
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -28,11 +28,21 @@
 require 'optimizely/version'
 
 describe 'Optimizely' do
-  let(:config_body) { OptimizelySpec::VALID_CONFIG_BODY }
-  let(:config_body_JSON) { OptimizelySpec::VALID_CONFIG_BODY_JSON }
+  # need different sdk_key for every instance, otherwise notification center callbacks get called for the wrong tests
+  let!(:sdk_key) { SecureRandom.uuid }
+  let(:config_body) do
+    datafile = OptimizelySpec::VALID_CONFIG_BODY.dup
+    datafile['sdkKey'] = sdk_key
+    datafile
+  end
+  let(:config_body_JSON) { JSON.dump(config_body) }
   let(:config_body_invalid_JSON) { OptimizelySpec::INVALID_CONFIG_BODY_JSON }
-  let(:config_body_integrations) { OptimizelySpec::CONFIG_DICT_WITH_INTEGRATIONS }
-  let(:config_body_integrations_JSON) { OptimizelySpec::CONFIG_DICT_WITH_INTEGRATIONS_JSON }
+  let(:config_body_integrations) do
+    datafile = OptimizelySpec::CONFIG_DICT_WITH_INTEGRATIONS.dup
+    datafile['sdkKey'] = sdk_key
+    datafile
+  end
+  let(:config_body_integrations_JSON) { JSON.dump(config_body_integrations) }
   let(:error_handler) { Optimizely::RaiseErrorHandler.new }
   let(:spy_logger) { spy('logger') }
   let(:version) { Optimizely::VERSION }
@@ -134,7 +144,7 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock
       allow_any_instance_of(Optimizely::SimpleLogger).to receive(:log).with(Logger::INFO, anything)
       allow_any_instance_of(Optimizely::SimpleLogger).to receive(:log).with(Logger::DEBUG, anything)
       expect_any_instance_of(Optimizely::SimpleLogger).to receive(:log).once.with(Logger::ERROR, 'Provided datafile is in an invalid format.')
-      config = config_body_integrations.dup
+      config = OptimizelySpec.deep_clone(config_body_integrations)
       config['integrations'][0].delete('key')
       integrations_json = JSON.dump(config)
 
@@ -142,7 +152,7 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock
     end
 
     it 'should be valid when datafile contains integrations with only key' do
-      config = config_body_integrations.dup
+      config = OptimizelySpec.deep_clone(config_body_integrations)
       config['integrations'].clear
       config['integrations'].push('key' => '123')
       integrations_json = JSON.dump(config)
@@ -152,7 +162,7 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock
     end
 
     it 'should be valid when datafile contains integrations with arbitrary fields' do
-      config = config_body_integrations.dup
+      config = OptimizelySpec.deep_clone(config_body_integrations)
       config['integrations'].clear
       config['integrations'].push('key' => 'future', 'any-key-1' => 1, 'any-key-2' => 'any-value-2')
       integrations_json = JSON.dump(config)
@@ -881,7 +891,7 @@ def callback(_args); end
     describe '.Optimizely with config manager' do
       before(:example) do
         stub_request(:post, impression_log_url)
-        stub_request(:get, 'https://cdn.optimizely.com/datafiles/valid_sdk_key.json')
+        stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
           .with(
             headers: {
               'Content-Type' => 'application/json'
@@ -901,7 +911,8 @@ def callback(_args); end
 
         expect(notification_center).to receive(:send_notifications).ordered
         http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
-          url: 'https://cdn.optimizely.com/datafiles/valid_sdk_key.json',
+          sdk_key: sdk_key,
+          url: "https://cdn.optimizely.com/datafiles/#{sdk_key}.json",
           notification_center: notification_center
         )
 
@@ -928,7 +939,7 @@ def callback(_args); end
         expect(notification_center).to receive(:send_notifications).ordered
 
         http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
-          sdk_key: 'valid_sdk_key',
+          sdk_key: sdk_key,
           notification_center: notification_center
         )
 
@@ -948,7 +959,7 @@ def callback(_args); end
     describe '.Optimizely with sdk key' do
       before(:example) do
         stub_request(:post, impression_log_url)
-        stub_request(:get, 'https://cdn.optimizely.com/datafiles/valid_sdk_key.json')
+        stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
           .with(
             headers: {
               'Content-Type' => 'application/json'
@@ -968,7 +979,7 @@ def callback(_args); end
 
         custom_project_instance = Optimizely::Project.new(
           nil, nil, spy_logger, error_handler,
-          false, nil, 'valid_sdk_key', nil, notification_center
+          false, nil, sdk_key, nil, notification_center
         )
 
         sleep 0.1 until custom_project_instance.config_manager.ready?
@@ -3455,7 +3466,7 @@ def callback(_args); end
   describe '.close' do
     before(:example) do
       stub_request(:post, impression_log_url)
-      stub_request(:get, 'https://cdn.optimizely.com/datafiles/valid_sdk_key.json')
+      stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
         .with(
           headers: {
             'Content-Type' => 'application/json'
@@ -3466,7 +3477,7 @@ def callback(_args); end
 
     it 'should stop config manager and event processor when optimizely close is called' do
       config_manager = Optimizely::HTTPProjectConfigManager.new(
-        sdk_key: 'valid_sdk_key',
+        sdk_key: sdk_key,
         start_by_default: true
       )
 
@@ -3490,7 +3501,7 @@ def callback(_args); end
 
     it 'should stop invalid object' do
       http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
-        sdk_key: 'valid_sdk_key'
+        sdk_key: sdk_key
       )
 
       project_instance = Optimizely::Project.new(
@@ -3504,7 +3515,7 @@ def callback(_args); end
 
     it 'shoud return optimizely as invalid for an API when close is called' do
       http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
-        sdk_key: 'valid_sdk_key'
+        sdk_key: sdk_key
       )
 
       project_instance = Optimizely::Project.new(
@@ -4440,10 +4451,10 @@ def callback(_args); end
   describe 'sdk_settings' do
     it 'should log info when disabled' do
       project_instance.close
-      stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json')
+      stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
         .to_return(status: 200, body: config_body_integrations_JSON)
       sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(disable_odp: true)
-      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], sdk_settings)
+      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings)
       expect(project.odp_manager.instance_variable_get('@event_manager')).to be_nil
       expect(project.odp_manager.instance_variable_get('@segment_manager')).to be_nil
       project.close
@@ -4453,11 +4464,11 @@ def callback(_args); end
     end
 
     it 'should accept cache_size' do
-      stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json')
+      stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
         .to_return(status: 200, body: config_body_integrations_JSON)
 
       sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(segments_cache_size: 5)
-      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], sdk_settings)
+      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings)
       segment_manager = project.odp_manager.instance_variable_get('@segment_manager')
       expect(segment_manager.instance_variable_get('@segments_cache').capacity).to eq 5
       project.close
@@ -4466,10 +4477,10 @@ def callback(_args); end
     end
 
     it 'should accept cache_timeout' do
-      stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json')
+      stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
         .to_return(status: 200, body: config_body_integrations_JSON)
       sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(segments_cache_timeout_in_secs: 5)
-      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], sdk_settings)
+      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings)
       segment_manager = project.odp_manager.instance_variable_get('@segment_manager')
       expect(segment_manager.instance_variable_get('@segments_cache').timeout).to eq 5
       project.close
@@ -4478,10 +4489,10 @@ def callback(_args); end
     end
 
     it 'should accept cache_size and cache_timeout' do
-      stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json')
+      stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
         .to_return(status: 200, body: config_body_integrations_JSON)
       sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(segments_cache_size: 10, segments_cache_timeout_in_secs: 5)
-      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], sdk_settings)
+      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings)
       segment_manager = project.odp_manager.instance_variable_get('@segment_manager')
       segments_cache = segment_manager.instance_variable_get('@segments_cache')
       expect(segments_cache.capacity).to eq 10
@@ -4498,10 +4509,10 @@ def lookup(key); end
         def save(key, value); end
       end
 
-      stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json')
+      stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
         .to_return(status: 200, body: config_body_integrations_JSON)
       sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(odp_segments_cache: CustomCache.new)
-      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], sdk_settings)
+      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings)
       segment_manager = project.odp_manager.instance_variable_get('@segment_manager')
       expect(segment_manager.instance_variable_get('@segments_cache')).to be_a CustomCache
       project.close
@@ -4613,15 +4624,17 @@ class InvalidEventManager; end # rubocop:disable Lint/ConstantDefinitionInBlock
     end
 
     it 'should send event with HTTPProjectConfigManager' do
-      stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json')
-        .to_return(status: 200, body: config_body_integrations_JSON)
+      datafile = OptimizelySpec.deep_clone(config_body_integrations)
+      stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
+        .to_return(status: 200, body: JSON.dump(datafile))
       stub_request(:post, 'https://api.zaius.com/v3/events').to_return(status: 200)
       expect(spy_logger).to receive(:log).once.with(Logger::DEBUG, 'ODP event queue: flushing batch size 1.')
       expect(spy_logger).not_to receive(:log).with(Logger::ERROR, anything)
-      project = Optimizely::Project.new(nil, nil, spy_logger, nil, false, nil, 'sdk-key')
+      project = Optimizely::Project.new(nil, nil, spy_logger, nil, false, nil, sdk_key)
 
       # wait until project_config ready
       project.send(:project_config)
+      sleep 0.1 until project.odp_manager.instance_variable_get('@event_manager').instance_variable_get('@event_queue').empty?
 
       project.send_odp_event(type: 'wow', action: 'great', identifiers: {}, data: {})
       project.close
@@ -4637,17 +4650,17 @@ class InvalidEventManager; end # rubocop:disable Lint/ConstantDefinitionInBlock
 
     it 'should log debug if datafile not ready' do
       expect(spy_logger).to receive(:log).once.with(Logger::DEBUG, 'ODP event queue: cannot send before config has been set.')
-      project = Optimizely::Project.new(nil, nil, spy_logger, nil, false, nil, 'sdk-key')
+      project = Optimizely::Project.new(nil, nil, spy_logger, nil, false, nil, sdk_key)
       project.send_odp_event(type: 'wow', action: 'great', identifiers: {}, data: {})
       project.close
     end
 
     it 'should log error if odp not enabled with HTTPProjectConfigManager' do
-      stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json')
+      stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
         .to_return(status: 200, body: config_body_integrations_JSON)
       expect(spy_logger).to receive(:log).once.with(Logger::ERROR, 'ODP is not enabled.')
       sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(disable_odp: true)
-      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], sdk_settings)
+      project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings)
       sleep 0.1 until project.config_manager.ready?
       project.send_odp_event(type: 'wow', action: 'great', identifiers: {}, data: {})
       project.close
diff --git a/spec/spec_params.rb b/spec/spec_params.rb
index 1e5911dd..e43ce3cc 100644
--- a/spec/spec_params.rb
+++ b/spec/spec_params.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 #
-#    Copyright 2016-2021, Optimizely and contributors
+#    Copyright 2016-2021, 2023, Optimizely and contributors
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -1328,7 +1328,8 @@ module OptimizelySpec
         'key' => 'event1'
       }
     ],
-    'revision' => '101'
+    'revision' => '101',
+    'sdkKey' => 'INTEGRATIONS'
   }.freeze
 
   SIMILAR_EXP_KEYS = {
@@ -1936,4 +1937,21 @@ module OptimizelySpec
   # SEND_FLAG_DECISIONS_DISABLED_CONFIG['sendFlagDecisions'] = false
 
   CONFIG_DICT_WITH_INTEGRATIONS_JSON = JSON.dump(CONFIG_DICT_WITH_INTEGRATIONS)
+
+  def self.deep_clone(obj)
+    obj.dup.tap do |new_obj|
+      case new_obj
+      when Hash
+        new_obj.each do |key, val|
+          new_obj[key] = deep_clone(val)
+        end
+      when Array
+        new_obj.map! do |val|
+          deep_clone(val)
+        end
+      else
+        new_obj
+      end
+    end
+  end
 end