diff --git a/Cargo.lock b/Cargo.lock
index 32a3c4b8f98cb76af89031757b7735aff1794994..96d388f7610f7bb05c0d5798ca38ffcc75a146a2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1651,6 +1651,8 @@ dependencies = [
  "frame-support",
  "parity-scale-codec",
  "scale-info",
+ "sp-core",
+ "staging-xcm",
 ]
 
 [[package]]
@@ -1661,6 +1663,8 @@ dependencies = [
  "frame-support",
  "parity-scale-codec",
  "scale-info",
+ "sp-core",
+ "staging-xcm",
 ]
 
 [[package]]
@@ -2055,6 +2059,7 @@ dependencies = [
  "bp-rococo",
  "bp-runtime",
  "bp-westend",
+ "bp-xcm-bridge-hub-router",
  "bridge-hub-common",
  "bridge-hub-test-utils",
  "bridge-runtime-common",
@@ -2247,6 +2252,7 @@ dependencies = [
  "bp-rococo",
  "bp-runtime",
  "bp-westend",
+ "bp-xcm-bridge-hub-router",
  "bridge-hub-common",
  "bridge-hub-test-utils",
  "bridge-runtime-common",
@@ -12018,6 +12024,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 2e4725e6d20092159b36067771bfcf4c754535d6..c23700c3711110d8e185cdb829ca4ecd84f221c2 100644
--- a/bridges/chains/chain-asset-hub-rococo/Cargo.toml
+++ b/bridges/chains/chain-asset-hub-rococo/Cargo.toml
@@ -17,7 +17,9 @@ workspace = true
 codec = { workspace = true }
 scale-info = { features = ["derive"], workspace = true }
 frame-support.workspace = true
+sp-core.workspace = true
 bp-xcm-bridge-hub-router.workspace = true
+xcm.workspace = true
 
 [features]
 default = ["std"]
@@ -26,4 +28,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..2f8b879226a16adc45371cd50d00e889bfe72708 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,
+			require_weight_at_most: 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 a69380c484614f352d085fcc27e274998990f5b6..9187a756830f2eace1f621ef199b06a358ade9f2 100644
--- a/bridges/chains/chain-asset-hub-westend/Cargo.toml
+++ b/bridges/chains/chain-asset-hub-westend/Cargo.toml
@@ -17,7 +17,9 @@ workspace = true
 codec = { workspace = true }
 scale-info = { features = ["derive"], workspace = true }
 frame-support.workspace = true
+sp-core.workspace = true
 bp-xcm-bridge-hub-router.workspace = true
+xcm.workspace = true
 
 [features]
 default = ["std"]
@@ -26,4 +28,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..bdf350cafd1010058603046c807f5766988b8459 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,
+			require_weight_at_most: 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 bb265e1925a20fbfb7b4f6ea480b2145ca75cc4f..559f7261a293508896049f8b8a81cd6e2199466b 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 b2bd37e99369d3053ac03da645ae48335d21237b..6ffb408fe1956850f260f9b7c5fb936f199ec963 100644
--- a/bridges/modules/xcm-bridge-hub/Cargo.toml
+++ b/bridges/modules/xcm-bridge-hub/Cargo.toml
@@ -28,18 +28,20 @@ xcm-builder.workspace = true
 xcm-executor.workspace = true
 
 [dev-dependencies]
-pallet-balances = { path = "../../../substrate/frame/balances", default-features = false }
-sp-io = { path = "../../../substrate/primitives/io", default-features = false }
-bp-runtime = { path = "../../primitives/runtime", default-features = false }
-bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
-pallet-xcm-bridge-hub-router = { path = "../xcm-bridge-hub-router", default-features = false }
-polkadot-parachain-primitives = { path = "../../../polkadot/parachain", default-features = false }
+pallet-balances = { workspace = true }
+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]
 default = ["std"]
 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 22c60fb4ad60bb56a1a7748b497c640a5775adac..198fd5a3549fcb352632c6bcdaabc7880929bd4d 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 6511b9fc5b04e6da2052853c49df716ed3b13ac9..8bffafa40afe3537ebbc5b21b185f939d51dfcc4 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,85 @@ 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,
+				require_weight_at_most: frame_support::weights::Weight::from_parts(
+					200_000_000,
+					6144
+				),
+				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 061e7a27506329029da1e62f7ddd56f516b5dd80..a08830d430fb91c68dbe0ea9b1de02a473e426e7 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 96dbc9c0a186dd25176f1d5d3a9e12f5f83cb90e..c481b9bb7fd6e8b59a8b453703cb5c56097ab9e7 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,
@@ -924,6 +925,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 6b0cf87a6f7a16e8a5e4af8a11d9bb9031f820fe..7e0226f144e68f478745dea1ddbae5b99b10d3ba 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,
@@ -1235,6 +1235,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 19baa921ff235def502970ba49451d5ab6a3c28f..bc11f8b1e7c831f649742b4363c2c98cb5cd1f4e 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
@@ -46,8 +46,8 @@ use frame_support::{
 	traits::{
 		fungible, fungibles,
 		tokens::{imbalance::ResolveAssetTo, nonfungibles_v2::Inspect},
-		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, InstanceFilter,
-		TransformOrigin,
+		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Equals,
+		InstanceFilter, TransformOrigin,
 	},
 	weights::{ConstantMultiplier, Weight, WeightToFee as _},
 	BoundedVec, PalletId,
@@ -58,6 +58,7 @@ use frame_system::{
 };
 use pallet_asset_conversion_tx_payment::SwapAssetAdapter;
 use pallet_nfts::{DestroyWitness, PalletFeatures};
+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,
@@ -917,6 +918,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 ad3c450eb375d1bb85da7230b3c9a30ec8fd911f..0fcdfd08af7dd9f76ca18fd7bf9972daafff9e8f 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs
@@ -28,7 +28,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::{
@@ -1242,6 +1242,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 d8676117474023cdb9bbb750248eb48cf59f2e32..e0988b03281c4369a633cbe33577028dab70fc4a 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 d92f790bac41c0e891bcfb24008a790fb841a781..fddbf99c65dbf5ce45dabe1d3b7ebf74fba02989 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml
@@ -91,6 +91,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
@@ -133,6 +134,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 eeb06d72a452d7f82e54d2b38a7772a28fb103c8..97a28f99af0a10dc05ddfeb00af3ec2cfa6eaf66 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};
@@ -156,11 +156,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>,
@@ -173,7 +208,7 @@ where
 		bp_runtime::AccountIdOf<pallet_xcm_bridge_hub::ThisChainOf<R, XBHI>>,
 	>,
 {
-	use pallet_xcm_bridge_hub::{Bridge, BridgeId, BridgeState};
+	use pallet_xcm_bridge_hub::{Bridge, BridgeState};
 	use sp_runtime::traits::Zero;
 	use xcm::VersionedInteriorLocation;
 
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 63177b9a5fe4a764f1e904f68f09fb208e472643..b1799608e32404cdf43a4f092ba9bfa3e5bec4a8 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml
@@ -85,6 +85,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
@@ -103,7 +104,6 @@ snowbridge-outbound-queue-runtime-api.workspace = true
 snowbridge-router-primitives.workspace = true
 snowbridge-runtime-common.workspace = true
 
-
 [dev-dependencies]
 bridge-hub-test-utils = { default-features = true, path = "../test-utils" }
 bridge-runtime-common = { features = ["integrity-test"], default-features = true, path = "../../../../../bridges/bin/runtime-common" }
@@ -126,6 +126,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 0a21dbe523992f976d67729fcd6f4c55e51328ca..39f64a82aceb721b725406a3c1c79713390d84ad 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,
@@ -185,11 +185,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>,
@@ -202,7 +237,7 @@ where
 		bp_runtime::AccountIdOf<pallet_xcm_bridge_hub::ThisChainOf<R, XBHI>>,
 	>,
 {
-	use pallet_xcm_bridge_hub::{Bridge, BridgeId, BridgeState};
+	use pallet_xcm_bridge_hub::{Bridge, BridgeState};
 	use sp_runtime::traits::Zero;
 	use xcm::VersionedInteriorLocation;
 
diff --git a/polkadot/node/network/collator-protocol/src/error.rs b/polkadot/node/network/collator-protocol/src/error.rs
index bc39656c2ff4b92e99856129d154557536468f69..57980b8911625c66ddc106db6dce00af18f2d6eb 100644
--- a/polkadot/node/network/collator-protocol/src/error.rs
+++ b/polkadot/node/network/collator-protocol/src/error.rs
@@ -100,9 +100,7 @@ impl SecondingError {
 		use SecondingError::*;
 		matches!(
 			self,
-			PersistedValidationDataMismatch |
-				CandidateHashMismatch |
-				ParentHeadDataMismatch
+			PersistedValidationDataMismatch | CandidateHashMismatch | ParentHeadDataMismatch
 		)
 	}
 }
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