diff --git a/Cargo.lock b/Cargo.lock
index f92d703a8f0755304ab22b00f65c52b19e144004..72213da0de7845ee33f9fb11fb02acfa7987b535 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1869,6 +1869,8 @@ dependencies = [
  "frame-support",
  "parity-scale-codec",
  "scale-info",
+ "sp-core 28.0.0",
+ "staging-xcm",
 ]
 
 [[package]]
@@ -1879,6 +1881,8 @@ dependencies = [
  "frame-support",
  "parity-scale-codec",
  "scale-info",
+ "sp-core 28.0.0",
+ "staging-xcm",
 ]
 
 [[package]]
@@ -2273,6 +2277,7 @@ dependencies = [
  "bp-rococo",
  "bp-runtime",
  "bp-westend",
+ "bp-xcm-bridge-hub-router",
  "bridge-hub-common",
  "bridge-hub-test-utils",
  "bridge-runtime-common",
@@ -2466,6 +2471,7 @@ dependencies = [
  "bp-rococo",
  "bp-runtime",
  "bp-westend",
+ "bp-xcm-bridge-hub-router",
  "bridge-hub-common",
  "bridge-hub-test-utils",
  "bridge-runtime-common",
@@ -13268,6 +13274,7 @@ dependencies = [
  "bp-messages",
  "bp-runtime",
  "bp-xcm-bridge-hub",
+ "bp-xcm-bridge-hub-router",
  "frame-support",
  "frame-system",
  "log",
diff --git a/bridges/chains/chain-asset-hub-rococo/Cargo.toml b/bridges/chains/chain-asset-hub-rococo/Cargo.toml
index 363a869048aae4d875d68a8ca46e30756cbc799f..4eb93ab52bc91813753692f03bdadc071b36b2e8 100644
--- a/bridges/chains/chain-asset-hub-rococo/Cargo.toml
+++ b/bridges/chains/chain-asset-hub-rococo/Cargo.toml
@@ -19,10 +19,14 @@ scale-info = { features = ["derive"], workspace = true }
 
 # Substrate Dependencies
 frame-support = { workspace = true }
+sp-core = { workspace = true }
 
 # Bridge Dependencies
 bp-xcm-bridge-hub-router = { workspace = true }
 
+# Polkadot dependencies
+xcm = { workspace = true }
+
 [features]
 default = ["std"]
 std = [
@@ -30,4 +34,6 @@ std = [
 	"codec/std",
 	"frame-support/std",
 	"scale-info/std",
+	"sp-core/std",
+	"xcm/std",
 ]
diff --git a/bridges/chains/chain-asset-hub-rococo/src/lib.rs b/bridges/chains/chain-asset-hub-rococo/src/lib.rs
index de2e9ae856d1f8756f0a2a6b9cae3da3e265e76e..4ff7b391acd050c081e75e7b65a43f375452ca6d 100644
--- a/bridges/chains/chain-asset-hub-rococo/src/lib.rs
+++ b/bridges/chains/chain-asset-hub-rococo/src/lib.rs
@@ -18,10 +18,13 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
+extern crate alloc;
+
 use codec::{Decode, Encode};
 use scale_info::TypeInfo;
 
 pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall;
+use xcm::latest::prelude::*;
 
 /// `AssetHubRococo` Runtime `Call` enum.
 ///
@@ -44,5 +47,27 @@ frame_support::parameter_types! {
 	pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144);
 }
 
+/// Builds an (un)congestion XCM program with the `report_bridge_status` call for
+/// `ToWestendXcmRouter`.
+pub fn build_congestion_message<RuntimeCall>(
+	bridge_id: sp_core::H256,
+	is_congested: bool,
+) -> alloc::vec::Vec<Instruction<RuntimeCall>> {
+	alloc::vec![
+		UnpaidExecution { weight_limit: Unlimited, check_origin: None },
+		Transact {
+			origin_kind: OriginKind::Xcm,
+			fallback_max_weight: Some(XcmBridgeHubRouterTransactCallMaxWeight::get()),
+			call: Call::ToWestendXcmRouter(XcmBridgeHubRouterCall::report_bridge_status {
+				bridge_id,
+				is_congested,
+			})
+			.encode()
+			.into(),
+		},
+		ExpectTransactStatus(MaybeErrorCode::Success),
+	]
+}
+
 /// Identifier of AssetHubRococo in the Rococo relay chain.
 pub const ASSET_HUB_ROCOCO_PARACHAIN_ID: u32 = 1000;
diff --git a/bridges/chains/chain-asset-hub-westend/Cargo.toml b/bridges/chains/chain-asset-hub-westend/Cargo.toml
index 430d9b6116cfc7fba648b96b1de6ef379f3e38f3..22071399f4d18f30d0f4173129dcbfc48aa95c8d 100644
--- a/bridges/chains/chain-asset-hub-westend/Cargo.toml
+++ b/bridges/chains/chain-asset-hub-westend/Cargo.toml
@@ -19,10 +19,14 @@ scale-info = { features = ["derive"], workspace = true }
 
 # Substrate Dependencies
 frame-support = { workspace = true }
+sp-core = { workspace = true }
 
 # Bridge Dependencies
 bp-xcm-bridge-hub-router = { workspace = true }
 
+# Polkadot dependencies
+xcm = { workspace = true }
+
 [features]
 default = ["std"]
 std = [
@@ -30,4 +34,6 @@ std = [
 	"codec/std",
 	"frame-support/std",
 	"scale-info/std",
+	"sp-core/std",
+	"xcm/std",
 ]
diff --git a/bridges/chains/chain-asset-hub-westend/src/lib.rs b/bridges/chains/chain-asset-hub-westend/src/lib.rs
index 9de1c88098942cdf7bd0684462a95ac3de412490..9d245e08f7cc83768e7f334bf8d60f2c242aeb62 100644
--- a/bridges/chains/chain-asset-hub-westend/src/lib.rs
+++ b/bridges/chains/chain-asset-hub-westend/src/lib.rs
@@ -18,10 +18,13 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
+extern crate alloc;
+
 use codec::{Decode, Encode};
 use scale_info::TypeInfo;
 
 pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall;
+use xcm::latest::prelude::*;
 
 /// `AssetHubWestend` Runtime `Call` enum.
 ///
@@ -44,5 +47,27 @@ frame_support::parameter_types! {
 	pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144);
 }
 
+/// Builds an (un)congestion XCM program with the `report_bridge_status` call for
+/// `ToRococoXcmRouter`.
+pub fn build_congestion_message<RuntimeCall>(
+	bridge_id: sp_core::H256,
+	is_congested: bool,
+) -> alloc::vec::Vec<Instruction<RuntimeCall>> {
+	alloc::vec![
+		UnpaidExecution { weight_limit: Unlimited, check_origin: None },
+		Transact {
+			origin_kind: OriginKind::Xcm,
+			fallback_max_weight: Some(XcmBridgeHubRouterTransactCallMaxWeight::get()),
+			call: Call::ToRococoXcmRouter(XcmBridgeHubRouterCall::report_bridge_status {
+				bridge_id,
+				is_congested,
+			})
+			.encode()
+			.into(),
+		},
+		ExpectTransactStatus(MaybeErrorCode::Success),
+	]
+}
+
 /// Identifier of AssetHubWestend in the Westend relay chain.
 pub const ASSET_HUB_WESTEND_PARACHAIN_ID: u32 = 1000;
diff --git a/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs b/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs
index 3c4a10f82e7dff12ae7d8f7b300bc5d66839073c..ff06a1e3c8c5ac67aa7b36d4606721062700f1ea 100644
--- a/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs
+++ b/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs
@@ -18,9 +18,9 @@
 
 #![cfg(feature = "runtime-benchmarks")]
 
-use crate::{DeliveryFeeFactor, MINIMAL_DELIVERY_FEE_FACTOR};
+use crate::{Bridge, BridgeState, Call, MINIMAL_DELIVERY_FEE_FACTOR};
 use frame_benchmarking::{benchmarks_instance_pallet, BenchmarkError};
-use frame_support::traits::{Get, Hooks};
+use frame_support::traits::{EnsureOrigin, Get, Hooks, UnfilteredDispatchable};
 use sp_runtime::traits::Zero;
 use xcm::prelude::*;
 
@@ -45,16 +45,35 @@ pub trait Config<I: 'static>: crate::Config<I> {
 
 benchmarks_instance_pallet! {
 	on_initialize_when_non_congested {
-		DeliveryFeeFactor::<T, I>::put(MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR);
+		Bridge::<T, I>::put(BridgeState {
+			is_congested: false,
+			delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR,
+		});
 	}: {
 		crate::Pallet::<T, I>::on_initialize(Zero::zero())
 	}
 
 	on_initialize_when_congested {
-		DeliveryFeeFactor::<T, I>::put(MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR);
+		Bridge::<T, I>::put(BridgeState {
+			is_congested: false,
+			delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR,
+		});
 		let _ = T::ensure_bridged_target_destination()?;
 		T::make_congested();
 	}: {
 		crate::Pallet::<T, I>::on_initialize(Zero::zero())
 	}
+
+	report_bridge_status {
+		Bridge::<T, I>::put(BridgeState::default());
+
+		let origin: T::RuntimeOrigin = T::BridgeHubOrigin::try_successful_origin().expect("expected valid BridgeHubOrigin");
+		let bridge_id = Default::default();
+		let is_congested = true;
+
+		let call = Call::<T, I>::report_bridge_status { bridge_id, is_congested };
+	}: { call.dispatch_bypass_filter(origin)? }
+	verify {
+		assert!(Bridge::<T, I>::get().is_congested);
+	}
 }
diff --git a/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/bridges/modules/xcm-bridge-hub-router/src/lib.rs
index fe8f5a2efdfb8ad2f166941a841def915bc103b0..7361696faba71fd68c5d2b9fb4982a229ec48fd0 100644
--- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs
+++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs
@@ -30,9 +30,10 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
-pub use bp_xcm_bridge_hub_router::XcmChannelStatusProvider;
+pub use bp_xcm_bridge_hub_router::{BridgeState, XcmChannelStatusProvider};
 use codec::Encode;
 use frame_support::traits::Get;
+use sp_core::H256;
 use sp_runtime::{FixedPointNumber, FixedU128, Saturating};
 use sp_std::vec::Vec;
 use xcm::prelude::*;
@@ -98,6 +99,8 @@ pub mod pallet {
 		/// Checks the XCM version for the destination.
 		type DestinationVersion: GetVersion;
 
+		/// Origin of the sibling bridge hub that is allowed to report bridge status.
+		type BridgeHubOrigin: EnsureOrigin<Self::RuntimeOrigin>;
 		/// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location.
 		type ToBridgeHubSender: SendXcm;
 		/// Local XCM channel manager.
@@ -120,95 +123,112 @@ pub mod pallet {
 				return T::WeightInfo::on_initialize_when_congested()
 			}
 
+			// if bridge has reported congestion, we don't change anything
+			let mut bridge = Self::bridge();
+			if bridge.is_congested {
+				return T::WeightInfo::on_initialize_when_congested()
+			}
+
 			// if we can't decrease the delivery fee factor anymore, we don't change anything
-			let mut delivery_fee_factor = Self::delivery_fee_factor();
-			if delivery_fee_factor == MINIMAL_DELIVERY_FEE_FACTOR {
+			if bridge.delivery_fee_factor == MINIMAL_DELIVERY_FEE_FACTOR {
 				return T::WeightInfo::on_initialize_when_congested()
 			}
 
-			let previous_factor = delivery_fee_factor;
-			delivery_fee_factor =
-				MINIMAL_DELIVERY_FEE_FACTOR.max(delivery_fee_factor / EXPONENTIAL_FEE_BASE);
+			let previous_factor = bridge.delivery_fee_factor;
+			bridge.delivery_fee_factor =
+				MINIMAL_DELIVERY_FEE_FACTOR.max(bridge.delivery_fee_factor / EXPONENTIAL_FEE_BASE);
+
 			log::info!(
 				target: LOG_TARGET,
 				"Bridge channel is uncongested. Decreased fee factor from {} to {}",
 				previous_factor,
-				delivery_fee_factor,
+				bridge.delivery_fee_factor,
 			);
 			Self::deposit_event(Event::DeliveryFeeFactorDecreased {
-				new_value: delivery_fee_factor,
+				new_value: bridge.delivery_fee_factor,
 			});
 
-			DeliveryFeeFactor::<T, I>::put(delivery_fee_factor);
+			Bridge::<T, I>::put(bridge);
 
 			T::WeightInfo::on_initialize_when_non_congested()
 		}
 	}
 
-	/// Initialization value for the delivery fee factor.
-	#[pallet::type_value]
-	pub fn InitialFactor() -> FixedU128 {
-		MINIMAL_DELIVERY_FEE_FACTOR
+	#[pallet::call]
+	impl<T: Config<I>, I: 'static> Pallet<T, I> {
+		/// Notification about congested bridge queue.
+		#[pallet::call_index(0)]
+		#[pallet::weight(T::WeightInfo::report_bridge_status())]
+		pub fn report_bridge_status(
+			origin: OriginFor<T>,
+			// this argument is not currently used, but to ease future migration, we'll keep it
+			// here
+			bridge_id: H256,
+			is_congested: bool,
+		) -> DispatchResult {
+			let _ = T::BridgeHubOrigin::ensure_origin(origin)?;
+
+			log::info!(
+				target: LOG_TARGET,
+				"Received bridge status from {:?}: congested = {}",
+				bridge_id,
+				is_congested,
+			);
+
+			Bridge::<T, I>::mutate(|bridge| {
+				bridge.is_congested = is_congested;
+			});
+			Ok(())
+		}
 	}
 
-	/// The number to multiply the base delivery fee by.
+	/// Bridge that we are using.
 	///
-	/// This factor is shared by all bridges, served by this pallet. For example, if this
-	/// chain (`Config::UniversalLocation`) opens two bridges (
-	/// `X2(GlobalConsensus(Config::BridgedNetworkId::get()), Parachain(1000))` and
-	/// `X2(GlobalConsensus(Config::BridgedNetworkId::get()), Parachain(2000))`), then they
-	/// both will be sharing the same fee factor. This is because both bridges are sharing
-	/// the same local XCM channel with the child/sibling bridge hub, which we are using
-	/// to detect congestion:
-	///
-	/// ```nocompile
-	///  ThisChain --- Local XCM channel --> Sibling Bridge Hub ------
-	///                                            |                   |
-	///                                            |                   |
-	///                                            |                   |
-	///                                          Lane1               Lane2
-	///                                            |                   |
-	///                                            |                   |
-	///                                            |                   |
-	///                                           \ /                  |
-	///  Parachain1  <-- Local XCM channel --- Remote Bridge Hub <------
-	///                                            |
-	///                                            |
-	///  Parachain1  <-- Local XCM channel ---------
-	/// ```
-	///
-	/// If at least one of other channels is congested, the local XCM channel with sibling
-	/// bridge hub eventually becomes congested too. And we have no means to detect - which
-	/// bridge exactly causes the congestion. So the best solution here is not to make
-	/// any differences between all bridges, started by this chain.
+	/// **bridges-v1** assumptions: all outbound messages through this router are using single lane
+	/// and to single remote consensus. If there is some other remote consensus that uses the same
+	/// bridge hub, the separate pallet instance shall be used, In `v2` we'll have all required
+	/// primitives (lane-id aka bridge-id, derived from XCM locations) to support multiple  bridges
+	/// by the same pallet instance.
 	#[pallet::storage]
-	#[pallet::getter(fn delivery_fee_factor)]
-	pub type DeliveryFeeFactor<T: Config<I>, I: 'static = ()> =
-		StorageValue<_, FixedU128, ValueQuery, InitialFactor>;
+	#[pallet::getter(fn bridge)]
+	pub type Bridge<T: Config<I>, I: 'static = ()> = StorageValue<_, BridgeState, ValueQuery>;
 
 	impl<T: Config<I>, I: 'static> Pallet<T, I> {
 		/// Called when new message is sent (queued to local outbound XCM queue) over the bridge.
 		pub(crate) fn on_message_sent_to_bridge(message_size: u32) {
-			// if outbound channel is not congested, do nothing
-			if !T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get()) {
-				return
-			}
+			log::trace!(
+				target: LOG_TARGET,
+				"on_message_sent_to_bridge - message_size: {message_size:?}",
+			);
+			let _ = Bridge::<T, I>::try_mutate(|bridge| {
+				let is_channel_with_bridge_hub_congested =
+					T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get());
+				let is_bridge_congested = bridge.is_congested;
+
+				// if outbound queue is not congested AND bridge has not reported congestion, do
+				// nothing
+				if !is_channel_with_bridge_hub_congested && !is_bridge_congested {
+					return Err(())
+				}
+
+				// ok - we need to increase the fee factor, let's do that
+				let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024))
+					.saturating_mul(MESSAGE_SIZE_FEE_BASE);
+				let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor);
+				let previous_factor = bridge.delivery_fee_factor;
+				bridge.delivery_fee_factor =
+					bridge.delivery_fee_factor.saturating_mul(total_factor);
 
-			// ok - we need to increase the fee factor, let's do that
-			let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024))
-				.saturating_mul(MESSAGE_SIZE_FEE_BASE);
-			let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor);
-			DeliveryFeeFactor::<T, I>::mutate(|f| {
-				let previous_factor = *f;
-				*f = f.saturating_mul(total_factor);
 				log::info!(
 					target: LOG_TARGET,
 					"Bridge channel is congested. Increased fee factor from {} to {}",
 					previous_factor,
-					f,
+					bridge.delivery_fee_factor,
 				);
-				Self::deposit_event(Event::DeliveryFeeFactorIncreased { new_value: *f });
-				*f
+				Self::deposit_event(Event::DeliveryFeeFactorIncreased {
+					new_value: bridge.delivery_fee_factor,
+				});
+				Ok(())
 			});
 		}
 	}
@@ -310,9 +330,9 @@ impl<T: Config<I>, I: 'static> ExporterFor for Pallet<T, I> {
 		let message_size = message.encoded_size();
 		let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get());
 		let fee_sum = base_fee.saturating_add(message_fee);
-
-		let fee_factor = Self::delivery_fee_factor();
+		let fee_factor = Self::bridge().delivery_fee_factor;
 		let fee = fee_factor.saturating_mul_int(fee_sum);
+
 		let fee = if fee > 0 { Some((T::FeeAsset::get(), fee).into()) } else { None };
 
 		log::info!(
@@ -427,24 +447,47 @@ mod tests {
 	use frame_system::{EventRecord, Phase};
 	use sp_runtime::traits::One;
 
+	fn congested_bridge(delivery_fee_factor: FixedU128) -> BridgeState {
+		BridgeState { is_congested: true, delivery_fee_factor }
+	}
+
+	fn uncongested_bridge(delivery_fee_factor: FixedU128) -> BridgeState {
+		BridgeState { is_congested: false, delivery_fee_factor }
+	}
+
 	#[test]
 	fn initial_fee_factor_is_one() {
 		run_test(|| {
-			assert_eq!(DeliveryFeeFactor::<TestRuntime, ()>::get(), MINIMAL_DELIVERY_FEE_FACTOR);
+			assert_eq!(
+				Bridge::<TestRuntime, ()>::get(),
+				uncongested_bridge(MINIMAL_DELIVERY_FEE_FACTOR),
+			);
 		})
 	}
 
 	#[test]
 	fn fee_factor_is_not_decreased_from_on_initialize_when_xcm_channel_is_congested() {
 		run_test(|| {
-			DeliveryFeeFactor::<TestRuntime, ()>::put(FixedU128::from_rational(125, 100));
+			Bridge::<TestRuntime, ()>::put(uncongested_bridge(FixedU128::from_rational(125, 100)));
 			TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get());
 
 			// it should not decrease, because queue is congested
-			let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor();
+			let old_delivery = XcmBridgeHubRouter::bridge();
 			XcmBridgeHubRouter::on_initialize(One::one());
-			assert_eq!(XcmBridgeHubRouter::delivery_fee_factor(), old_delivery_fee_factor);
+			assert_eq!(XcmBridgeHubRouter::bridge(), old_delivery);
+			assert_eq!(System::events(), vec![]);
+		})
+	}
+
+	#[test]
+	fn fee_factor_is_not_decreased_from_on_initialize_when_bridge_has_reported_congestion() {
+		run_test(|| {
+			Bridge::<TestRuntime, ()>::put(congested_bridge(FixedU128::from_rational(125, 100)));
 
+			// it should not decrease, because bridge congested
+			let old_bridge = XcmBridgeHubRouter::bridge();
+			XcmBridgeHubRouter::on_initialize(One::one());
+			assert_eq!(XcmBridgeHubRouter::bridge(), old_bridge);
 			assert_eq!(System::events(), vec![]);
 		})
 	}
@@ -453,16 +496,19 @@ mod tests {
 	fn fee_factor_is_decreased_from_on_initialize_when_xcm_channel_is_uncongested() {
 		run_test(|| {
 			let initial_fee_factor = FixedU128::from_rational(125, 100);
-			DeliveryFeeFactor::<TestRuntime, ()>::put(initial_fee_factor);
+			Bridge::<TestRuntime, ()>::put(uncongested_bridge(initial_fee_factor));
 
-			// it shold eventually decreased to one
-			while XcmBridgeHubRouter::delivery_fee_factor() > MINIMAL_DELIVERY_FEE_FACTOR {
+			// it should eventually decrease to one
+			while XcmBridgeHubRouter::bridge().delivery_fee_factor > MINIMAL_DELIVERY_FEE_FACTOR {
 				XcmBridgeHubRouter::on_initialize(One::one());
 			}
 
-			// verify that it doesn't decreases anymore
+			// verify that it doesn't decrease anymore
 			XcmBridgeHubRouter::on_initialize(One::one());
-			assert_eq!(XcmBridgeHubRouter::delivery_fee_factor(), MINIMAL_DELIVERY_FEE_FACTOR);
+			assert_eq!(
+				XcmBridgeHubRouter::bridge(),
+				uncongested_bridge(MINIMAL_DELIVERY_FEE_FACTOR)
+			);
 
 			// check emitted event
 			let first_system_event = System::events().first().cloned();
@@ -582,7 +628,7 @@ mod tests {
 			// but when factor is larger than one, it increases the fee, so it becomes:
 			// `(BASE_FEE + BYTE_FEE * msg_size) * F + HRMP_FEE`
 			let factor = FixedU128::from_rational(125, 100);
-			DeliveryFeeFactor::<TestRuntime, ()>::put(factor);
+			Bridge::<TestRuntime, ()>::put(uncongested_bridge(factor));
 			let expected_fee =
 				(FixedU128::saturating_from_integer(BASE_FEE + BYTE_FEE * (msg_size as u128)) *
 					factor)
@@ -598,7 +644,7 @@ mod tests {
 	#[test]
 	fn sent_message_doesnt_increase_factor_if_queue_is_uncongested() {
 		run_test(|| {
-			let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor();
+			let old_bridge = XcmBridgeHubRouter::bridge();
 			assert_eq!(
 				send_xcm::<XcmBridgeHubRouter>(
 					Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]),
@@ -609,7 +655,7 @@ mod tests {
 			);
 
 			assert!(TestToBridgeHubSender::is_message_sent());
-			assert_eq!(old_delivery_fee_factor, XcmBridgeHubRouter::delivery_fee_factor());
+			assert_eq!(old_bridge, XcmBridgeHubRouter::bridge());
 
 			assert_eq!(System::events(), vec![]);
 		});
@@ -620,7 +666,39 @@ mod tests {
 		run_test(|| {
 			TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get());
 
-			let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor();
+			let old_bridge = XcmBridgeHubRouter::bridge();
+			assert_ok!(send_xcm::<XcmBridgeHubRouter>(
+				Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]),
+				vec![ClearOrigin].into(),
+			)
+			.map(drop));
+
+			assert!(TestToBridgeHubSender::is_message_sent());
+			assert!(
+				old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor
+			);
+
+			// check emitted event
+			let first_system_event = System::events().first().cloned();
+			assert!(matches!(
+				first_system_event,
+				Some(EventRecord {
+					phase: Phase::Initialization,
+					event: RuntimeEvent::XcmBridgeHubRouter(
+						Event::DeliveryFeeFactorIncreased { .. }
+					),
+					..
+				})
+			));
+		});
+	}
+
+	#[test]
+	fn sent_message_increases_factor_if_bridge_has_reported_congestion() {
+		run_test(|| {
+			Bridge::<TestRuntime, ()>::put(congested_bridge(MINIMAL_DELIVERY_FEE_FACTOR));
+
+			let old_bridge = XcmBridgeHubRouter::bridge();
 			assert_ok!(send_xcm::<XcmBridgeHubRouter>(
 				Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]),
 				vec![ClearOrigin].into(),
@@ -628,7 +706,9 @@ mod tests {
 			.map(drop));
 
 			assert!(TestToBridgeHubSender::is_message_sent());
-			assert!(old_delivery_fee_factor < XcmBridgeHubRouter::delivery_fee_factor());
+			assert!(
+				old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor
+			);
 
 			// check emitted event
 			let first_system_event = System::events().first().cloned();
diff --git a/bridges/modules/xcm-bridge-hub-router/src/mock.rs b/bridges/modules/xcm-bridge-hub-router/src/mock.rs
index 095572883920fce371536d8575df77b514a4b148..ac642e108c2ae615397d046a6be9a7dd25f5621c 100644
--- a/bridges/modules/xcm-bridge-hub-router/src/mock.rs
+++ b/bridges/modules/xcm-bridge-hub-router/src/mock.rs
@@ -80,6 +80,7 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime {
 	type DestinationVersion =
 		LatestOrNoneForLocationVersionChecker<Equals<UnknownXcmVersionForRoutableLocation>>;
 
+	type BridgeHubOrigin = frame_system::EnsureRoot<u64>;
 	type ToBridgeHubSender = TestToBridgeHubSender;
 	type LocalXcmChannelManager = TestLocalXcmChannelManager;
 
diff --git a/bridges/modules/xcm-bridge-hub-router/src/weights.rs b/bridges/modules/xcm-bridge-hub-router/src/weights.rs
index d9a0426fecaf8de6858b785222a50d0e4291f0af..8f5012c9de26beb1626775961a95ff32210b35d7 100644
--- a/bridges/modules/xcm-bridge-hub-router/src/weights.rs
+++ b/bridges/modules/xcm-bridge-hub-router/src/weights.rs
@@ -52,6 +52,7 @@ use sp_std::marker::PhantomData;
 pub trait WeightInfo {
 	fn on_initialize_when_non_congested() -> Weight;
 	fn on_initialize_when_congested() -> Weight;
+	fn report_bridge_status() -> Weight;
 }
 
 /// Weights for `pallet_xcm_bridge_hub_router` that are generated using one of the Bridge testnets.
@@ -85,6 +86,19 @@ impl<T: frame_system::Config> WeightInfo for BridgeWeight<T> {
 		// Minimum execution time: 4_239 nanoseconds.
 		Weight::from_parts(4_383_000, 3547).saturating_add(T::DbWeight::get().reads(1_u64))
 	}
+	/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
+	///
+	/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
+	/// 512, mode: `MaxEncodedLen`)
+	fn report_bridge_status() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `53`
+		//  Estimated: `1502`
+		// Minimum execution time: 10_427 nanoseconds.
+		Weight::from_parts(10_682_000, 1502)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
 }
 
 // For backwards compatibility and tests
@@ -120,4 +134,17 @@ impl WeightInfo for () {
 		// Minimum execution time: 4_239 nanoseconds.
 		Weight::from_parts(4_383_000, 3547).saturating_add(RocksDbWeight::get().reads(1_u64))
 	}
+	/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
+	///
+	/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
+	/// 512, mode: `MaxEncodedLen`)
+	fn report_bridge_status() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `53`
+		//  Estimated: `1502`
+		// Minimum execution time: 10_427 nanoseconds.
+		Weight::from_parts(10_682_000, 1502)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
 }
diff --git a/bridges/modules/xcm-bridge-hub/Cargo.toml b/bridges/modules/xcm-bridge-hub/Cargo.toml
index fe58b910a94ef99547bed8f2956a6084aea43e2d..251dcfb45bcb6be91049208f830201fa7bca00ae 100644
--- a/bridges/modules/xcm-bridge-hub/Cargo.toml
+++ b/bridges/modules/xcm-bridge-hub/Cargo.toml
@@ -39,6 +39,7 @@ sp-io = { workspace = true }
 bp-runtime = { workspace = true }
 bp-header-chain = { workspace = true }
 pallet-xcm-bridge-hub-router = { workspace = true }
+bp-xcm-bridge-hub-router = { workspace = true }
 polkadot-parachain-primitives = { workspace = true }
 
 [features]
@@ -47,6 +48,7 @@ std = [
 	"bp-header-chain/std",
 	"bp-messages/std",
 	"bp-runtime/std",
+	"bp-xcm-bridge-hub-router/std",
 	"bp-xcm-bridge-hub/std",
 	"codec/std",
 	"frame-support/std",
diff --git a/bridges/modules/xcm-bridge-hub/src/exporter.rs b/bridges/modules/xcm-bridge-hub/src/exporter.rs
index 5afb9f36bc9414bde920eec3d3e84bc7487f711d..93b6093b42af5ab42794f92bf1a7683d32e60317 100644
--- a/bridges/modules/xcm-bridge-hub/src/exporter.rs
+++ b/bridges/modules/xcm-bridge-hub/src/exporter.rs
@@ -364,7 +364,7 @@ mod tests {
 
 	use bp_runtime::RangeInclusiveExt;
 	use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState};
-	use frame_support::assert_ok;
+	use frame_support::{assert_ok, traits::EnsureOrigin};
 	use pallet_bridge_messages::InboundLaneStorage;
 	use xcm_builder::{NetworkExportTable, UnpaidRemoteExporter};
 	use xcm_executor::traits::{export_xcm, ConvertLocation};
@@ -381,9 +381,8 @@ mod tests {
 		BridgedUniversalDestination::get()
 	}
 
-	fn open_lane() -> (BridgeLocations, TestLaneIdType) {
+	fn open_lane(origin: RuntimeOrigin) -> (BridgeLocations, TestLaneIdType) {
 		// open expected outbound lane
-		let origin = OpenBridgeOrigin::sibling_parachain_origin();
 		let with = bridged_asset_hub_universal_location();
 		let locations =
 			XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap();
@@ -439,7 +438,7 @@ mod tests {
 	}
 
 	fn open_lane_and_send_regular_message() -> (BridgeId, TestLaneIdType) {
-		let (locations, lane_id) = open_lane();
+		let (locations, lane_id) = open_lane(OpenBridgeOrigin::sibling_parachain_origin());
 
 		// now let's try to enqueue message using our `ExportXcm` implementation
 		export_xcm::<XcmOverBridge>(
@@ -473,7 +472,7 @@ mod tests {
 	fn exporter_does_not_suspend_the_bridge_if_outbound_bridge_queue_is_not_congested() {
 		run_test(|| {
 			let (bridge_id, _) = open_lane_and_send_regular_message();
-			assert!(!TestLocalXcmChannelManager::is_bridge_suspened());
+			assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
 			assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
 		});
 	}
@@ -490,7 +489,7 @@ mod tests {
 			}
 
 			open_lane_and_send_regular_message();
-			assert!(!TestLocalXcmChannelManager::is_bridge_suspened());
+			assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
 		});
 	}
 
@@ -502,11 +501,11 @@ mod tests {
 				open_lane_and_send_regular_message();
 			}
 
-			assert!(!TestLocalXcmChannelManager::is_bridge_suspened());
+			assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
 			assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
 
 			open_lane_and_send_regular_message();
-			assert!(TestLocalXcmChannelManager::is_bridge_suspened());
+			assert!(TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
 			assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended);
 		});
 	}
@@ -523,7 +522,7 @@ mod tests {
 				OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1,
 			);
 
-			assert!(!TestLocalXcmChannelManager::is_bridge_resumed());
+			assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
 			assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended);
 		});
 	}
@@ -537,7 +536,7 @@ mod tests {
 				OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
 			);
 
-			assert!(!TestLocalXcmChannelManager::is_bridge_resumed());
+			assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
 			assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
 		});
 	}
@@ -554,7 +553,7 @@ mod tests {
 				OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
 			);
 
-			assert!(TestLocalXcmChannelManager::is_bridge_resumed());
+			assert!(TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
 			assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
 		});
 	}
@@ -648,7 +647,10 @@ mod tests {
 			let dest = Location::new(2, BridgedUniversalDestination::get());
 
 			// open bridge
-			let (_, expected_lane_id) = open_lane();
+			let origin = OpenBridgeOrigin::sibling_parachain_origin();
+			let origin_as_location =
+				OpenBridgeOriginOf::<TestRuntime, ()>::try_origin(origin.clone()).unwrap();
+			let (_, expected_lane_id) = open_lane(origin);
 
 			// check before - no messages
 			assert_eq!(
@@ -662,18 +664,24 @@ mod tests {
 			);
 
 			// send `ExportMessage(message)` by `UnpaidRemoteExporter`.
-			TestExportXcmWithXcmOverBridge::set_origin_for_execute(SiblingLocation::get());
+			ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location);
 			assert_ok!(send_xcm::<
 				UnpaidRemoteExporter<
 					NetworkExportTable<BridgeTable>,
-					TestExportXcmWithXcmOverBridge,
+					ExecuteXcmOverSendXcm,
 					UniversalLocation,
 				>,
 			>(dest.clone(), Xcm::<()>::default()));
 
+			// we need to set `UniversalLocation` for `sibling_parachain_origin` for
+			// `XcmOverBridgeWrappedWithExportMessageRouterInstance`.
+			ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get()));
 			// send `ExportMessage(message)` by `pallet_xcm_bridge_hub_router`.
-			TestExportXcmWithXcmOverBridge::set_origin_for_execute(SiblingLocation::get());
-			assert_ok!(send_xcm::<XcmOverBridgeRouter>(dest.clone(), Xcm::<()>::default()));
+			ExecuteXcmOverSendXcm::set_origin_for_execute(SiblingLocation::get());
+			assert_ok!(send_xcm::<XcmOverBridgeWrappedWithExportMessageRouter>(
+				dest.clone(),
+				Xcm::<()>::default()
+			));
 
 			// check after - a message ready to be relayed
 			assert_eq!(
@@ -765,7 +773,7 @@ mod tests {
 			);
 
 			// ok
-			let _ = open_lane();
+			let _ = open_lane(OpenBridgeOrigin::sibling_parachain_origin());
 			let mut dest_wrapper = Some(bridged_relative_destination());
 			assert_ok!(XcmOverBridge::validate(
 				BridgedRelayNetwork::get(),
@@ -780,4 +788,77 @@ mod tests {
 			assert_eq!(None, dest_wrapper);
 		});
 	}
+
+	#[test]
+	fn congestion_with_pallet_xcm_bridge_hub_router_works() {
+		run_test(|| {
+			// valid routable destination
+			let dest = Location::new(2, BridgedUniversalDestination::get());
+
+			fn router_bridge_state() -> pallet_xcm_bridge_hub_router::BridgeState {
+				pallet_xcm_bridge_hub_router::Bridge::<
+					TestRuntime,
+					XcmOverBridgeWrappedWithExportMessageRouterInstance,
+				>::get()
+			}
+
+			// open two bridges
+			let origin = OpenBridgeOrigin::sibling_parachain_origin();
+			let origin_as_location =
+				OpenBridgeOriginOf::<TestRuntime, ()>::try_origin(origin.clone()).unwrap();
+			let (bridge_1, expected_lane_id_1) = open_lane(origin);
+
+			// we need to set `UniversalLocation` for `sibling_parachain_origin` for
+			// `XcmOverBridgeWrappedWithExportMessageRouterInstance`.
+			ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get()));
+
+			// check before
+			// bridges are opened
+			assert_eq!(
+				XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state,
+				BridgeState::Opened
+			);
+
+			// the router is uncongested
+			assert!(!router_bridge_state().is_congested);
+			assert!(!TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id()));
+			assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
+
+			// make bridges congested with sending too much messages
+			for _ in 1..(OUTBOUND_LANE_CONGESTED_THRESHOLD + 2) {
+				// send `ExportMessage(message)` by `pallet_xcm_bridge_hub_router`.
+				ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location.clone());
+				assert_ok!(send_xcm::<XcmOverBridgeWrappedWithExportMessageRouter>(
+					dest.clone(),
+					Xcm::<()>::default()
+				));
+			}
+
+			// checks after
+			// bridges are suspended
+			assert_eq!(
+				XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state,
+				BridgeState::Suspended,
+			);
+			// the router is congested
+			assert!(router_bridge_state().is_congested);
+			assert!(TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id()));
+			assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
+
+			// make bridges uncongested to trigger resume signal
+			XcmOverBridge::on_bridge_messages_delivered(
+				expected_lane_id_1,
+				OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
+			);
+
+			// bridge is again opened
+			assert_eq!(
+				XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state,
+				BridgeState::Opened
+			);
+			// the router is uncongested
+			assert!(!router_bridge_state().is_congested);
+			assert!(TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
+		})
+	}
 }
diff --git a/bridges/modules/xcm-bridge-hub/src/lib.rs b/bridges/modules/xcm-bridge-hub/src/lib.rs
index 1b2536598a202892052d395284d88c682448c801..682db811efa77367d1cb54a14641c4ad8cd93e4c 100644
--- a/bridges/modules/xcm-bridge-hub/src/lib.rs
+++ b/bridges/modules/xcm-bridge-hub/src/lib.rs
@@ -145,8 +145,8 @@
 
 use bp_messages::{LaneState, MessageNonce};
 use bp_runtime::{AccountIdOf, BalanceOf, RangeInclusiveExt};
-pub use bp_xcm_bridge_hub::{Bridge, BridgeId, BridgeState};
-use bp_xcm_bridge_hub::{BridgeLocations, BridgeLocationsError, LocalXcmChannelManager};
+pub use bp_xcm_bridge_hub::{Bridge, BridgeId, BridgeState, LocalXcmChannelManager};
+use bp_xcm_bridge_hub::{BridgeLocations, BridgeLocationsError};
 use frame_support::{traits::fungible::MutateHold, DefaultNoBound};
 use frame_system::Config as SystemConfig;
 use pallet_bridge_messages::{Config as BridgeMessagesConfig, LanesManagerError};
diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs
index 9f06b99ef6d56c20afa298291e6764e5a422fd10..d186507dab1792960ba225fa9d9f16d9ead4f667 100644
--- a/bridges/modules/xcm-bridge-hub/src/mock.rs
+++ b/bridges/modules/xcm-bridge-hub/src/mock.rs
@@ -24,10 +24,10 @@ use bp_messages::{
 };
 use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, HashOf};
 use bp_xcm_bridge_hub::{BridgeId, LocalXcmChannelManager};
-use codec::Encode;
+use codec::{Decode, Encode};
 use frame_support::{
 	assert_ok, derive_impl, parameter_types,
-	traits::{EnsureOrigin, Equals, Everything, OriginTrait},
+	traits::{EnsureOrigin, Equals, Everything, Get, OriginTrait},
 	weights::RuntimeDbWeight,
 };
 use polkadot_parachain_primitives::primitives::Sibling;
@@ -44,7 +44,7 @@ use xcm_builder::{
 	InspectMessageQueues, NetworkExportTable, NetworkExportTableItem, ParentIsPreset,
 	SiblingParachainConvertsVia,
 };
-use xcm_executor::XcmExecutor;
+use xcm_executor::{traits::ConvertOrigin, XcmExecutor};
 
 pub type AccountId = AccountId32;
 pub type Balance = u64;
@@ -63,7 +63,7 @@ frame_support::construct_runtime! {
 		Balances: pallet_balances::{Pallet, Event<T>},
 		Messages: pallet_bridge_messages::{Pallet, Call, Event<T>},
 		XcmOverBridge: pallet_xcm_bridge_hub::{Pallet, Call, HoldReason, Event<T>},
-		XcmOverBridgeRouter: pallet_xcm_bridge_hub_router,
+		XcmOverBridgeWrappedWithExportMessageRouter: pallet_xcm_bridge_hub_router = 57,
 	}
 }
 
@@ -208,17 +208,27 @@ impl pallet_xcm_bridge_hub::Config for TestRuntime {
 	type BlobDispatcher = TestBlobDispatcher;
 }
 
-impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime {
+/// A router instance simulates a scenario where the router is deployed on a different chain than
+/// the `MessageExporter`. This means that the router sends an `ExportMessage`.
+pub type XcmOverBridgeWrappedWithExportMessageRouterInstance = ();
+impl pallet_xcm_bridge_hub_router::Config<XcmOverBridgeWrappedWithExportMessageRouterInstance>
+	for TestRuntime
+{
 	type RuntimeEvent = RuntimeEvent;
 	type WeightInfo = ();
 
-	type UniversalLocation = UniversalLocation;
+	type UniversalLocation = ExportMessageOriginUniversalLocation;
 	type SiblingBridgeHubLocation = BridgeHubLocation;
 	type BridgedNetworkId = BridgedRelayNetwork;
 	type Bridges = NetworkExportTable<BridgeTable>;
 	type DestinationVersion = AlwaysLatest;
 
-	type ToBridgeHubSender = TestExportXcmWithXcmOverBridge;
+	// We convert to root `here` location with `BridgeHubLocationXcmOriginAsRoot`
+	type BridgeHubOrigin = frame_system::EnsureRoot<AccountId>;
+	// **Note**: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which
+	// calls the `ExportXcm` implementation of `pallet_xcm_bridge_hub` as the
+	// `MessageExporter`.
+	type ToBridgeHubSender = ExecuteXcmOverSendXcm;
 	type LocalXcmChannelManager = TestLocalXcmChannelManager;
 
 	type ByteFee = ConstU128<0>;
@@ -230,7 +240,7 @@ impl xcm_executor::Config for XcmConfig {
 	type RuntimeCall = RuntimeCall;
 	type XcmSender = ();
 	type AssetTransactor = ();
-	type OriginConverter = ();
+	type OriginConverter = BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>;
 	type IsReserve = ();
 	type IsTeleporter = ();
 	type UniversalLocation = UniversalLocation;
@@ -270,8 +280,8 @@ thread_local! {
 ///
 /// Note: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which calls the
 /// `ExportXcm` implementation of `pallet_xcm_bridge_hub` as `MessageExporter`.
-pub struct TestExportXcmWithXcmOverBridge;
-impl SendXcm for TestExportXcmWithXcmOverBridge {
+pub struct ExecuteXcmOverSendXcm;
+impl SendXcm for ExecuteXcmOverSendXcm {
 	type Ticket = Xcm<()>;
 
 	fn validate(
@@ -298,7 +308,7 @@ impl SendXcm for TestExportXcmWithXcmOverBridge {
 		Ok(hash)
 	}
 }
-impl InspectMessageQueues for TestExportXcmWithXcmOverBridge {
+impl InspectMessageQueues for ExecuteXcmOverSendXcm {
 	fn clear_messages() {
 		todo!()
 	}
@@ -307,12 +317,51 @@ impl InspectMessageQueues for TestExportXcmWithXcmOverBridge {
 		todo!()
 	}
 }
-impl TestExportXcmWithXcmOverBridge {
+impl ExecuteXcmOverSendXcm {
 	pub fn set_origin_for_execute(origin: Location) {
 		EXECUTE_XCM_ORIGIN.with(|o| *o.borrow_mut() = Some(origin));
 	}
 }
 
+/// A dynamic way to set different universal location for the origin which sends `ExportMessage`.
+pub struct ExportMessageOriginUniversalLocation;
+impl ExportMessageOriginUniversalLocation {
+	pub(crate) fn set(universal_location: Option<InteriorLocation>) {
+		EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| *o.borrow_mut() = universal_location);
+	}
+}
+impl Get<InteriorLocation> for ExportMessageOriginUniversalLocation {
+	fn get() -> InteriorLocation {
+		EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| {
+			o.borrow()
+				.clone()
+				.expect("`EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION` is not set!")
+		})
+	}
+}
+thread_local! {
+	pub static EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION: RefCell<Option<InteriorLocation>> = RefCell::new(None);
+}
+
+pub struct BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>(
+	sp_std::marker::PhantomData<RuntimeOrigin>,
+);
+impl<RuntimeOrigin: OriginTrait> ConvertOrigin<RuntimeOrigin>
+	for BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>
+{
+	fn convert_origin(
+		origin: impl Into<Location>,
+		kind: OriginKind,
+	) -> Result<RuntimeOrigin, Location> {
+		let origin = origin.into();
+		if kind == OriginKind::Xcm && origin.eq(&BridgeHubLocation::get()) {
+			Ok(RuntimeOrigin::root())
+		} else {
+			Err(origin)
+		}
+	}
+}
+
 /// Type for specifying how a `Location` can be converted into an `AccountId`. This is used
 /// when determining ownership of accounts for asset transacting and when attempting to use XCM
 /// `Transact` in order to determine the dispatch Origin.
@@ -396,6 +445,9 @@ impl EnsureOrigin<RuntimeOrigin> for OpenBridgeOrigin {
 	}
 }
 
+pub(crate) type OpenBridgeOriginOf<T, I> =
+	<T as pallet_xcm_bridge_hub::Config<I>>::OpenBridgeOrigin;
+
 pub struct TestLocalXcmChannelManager;
 
 impl TestLocalXcmChannelManager {
@@ -403,30 +455,82 @@ impl TestLocalXcmChannelManager {
 		frame_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Congested", &true);
 	}
 
-	pub fn is_bridge_suspened() -> bool {
-		frame_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Suspended")
+	fn suspended_key(bridge: &BridgeId) -> Vec<u8> {
+		[b"TestLocalXcmChannelManager.Suspended", bridge.encode().as_slice()].concat()
+	}
+	fn resumed_key(bridge: &BridgeId) -> Vec<u8> {
+		[b"TestLocalXcmChannelManager.Resumed", bridge.encode().as_slice()].concat()
+	}
+
+	pub fn is_bridge_suspended(bridge: &BridgeId) -> bool {
+		frame_support::storage::unhashed::get_or_default(&Self::suspended_key(bridge))
 	}
 
-	pub fn is_bridge_resumed() -> bool {
-		frame_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Resumed")
+	pub fn is_bridge_resumed(bridge: &BridgeId) -> bool {
+		frame_support::storage::unhashed::get_or_default(&Self::resumed_key(bridge))
+	}
+
+	fn build_congestion_message(bridge: &BridgeId, is_congested: bool) -> Vec<Instruction<()>> {
+		use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall;
+		#[allow(clippy::large_enum_variant)]
+		#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, scale_info::TypeInfo)]
+		enum Call {
+			#[codec(index = 57)]
+			XcmOverBridgeWrappedWithExportMessageRouter(XcmBridgeHubRouterCall),
+		}
+
+		sp_std::vec![
+			UnpaidExecution { weight_limit: Unlimited, check_origin: None },
+			Transact {
+				origin_kind: OriginKind::Xcm,
+				fallback_max_weight: None,
+				call: Call::XcmOverBridgeWrappedWithExportMessageRouter(
+					XcmBridgeHubRouterCall::report_bridge_status {
+						bridge_id: bridge.inner(),
+						is_congested,
+					}
+				)
+				.encode()
+				.into(),
+			},
+			ExpectTransactStatus(MaybeErrorCode::Success),
+		]
+	}
+
+	fn report_bridge_status(
+		local_origin: &Location,
+		bridge: &BridgeId,
+		is_congested: bool,
+		key: Vec<u8>,
+	) -> Result<(), SendError> {
+		// send as BridgeHub would send to sibling chain
+		ExecuteXcmOverSendXcm::set_origin_for_execute(BridgeHubLocation::get());
+		let result = send_xcm::<ExecuteXcmOverSendXcm>(
+			local_origin.clone(),
+			Self::build_congestion_message(&bridge, is_congested).into(),
+		);
+
+		if result.is_ok() {
+			frame_support::storage::unhashed::put(&key, &true);
+		}
+
+		result.map(|_| ())
 	}
 }
 
 impl LocalXcmChannelManager for TestLocalXcmChannelManager {
-	type Error = ();
+	type Error = SendError;
 
 	fn is_congested(_with: &Location) -> bool {
 		frame_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Congested")
 	}
 
-	fn suspend_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> {
-		frame_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Suspended", &true);
-		Ok(())
+	fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
+		Self::report_bridge_status(local_origin, &bridge, true, Self::suspended_key(&bridge))
 	}
 
-	fn resume_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> {
-		frame_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Resumed", &true);
-		Ok(())
+	fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
+		Self::report_bridge_status(local_origin, &bridge, false, Self::resumed_key(&bridge))
 	}
 }
 
diff --git a/bridges/primitives/xcm-bridge-hub/src/lib.rs b/bridges/primitives/xcm-bridge-hub/src/lib.rs
index 63beb1bc30410c4ba84334953b0e302d4e2e1406..471cf402c34fbdc6d2b47070ce19736d10669884 100644
--- a/bridges/primitives/xcm-bridge-hub/src/lib.rs
+++ b/bridges/primitives/xcm-bridge-hub/src/lib.rs
@@ -87,6 +87,11 @@ impl BridgeId {
 				.into(),
 		)
 	}
+
+	/// Access the inner representation.
+	pub fn inner(&self) -> H256 {
+		self.0
+	}
 }
 
 impl core::fmt::Debug for BridgeId {
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
index c5cf62e1c2c2826cb0dcbc5899f8be8db2f2f56e..fb56e6fd6e687560bdc2f54d0fc1bacf83b6e626 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
@@ -62,7 +62,8 @@ use frame_support::{
 	ord_parameter_types, parameter_types,
 	traits::{
 		fungible, fungibles, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, ConstBool,
-		ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, InstanceFilter, TransformOrigin,
+		ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, InstanceFilter,
+		TransformOrigin,
 	},
 	weights::{ConstantMultiplier, Weight, WeightToFee as _},
 	BoundedVec, PalletId,
@@ -933,6 +934,10 @@ impl pallet_xcm_bridge_hub_router::Config<ToWestendXcmRouterInstance> for Runtim
 	type Bridges = xcm_config::bridging::NetworkExportTable;
 	type DestinationVersion = PolkadotXcm;
 
+	type BridgeHubOrigin = frame_support::traits::EitherOfDiverse<
+		EnsureRoot<AccountId>,
+		EnsureXcm<Equals<Self::SiblingBridgeHubLocation>>,
+	>;
 	type ToBridgeHubSender = XcmpQueue;
 	type LocalXcmChannelManager =
 		cumulus_pallet_xcmp_queue::bridging::InAndOutXcmpChannelStatusProvider<Runtime>;
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs
index 00ecf239428f03c9b967b975bdc5b0835ccbf259..9a75428ada8b1c5b6295982c92dfa42058117cd9 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs
@@ -17,9 +17,9 @@
 //! Autogenerated weights for `pallet_xcm_bridge_hub_router`
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
-//! DATE: 2024-08-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2024-12-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! HOSTNAME: `runner-acd6uxux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
 //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024
 
 // Executed Command:
@@ -52,14 +52,14 @@ impl<T: frame_system::Config> pallet_xcm_bridge_hub_router::WeightInfo for Weigh
 	/// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: Some(4002), added: 4497, mode: `MaxEncodedLen`)
 	/// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0)
 	/// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`)
-	/// Storage: `ToWestendXcmRouter::DeliveryFeeFactor` (r:1 w:1)
-	/// Proof: `ToWestendXcmRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	/// Storage: `ToWestendXcmRouter::Bridge` (r:1 w:1)
+	/// Proof: `ToWestendXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`)
 	fn on_initialize_when_non_congested() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `153`
+		//  Measured:  `154`
 		//  Estimated: `5487`
-		// Minimum execution time: 12_993_000 picoseconds.
-		Weight::from_parts(13_428_000, 0)
+		// Minimum execution time: 13_884_000 picoseconds.
+		Weight::from_parts(14_312_000, 0)
 			.saturating_add(Weight::from_parts(0, 5487))
 			.saturating_add(T::DbWeight::get().reads(3))
 			.saturating_add(T::DbWeight::get().writes(1))
@@ -72,9 +72,21 @@ impl<T: frame_system::Config> pallet_xcm_bridge_hub_router::WeightInfo for Weigh
 		// Proof Size summary in bytes:
 		//  Measured:  `144`
 		//  Estimated: `5487`
-		// Minimum execution time: 6_305_000 picoseconds.
-		Weight::from_parts(6_536_000, 0)
+		// Minimum execution time: 6_909_000 picoseconds.
+		Weight::from_parts(7_115_000, 0)
 			.saturating_add(Weight::from_parts(0, 5487))
 			.saturating_add(T::DbWeight::get().reads(2))
 	}
+	/// Storage: `ToWestendXcmRouter::Bridge` (r:1 w:1)
+	/// Proof: `ToWestendXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`)
+	fn report_bridge_status() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `150`
+		//  Estimated: `1502`
+		// Minimum execution time: 12_394_000 picoseconds.
+		Weight::from_parts(12_883_000, 0)
+			.saturating_add(Weight::from_parts(0, 1502))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
 }
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs
index 5da8b45417a3baafdccf3eab73ddb3f5224a2fb2..1e8a2be4a7779369df34e792e8a392943ba21c93 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs
@@ -27,7 +27,7 @@ use asset_hub_rococo_runtime::{
 	AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection,
 	ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase,
 	MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
-	SessionKeys, TrustBackedAssetsInstance, XcmpQueue,
+	SessionKeys, ToWestendXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue,
 };
 use asset_test_utils::{
 	test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys,
@@ -1242,6 +1242,58 @@ mod asset_hub_rococo_tests {
 		)
 	}
 
+	#[test]
+	fn report_bridge_status_from_xcm_bridge_router_for_westend_works() {
+		asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::<
+			Runtime,
+			AllPalletsWithoutSystem,
+			XcmConfig,
+			LocationToAccountId,
+			ToWestendXcmRouterInstance,
+		>(
+			collator_session_keys(),
+			bridging_to_asset_hub_westend,
+			|| bp_asset_hub_rococo::build_congestion_message(Default::default(), true).into(),
+			|| bp_asset_hub_rococo::build_congestion_message(Default::default(), false).into(),
+		)
+	}
+
+	#[test]
+	fn test_report_bridge_status_call_compatibility() {
+		// if this test fails, make sure `bp_asset_hub_rococo` has valid encoding
+		assert_eq!(
+			RuntimeCall::ToWestendXcmRouter(
+				pallet_xcm_bridge_hub_router::Call::report_bridge_status {
+					bridge_id: Default::default(),
+					is_congested: true,
+				}
+			)
+			.encode(),
+			bp_asset_hub_rococo::Call::ToWestendXcmRouter(
+				bp_asset_hub_rococo::XcmBridgeHubRouterCall::report_bridge_status {
+					bridge_id: Default::default(),
+					is_congested: true,
+				}
+			)
+			.encode()
+		);
+	}
+
+	#[test]
+	fn check_sane_weight_report_bridge_status_for_westend() {
+		use pallet_xcm_bridge_hub_router::WeightInfo;
+		let actual = <Runtime as pallet_xcm_bridge_hub_router::Config<
+			ToWestendXcmRouterInstance,
+		>>::WeightInfo::report_bridge_status();
+		let max_weight = bp_asset_hub_rococo::XcmBridgeHubRouterTransactCallMaxWeight::get();
+		assert!(
+			actual.all_lte(max_weight),
+			"max_weight: {:?} should be adjusted to actual {:?}",
+			max_weight,
+			actual
+		);
+	}
+
 	#[test]
 	fn reserve_transfer_native_asset_to_non_teleport_para_works() {
 		asset_test_utils::test_cases::reserve_transfer_native_asset_to_non_teleport_para_works::<
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
index d261de22c00e2a0447883646030eaca759f548e3..33c56130cb5caf1cfe791520d9ea3d9512a1f377 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
@@ -49,8 +49,8 @@ use frame_support::{
 			imbalance::ResolveAssetTo, nonfungibles_v2::Inspect, Fortitude::Polite,
 			Preservation::Expendable,
 		},
-		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, InstanceFilter,
-		Nothing, TransformOrigin,
+		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Equals,
+		InstanceFilter, Nothing, TransformOrigin,
 	},
 	weights::{ConstantMultiplier, Weight, WeightToFee as _},
 	BoundedVec, PalletId,
@@ -62,6 +62,7 @@ use frame_system::{
 use pallet_asset_conversion_tx_payment::SwapAssetAdapter;
 use pallet_nfts::{DestroyWitness, PalletFeatures};
 use pallet_revive::{evm::runtime::EthExtra, AddressMapper};
+use pallet_xcm::EnsureXcm;
 use parachains_common::{
 	impls::DealWithFees, message_queue::*, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance,
 	BlockNumber, CollectionId, Hash, Header, ItemId, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO,
@@ -930,6 +931,10 @@ impl pallet_xcm_bridge_hub_router::Config<ToRococoXcmRouterInstance> for Runtime
 	type Bridges = xcm_config::bridging::NetworkExportTable;
 	type DestinationVersion = PolkadotXcm;
 
+	type BridgeHubOrigin = frame_support::traits::EitherOfDiverse<
+		EnsureRoot<AccountId>,
+		EnsureXcm<Equals<Self::SiblingBridgeHubLocation>>,
+	>;
 	type ToBridgeHubSender = XcmpQueue;
 	type LocalXcmChannelManager =
 		cumulus_pallet_xcmp_queue::bridging::InAndOutXcmpChannelStatusProvider<Runtime>;
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs
index c0898012e9f32be0b6d6a09ae6e02a41fdf05a38..78aa839deacd2e529741b99c57c8c134393a830d 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs
@@ -17,9 +17,9 @@
 //! Autogenerated weights for `pallet_xcm_bridge_hub_router`
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
-//! DATE: 2024-08-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2024-12-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! HOSTNAME: `runner-acd6uxux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
 //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024
 
 // Executed Command:
@@ -52,14 +52,14 @@ impl<T: frame_system::Config> pallet_xcm_bridge_hub_router::WeightInfo for Weigh
 	/// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: Some(4002), added: 4497, mode: `MaxEncodedLen`)
 	/// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0)
 	/// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`)
-	/// Storage: `ToRococoXcmRouter::DeliveryFeeFactor` (r:1 w:1)
-	/// Proof: `ToRococoXcmRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	/// Storage: `ToRococoXcmRouter::Bridge` (r:1 w:1)
+	/// Proof: `ToRococoXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`)
 	fn on_initialize_when_non_congested() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `225`
+		//  Measured:  `259`
 		//  Estimated: `5487`
-		// Minimum execution time: 13_483_000 picoseconds.
-		Weight::from_parts(13_862_000, 0)
+		// Minimum execution time: 14_643_000 picoseconds.
+		Weight::from_parts(14_992_000, 0)
 			.saturating_add(Weight::from_parts(0, 5487))
 			.saturating_add(T::DbWeight::get().reads(3))
 			.saturating_add(T::DbWeight::get().writes(1))
@@ -70,11 +70,23 @@ impl<T: frame_system::Config> pallet_xcm_bridge_hub_router::WeightInfo for Weigh
 	/// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`)
 	fn on_initialize_when_congested() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `111`
+		//  Measured:  `144`
 		//  Estimated: `5487`
-		// Minimum execution time: 5_078_000 picoseconds.
-		Weight::from_parts(5_233_000, 0)
+		// Minimum execution time: 5_367_000 picoseconds.
+		Weight::from_parts(5_604_000, 0)
 			.saturating_add(Weight::from_parts(0, 5487))
 			.saturating_add(T::DbWeight::get().reads(2))
 	}
+	/// Storage: `ToRococoXcmRouter::Bridge` (r:1 w:1)
+	/// Proof: `ToRococoXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`)
+	fn report_bridge_status() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `150`
+		//  Estimated: `1502`
+		// Minimum execution time: 12_562_000 picoseconds.
+		Weight::from_parts(12_991_000, 0)
+			.saturating_add(Weight::from_parts(0, 1502))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
 }
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs
index 5d0f843554a11572b21a67037f3dcf088039a823..bdac1050207ec174ffc7999234fb00e48ee9f7c6 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs
@@ -27,7 +27,7 @@ use asset_hub_westend_runtime::{
 	AllPalletsWithoutSystem, Assets, Balances, ExistentialDeposit, ForeignAssets,
 	ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem,
 	PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys,
-	TrustBackedAssetsInstance, XcmpQueue,
+	ToRococoXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue,
 };
 pub use asset_hub_westend_runtime::{AssetConversion, AssetDeposit, CollatorSelection, System};
 use asset_test_utils::{
@@ -1250,6 +1250,56 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_suffic
 	)
 }
 
+#[test]
+fn report_bridge_status_from_xcm_bridge_router_for_rococo_works() {
+	asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::<
+		Runtime,
+		AllPalletsWithoutSystem,
+		XcmConfig,
+		LocationToAccountId,
+		ToRococoXcmRouterInstance,
+	>(
+		collator_session_keys(),
+		bridging_to_asset_hub_rococo,
+		|| bp_asset_hub_westend::build_congestion_message(Default::default(), true).into(),
+		|| bp_asset_hub_westend::build_congestion_message(Default::default(), false).into(),
+	)
+}
+
+#[test]
+fn test_report_bridge_status_call_compatibility() {
+	// if this test fails, make sure `bp_asset_hub_rococo` has valid encoding
+	assert_eq!(
+		RuntimeCall::ToRococoXcmRouter(pallet_xcm_bridge_hub_router::Call::report_bridge_status {
+			bridge_id: Default::default(),
+			is_congested: true,
+		})
+		.encode(),
+		bp_asset_hub_westend::Call::ToRococoXcmRouter(
+			bp_asset_hub_westend::XcmBridgeHubRouterCall::report_bridge_status {
+				bridge_id: Default::default(),
+				is_congested: true,
+			}
+		)
+		.encode()
+	)
+}
+
+#[test]
+fn check_sane_weight_report_bridge_status() {
+	use pallet_xcm_bridge_hub_router::WeightInfo;
+	let actual = <Runtime as pallet_xcm_bridge_hub_router::Config<
+		ToRococoXcmRouterInstance,
+	>>::WeightInfo::report_bridge_status();
+	let max_weight = bp_asset_hub_westend::XcmBridgeHubRouterTransactCallMaxWeight::get();
+	assert!(
+		actual.all_lte(max_weight),
+		"max_weight: {:?} should be adjusted to actual {:?}",
+		max_weight,
+		actual
+	);
+}
+
 #[test]
 fn change_xcm_bridge_hub_router_byte_fee_by_governance_works() {
 	asset_test_utils::test_cases::change_storage_constant_by_governance_works::<
diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs
index 4f144e24aa300471b1e14e62816032c2e64234f8..9b05f2d46dfb53744c9e1a21adc0f919ec42ad09 100644
--- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs
+++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs
@@ -551,10 +551,7 @@ pub fn report_bridge_status_from_xcm_bridge_router_works<
 					Weight::zero(),
 				);
 				assert_ok!(outcome.ensure_complete());
-				assert_eq!(
-					is_congested,
-					<<Runtime as pallet_xcm_bridge_hub_router::Config<XcmBridgeHubRouterInstance>>::LocalXcmChannelManager as pallet_xcm_bridge_hub_router::XcmChannelStatusProvider>::is_congested(&local_bridge_hub_location)
-				);
+				assert_eq!(is_congested, pallet_xcm_bridge_hub_router::Pallet::<Runtime, XcmBridgeHubRouterInstance>::bridge().is_congested);
 			};
 
 			report_bridge_status(true);
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml
index 4af8a9f4385047ef547d4cb58368e0b9a57d9cf6..af29cc0deb9196cf0130b26b80f455ff712cecdb 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml
@@ -96,6 +96,7 @@ bp-relayers = { workspace = true }
 bp-runtime = { workspace = true }
 bp-rococo = { workspace = true }
 bp-westend = { workspace = true }
+bp-xcm-bridge-hub-router = { workspace = true }
 pallet-bridge-grandpa = { workspace = true }
 pallet-bridge-messages = { workspace = true }
 pallet-bridge-parachains = { workspace = true }
@@ -140,6 +141,7 @@ std = [
 	"bp-rococo/std",
 	"bp-runtime/std",
 	"bp-westend/std",
+	"bp-xcm-bridge-hub-router/std",
 	"bridge-hub-common/std",
 	"bridge-runtime-common/std",
 	"codec/std",
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs
index 2710d033d64b6ffad1fe8ebe5b91c074ab0398d0..a14101eb454bda852ec53043655431047e4b5eb3 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs
@@ -24,14 +24,14 @@ use crate::{
 	weights,
 	xcm_config::UniversalLocation,
 	AccountId, Balance, Balances, BridgeWestendMessages, PolkadotXcm, Runtime, RuntimeEvent,
-	RuntimeHoldReason, XcmOverBridgeHubWestend, XcmRouter,
+	RuntimeHoldReason, XcmOverBridgeHubWestend, XcmRouter, XcmpQueue,
 };
 use bp_messages::{
 	source_chain::FromBridgedChainMessagesDeliveryProof,
 	target_chain::FromBridgedChainMessagesProof, LegacyLaneId,
 };
 use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge;
-use pallet_xcm_bridge_hub::XcmAsPlainPayload;
+use pallet_xcm_bridge_hub::{BridgeId, XcmAsPlainPayload};
 
 use frame_support::{parameter_types, traits::PalletInfoAccess};
 use frame_system::{EnsureNever, EnsureRoot};
@@ -157,11 +157,46 @@ impl pallet_xcm_bridge_hub::Config<XcmOverBridgeHubWestendInstance> for Runtime
 	type AllowWithoutBridgeDeposit =
 		RelayOrOtherSystemParachains<AllSiblingSystemParachains, Runtime>;
 
-	// TODO:(bridges-v2) - add `LocalXcmChannelManager` impl - https://github.com/paritytech/parity-bridges-common/issues/3047
-	type LocalXcmChannelManager = ();
+	type LocalXcmChannelManager = CongestionManager;
 	type BlobDispatcher = FromWestendMessageBlobDispatcher;
 }
 
+/// Implementation of `bp_xcm_bridge_hub::LocalXcmChannelManager` for congestion management.
+pub struct CongestionManager;
+impl pallet_xcm_bridge_hub::LocalXcmChannelManager for CongestionManager {
+	type Error = SendError;
+
+	fn is_congested(with: &Location) -> bool {
+		// This is used to check the inbound bridge queue/messages to determine if they can be
+		// dispatched and sent to the sibling parachain. Therefore, checking outbound `XcmpQueue`
+		// is sufficient here.
+		use bp_xcm_bridge_hub_router::XcmChannelStatusProvider;
+		cumulus_pallet_xcmp_queue::bridging::OutXcmpChannelStatusProvider::<Runtime>::is_congested(
+			with,
+		)
+	}
+
+	fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
+		// This bridge is intended for AH<>AH communication with a hard-coded/static lane,
+		// so `local_origin` is expected to represent only the local AH.
+		send_xcm::<XcmpQueue>(
+			local_origin.clone(),
+			bp_asset_hub_rococo::build_congestion_message(bridge.inner(), true).into(),
+		)
+		.map(|_| ())
+	}
+
+	fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
+		// This bridge is intended for AH<>AH communication with a hard-coded/static lane,
+		// so `local_origin` is expected to represent only the local AH.
+		send_xcm::<XcmpQueue>(
+			local_origin.clone(),
+			bp_asset_hub_rococo::build_congestion_message(bridge.inner(), false).into(),
+		)
+		.map(|_| ())
+	}
+}
+
 #[cfg(feature = "runtime-benchmarks")]
 pub(crate) fn open_bridge_for_benchmarks<R, XBHI, C>(
 	with: pallet_xcm_bridge_hub::LaneIdOf<R, XBHI>,
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml
index 637e7c71064091c4f2736bf2dfd9c2b25e7201bd..d437fa36bc7b31b564e6c728e2d26a9899b2a294 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml
@@ -95,6 +95,7 @@ bp-relayers = { workspace = true }
 bp-runtime = { workspace = true }
 bp-rococo = { workspace = true }
 bp-westend = { workspace = true }
+bp-xcm-bridge-hub-router = { workspace = true }
 pallet-bridge-grandpa = { workspace = true }
 pallet-bridge-messages = { workspace = true }
 pallet-bridge-parachains = { workspace = true }
@@ -137,6 +138,7 @@ std = [
 	"bp-rococo/std",
 	"bp-runtime/std",
 	"bp-westend/std",
+	"bp-xcm-bridge-hub-router/std",
 	"bridge-hub-common/std",
 	"bridge-runtime-common/std",
 	"codec/std",
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs
index cd34655131444af9d07afacd0e898a93b04821d5..24e5482b7b097c03bef64b47df2d93b9470080c5 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs
@@ -21,7 +21,7 @@ use crate::{
 	weights,
 	xcm_config::UniversalLocation,
 	AccountId, Balance, Balances, BridgeRococoMessages, PolkadotXcm, Runtime, RuntimeEvent,
-	RuntimeHoldReason, XcmOverBridgeHubRococo, XcmRouter,
+	RuntimeHoldReason, XcmOverBridgeHubRococo, XcmRouter, XcmpQueue,
 };
 use bp_messages::{
 	source_chain::FromBridgedChainMessagesDeliveryProof,
@@ -29,7 +29,7 @@ use bp_messages::{
 };
 use bp_parachains::SingleParaStoredHeaderDataBuilder;
 use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge;
-use pallet_xcm_bridge_hub::XcmAsPlainPayload;
+use pallet_xcm_bridge_hub::{BridgeId, XcmAsPlainPayload};
 
 use frame_support::{
 	parameter_types,
@@ -186,11 +186,46 @@ impl pallet_xcm_bridge_hub::Config<XcmOverBridgeHubRococoInstance> for Runtime {
 	type AllowWithoutBridgeDeposit =
 		RelayOrOtherSystemParachains<AllSiblingSystemParachains, Runtime>;
 
-	// TODO:(bridges-v2) - add `LocalXcmChannelManager` impl - https://github.com/paritytech/parity-bridges-common/issues/3047
-	type LocalXcmChannelManager = ();
+	type LocalXcmChannelManager = CongestionManager;
 	type BlobDispatcher = FromRococoMessageBlobDispatcher;
 }
 
+/// Implementation of `bp_xcm_bridge_hub::LocalXcmChannelManager` for congestion management.
+pub struct CongestionManager;
+impl pallet_xcm_bridge_hub::LocalXcmChannelManager for CongestionManager {
+	type Error = SendError;
+
+	fn is_congested(with: &Location) -> bool {
+		// This is used to check the inbound bridge queue/messages to determine if they can be
+		// dispatched and sent to the sibling parachain. Therefore, checking outbound `XcmpQueue`
+		// is sufficient here.
+		use bp_xcm_bridge_hub_router::XcmChannelStatusProvider;
+		cumulus_pallet_xcmp_queue::bridging::OutXcmpChannelStatusProvider::<Runtime>::is_congested(
+			with,
+		)
+	}
+
+	fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
+		// This bridge is intended for AH<>AH communication with a hard-coded/static lane,
+		// so `local_origin` is expected to represent only the local AH.
+		send_xcm::<XcmpQueue>(
+			local_origin.clone(),
+			bp_asset_hub_westend::build_congestion_message(bridge.inner(), true).into(),
+		)
+		.map(|_| ())
+	}
+
+	fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
+		// This bridge is intended for AH<>AH communication with a hard-coded/static lane,
+		// so `local_origin` is expected to represent only the local AH.
+		send_xcm::<XcmpQueue>(
+			local_origin.clone(),
+			bp_asset_hub_westend::build_congestion_message(bridge.inner(), false).into(),
+		)
+		.map(|_| ())
+	}
+}
+
 #[cfg(feature = "runtime-benchmarks")]
 pub(crate) fn open_bridge_for_benchmarks<R, XBHI, C>(
 	with: pallet_xcm_bridge_hub::LaneIdOf<R, XBHI>,
diff --git a/prdoc/pr_6781.prdoc b/prdoc/pr_6781.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..8090be42034180b1d54901be4fdeff1aa7b6d83c
--- /dev/null
+++ b/prdoc/pr_6781.prdoc
@@ -0,0 +1,28 @@
+title: Bridges - revert-back congestion mechanism
+
+doc:
+- audience: Runtime Dev
+  description: |-
+    With [permissionless lanes PR#4949](https://github.com/paritytech/polkadot-sdk/pull/4949), the congestion mechanism based on sending `Transact(report_bridge_status(is_congested))` from `pallet-xcm-bridge-hub` to `pallet-xcm-bridge-hub-router` was replaced with a congestion mechanism that relied on monitoring XCMP queues. However, this approach could cause issues, such as suspending the entire XCMP queue instead of isolating the affected bridge. This PR reverts back to using `report_bridge_status` as before.
+
+crates:
+- name: pallet-xcm-bridge-hub-router
+  bump: patch
+- name: pallet-xcm-bridge-hub
+  bump: patch
+- name: bp-xcm-bridge-hub
+  bump: patch
+- name: bp-asset-hub-rococo
+  bump: patch
+- name: bp-asset-hub-westend
+  bump: patch
+- name: asset-hub-rococo-runtime
+  bump: patch
+- name: asset-hub-westend-runtime
+  bump: patch
+- name: asset-test-utils
+  bump: patch
+- name: bridge-hub-rococo-runtime
+  bump: patch
+- name: bridge-hub-westend-runtime
+  bump: patch