diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs
index 468c596b02e18e0e1cdfbf7d2a3658be7ac7ceff..59f2a08177c62aa7672f09b71a6b62736200ee63 100644
--- a/bridges/bin/millau/runtime/src/lib.rs
+++ b/bridges/bin/millau/runtime/src/lib.rs
@@ -40,6 +40,7 @@ use crate::{
 };
 
 use beefy_primitives::{crypto::AuthorityId as BeefyId, mmr::MmrLeafVersion, ValidatorSet};
+use bp_runtime::{HeaderId, HeaderIdProvider};
 use bridge_runtime_common::messages::{
 	source::estimate_message_dispatch_and_delivery_fee, MessageBridge,
 };
@@ -56,9 +57,7 @@ use sp_mmr_primitives::{
 };
 use sp_runtime::{
 	create_runtime_str, generic, impl_opaque_keys,
-	traits::{
-		Block as BlockT, Header as HeaderT, IdentityLookup, Keccak256, NumberFor, OpaqueKeys,
-	},
+	traits::{Block as BlockT, IdentityLookup, Keccak256, NumberFor, OpaqueKeys},
 	transaction_validity::{TransactionSource, TransactionValidity},
 	ApplyExtrinsicResult, FixedPointNumber, FixedU128, Perquintill,
 };
@@ -818,19 +817,19 @@ impl_runtime_apis! {
 	}
 
 	impl bp_rialto::RialtoFinalityApi<Block> for Runtime {
-		fn best_finalized() -> Option<(bp_rialto::BlockNumber, bp_rialto::Hash)> {
-			BridgeRialtoGrandpa::best_finalized().map(|header| (header.number, header.hash()))
+		fn best_finalized() -> Option<HeaderId<bp_rialto::Hash, bp_rialto::BlockNumber>> {
+			BridgeRialtoGrandpa::best_finalized().map(|header| header.id())
 		}
 	}
 
 	impl bp_westend::WestendFinalityApi<Block> for Runtime {
-		fn best_finalized() -> Option<(bp_westend::BlockNumber, bp_westend::Hash)> {
-			BridgeWestendGrandpa::best_finalized().map(|header| (header.number, header.hash()))
+		fn best_finalized() -> Option<HeaderId<bp_westend::Hash, bp_westend::BlockNumber>> {
+			BridgeWestendGrandpa::best_finalized().map(|header| header.id())
 		}
 	}
 
 	impl bp_westend::WestmintFinalityApi<Block> for Runtime {
-		fn best_finalized() -> Option<(bp_westend::BlockNumber, bp_westend::Hash)> {
+		fn best_finalized() -> Option<HeaderId<bp_westend::Hash, bp_westend::BlockNumber>> {
 			// the parachains finality pallet is never decoding parachain heads, so it is
 			// only done in the integration code
 			use bp_westend::WESTMINT_PARACHAIN_ID;
@@ -839,12 +838,12 @@ impl_runtime_apis! {
 				WithWestendParachainsInstance,
 			>::best_parachain_head(WESTMINT_PARACHAIN_ID.into())?;
 			let head = bp_westend::Header::decode(&mut &encoded_head.0[..]).ok()?;
-			Some((*head.number(), head.hash()))
+			Some(head.id())
 		}
 	}
 
 	impl bp_rialto_parachain::RialtoParachainFinalityApi<Block> for Runtime {
-		fn best_finalized() -> Option<(bp_rialto::BlockNumber, bp_rialto::Hash)> {
+		fn best_finalized() -> Option<HeaderId<bp_rialto::Hash, bp_rialto::BlockNumber>> {
 			// the parachains finality pallet is never decoding parachain heads, so it is
 			// only done in the integration code
 			let encoded_head = pallet_bridge_parachains::Pallet::<
@@ -852,7 +851,7 @@ impl_runtime_apis! {
 				WithRialtoParachainsInstance,
 			>::best_parachain_head(bp_rialto_parachain::RIALTO_PARACHAIN_ID.into())?;
 			let head = bp_rialto_parachain::Header::decode(&mut &encoded_head.0[..]).ok()?;
-			Some((*head.number(), head.hash()))
+			Some(head.id())
 		}
 	}
 
diff --git a/bridges/bin/rialto-parachain/runtime/src/lib.rs b/bridges/bin/rialto-parachain/runtime/src/lib.rs
index 9f9a37b45775f85218233e2ac5283d76ff47f014..e96268d8aa8caeb819c0110c437a6c0c9cc178c3 100644
--- a/bridges/bin/rialto-parachain/runtime/src/lib.rs
+++ b/bridges/bin/rialto-parachain/runtime/src/lib.rs
@@ -49,6 +49,7 @@ use sp_version::NativeVersion;
 use sp_version::RuntimeVersion;
 
 // A few exports that help ease life for downstream crates.
+use bp_runtime::{HeaderId, HeaderIdProvider};
 pub use frame_support::{
 	construct_runtime, match_types, parameter_types,
 	traits::{Everything, IsInVec, Nothing, Randomness},
@@ -727,8 +728,8 @@ impl_runtime_apis! {
 	}
 
 	impl bp_millau::MillauFinalityApi<Block> for Runtime {
-		fn best_finalized() -> Option<(bp_millau::BlockNumber, bp_millau::Hash)> {
-			BridgeMillauGrandpa::best_finalized().map(|header| (header.number, header.hash()))
+		fn best_finalized() -> Option<HeaderId<bp_millau::Hash, bp_millau::BlockNumber>> {
+			BridgeMillauGrandpa::best_finalized().map(|header| header.id())
 		}
 	}
 
diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs
index 9f0708ce9fd81fa8cc46e5891b93d651f8e60301..56cbca221d456d96df200f597ca6ecd1e8113492 100644
--- a/bridges/bin/rialto/runtime/src/lib.rs
+++ b/bridges/bin/rialto/runtime/src/lib.rs
@@ -35,6 +35,7 @@ pub mod xcm_config;
 use crate::millau_messages::{ToMillauMessagePayload, WithMillauMessageBridge};
 
 use beefy_primitives::{crypto::AuthorityId as BeefyId, mmr::MmrLeafVersion, ValidatorSet};
+use bp_runtime::{HeaderId, HeaderIdProvider};
 use bridge_runtime_common::messages::{
 	source::estimate_message_dispatch_and_delivery_fee, MessageBridge,
 };
@@ -653,8 +654,8 @@ impl_runtime_apis! {
 	}
 
 	impl bp_millau::MillauFinalityApi<Block> for Runtime {
-		fn best_finalized() -> Option<(bp_millau::BlockNumber, bp_millau::Hash)> {
-			BridgeMillauGrandpa::best_finalized().map(|header| (header.number, header.hash()))
+		fn best_finalized() -> Option<HeaderId<bp_millau::Hash, bp_millau::BlockNumber>> {
+			BridgeMillauGrandpa::best_finalized().map(|header| header.id())
 		}
 	}
 
diff --git a/bridges/primitives/chain-kusama/src/lib.rs b/bridges/primitives/chain-kusama/src/lib.rs
index f3450a77dcbff87a1a1e5a42ee0d52bcb8b4bd09..a72e607ddb29e6f1a5e40ed4cf27674db1c76d4e 100644
--- a/bridges/primitives/chain-kusama/src/lib.rs
+++ b/bridges/primitives/chain-kusama/src/lib.rs
@@ -110,7 +110,7 @@ sp_api::decl_runtime_apis! {
 	/// Kusama runtime itself.
 	pub trait KusamaFinalityApi {
 		/// Returns number and hash of the best finalized header known to the bridge module.
-		fn best_finalized() -> Option<(BlockNumber, Hash)>;
+		fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>;
 	}
 
 	/// Outbound message lane API for messages that are sent to Kusama chain.
diff --git a/bridges/primitives/chain-millau/src/lib.rs b/bridges/primitives/chain-millau/src/lib.rs
index 6bdd9c53ee1332e7e92264e96e8ef77aa200d546..71a545e6ba7bf1a62051452cd24027e81049e8c7 100644
--- a/bridges/primitives/chain-millau/src/lib.rs
+++ b/bridges/primitives/chain-millau/src/lib.rs
@@ -274,7 +274,7 @@ sp_api::decl_runtime_apis! {
 	/// Millau runtime itself.
 	pub trait MillauFinalityApi {
 		/// Returns number and hash of the best finalized header known to the bridge module.
-		fn best_finalized() -> Option<(BlockNumber, Hash)>;
+		fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>;
 	}
 
 	/// Outbound message lane API for messages that are sent to Millau chain.
diff --git a/bridges/primitives/chain-polkadot/src/lib.rs b/bridges/primitives/chain-polkadot/src/lib.rs
index af195e511c4c8835918fd1f8acc371c6a0b13c7f..2fcfa1f03e883d6128e8cbe9bfc3777d01644fab 100644
--- a/bridges/primitives/chain-polkadot/src/lib.rs
+++ b/bridges/primitives/chain-polkadot/src/lib.rs
@@ -111,7 +111,7 @@ sp_api::decl_runtime_apis! {
 	/// Polkadot runtime itself.
 	pub trait PolkadotFinalityApi {
 		/// Returns number and hash of the best finalized header known to the bridge module.
-		fn best_finalized() -> Option<(BlockNumber, Hash)>;
+		fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>;
 	}
 
 	/// Outbound message lane API for messages that are sent to Polkadot chain.
diff --git a/bridges/primitives/chain-rialto-parachain/src/lib.rs b/bridges/primitives/chain-rialto-parachain/src/lib.rs
index 00402ae49d2f6461d8aa97da450cf8ce74af2bfa..baef2e403b4989c81747ea3553375f63e3ee9566 100644
--- a/bridges/primitives/chain-rialto-parachain/src/lib.rs
+++ b/bridges/primitives/chain-rialto-parachain/src/lib.rs
@@ -216,7 +216,7 @@ sp_api::decl_runtime_apis! {
 	/// RialtoParachain runtime itself.
 	pub trait RialtoParachainFinalityApi {
 		/// Returns number and hash of the best finalized header known to the bridge module.
-		fn best_finalized() -> Option<(BlockNumber, Hash)>;
+		fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>;
 	}
 
 	/// Outbound message lane API for messages that are sent to RialtoParachain chain.
diff --git a/bridges/primitives/chain-rialto/src/lib.rs b/bridges/primitives/chain-rialto/src/lib.rs
index e5d1abbc7e6305ab488cddf966816fd235376ae2..d242f6dc4ac574e6df1ba09ddbf74cd80fab4844 100644
--- a/bridges/primitives/chain-rialto/src/lib.rs
+++ b/bridges/primitives/chain-rialto/src/lib.rs
@@ -237,7 +237,7 @@ sp_api::decl_runtime_apis! {
 	/// Rialto runtime itself.
 	pub trait RialtoFinalityApi {
 		/// Returns number and hash of the best finalized header known to the bridge module.
-		fn best_finalized() -> Option<(BlockNumber, Hash)>;
+		fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>;
 	}
 
 	/// Outbound message lane API for messages that are sent to Rialto chain.
diff --git a/bridges/primitives/chain-rococo/src/lib.rs b/bridges/primitives/chain-rococo/src/lib.rs
index ad3190a12235f9a6e4fe70a6d89a4a3d83f33f12..45f1937eee58a6e4ddf956fcd7a70c213c8d477a 100644
--- a/bridges/primitives/chain-rococo/src/lib.rs
+++ b/bridges/primitives/chain-rococo/src/lib.rs
@@ -106,7 +106,7 @@ sp_api::decl_runtime_apis! {
 	/// Rococo runtime itself.
 	pub trait RococoFinalityApi {
 		/// Returns number and hash of the best finalized header known to the bridge module.
-		fn best_finalized() -> Option<(BlockNumber, Hash)>;
+		fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>;
 	}
 
 	/// Outbound message lane API for messages that are sent to Rococo chain.
diff --git a/bridges/primitives/chain-westend/src/lib.rs b/bridges/primitives/chain-westend/src/lib.rs
index 16c0252ea77ac67c8b5496b9ced520ac0a8b5369..e0ac5ad961f7beeb103f1d46b8134fbd6e7ad7b6 100644
--- a/bridges/primitives/chain-westend/src/lib.rs
+++ b/bridges/primitives/chain-westend/src/lib.rs
@@ -104,7 +104,7 @@ sp_api::decl_runtime_apis! {
 	/// Westend runtime itself.
 	pub trait WestendFinalityApi {
 		/// Returns number and hash of the best finalized header known to the bridge module.
-		fn best_finalized() -> Option<(BlockNumber, Hash)>;
+		fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>;
 	}
 
 	/// API for querying information about the finalized Westmint headers.
@@ -113,7 +113,7 @@ sp_api::decl_runtime_apis! {
 	/// Westmint runtime itself.
 	pub trait WestmintFinalityApi {
 		/// Returns number and hash of the best finalized header known to the bridge module.
-		fn best_finalized() -> Option<(BlockNumber, Hash)>;
+		fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>;
 	}
 }
 
diff --git a/bridges/primitives/chain-wococo/src/lib.rs b/bridges/primitives/chain-wococo/src/lib.rs
index 83d913db7fc139d39b3a3e367d16dce5e385b2ea..02276aa7dd8658fda11d90333e050cfba5e69914 100644
--- a/bridges/primitives/chain-wococo/src/lib.rs
+++ b/bridges/primitives/chain-wococo/src/lib.rs
@@ -63,7 +63,7 @@ sp_api::decl_runtime_apis! {
 	/// Wococo runtime itself.
 	pub trait WococoFinalityApi {
 		/// Returns number and hash of the best finalized header known to the bridge module.
-		fn best_finalized() -> Option<(BlockNumber, Hash)>;
+		fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>;
 	}
 
 	/// Outbound message lane API for messages that are sent to Wococo chain.
diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs
index ae7250c49bdf6bc23333e63973c384ce317929a9..a51327e89b802f2dd609d509cab18b46dab8806e 100644
--- a/bridges/primitives/runtime/src/lib.rs
+++ b/bridges/primitives/runtime/src/lib.rs
@@ -82,7 +82,7 @@ pub const ACCOUNT_DERIVATION_PREFIX: &[u8] = b"pallet-bridge/account-derivation/
 pub const ROOT_ACCOUNT_DERIVATION_PREFIX: &[u8] = b"pallet-bridge/account-derivation/root";
 
 /// Generic header Id.
-#[derive(RuntimeDebug, Default, Clone, Copy, Eq, Hash, PartialEq)]
+#[derive(RuntimeDebug, Default, Clone, Encode, Decode, Copy, Eq, Hash, PartialEq)]
 pub struct HeaderId<Hash, Number>(pub Number, pub Hash);
 
 /// Generic header id provider.
diff --git a/bridges/relays/lib-substrate-relay/src/messages_source.rs b/bridges/relays/lib-substrate-relay/src/messages_source.rs
index 6647376d93516b635e738c41d99ce746bbfffe23..95a3feab0c11a0ad5360d18edaa2acc2800e3047 100644
--- a/bridges/relays/lib-substrate-relay/src/messages_source.rs
+++ b/bridges/relays/lib-substrate-relay/src/messages_source.rs
@@ -562,17 +562,13 @@ where
 			Some(at_self_hash),
 		)
 		.await?;
-	let decoded_best_finalized_peer_on_self =
-		Option::<(BlockNumberOf<PeerChain>, HashOf<PeerChain>)>::decode(
-			&mut &encoded_best_finalized_peer_on_self.0[..],
-		)
-		.map_err(SubstrateError::ResponseParseFailed)?
-		.map(Ok)
-		.unwrap_or(Err(SubstrateError::BridgePalletIsNotInitialized))?;
-	let peer_on_self_best_finalized_id =
-		HeaderId(decoded_best_finalized_peer_on_self.0, decoded_best_finalized_peer_on_self.1);
 
-	Ok(peer_on_self_best_finalized_id)
+	Option::<HeaderId<HashOf<PeerChain>, BlockNumberOf<PeerChain>>>::decode(
+		&mut &encoded_best_finalized_peer_on_self.0[..],
+	)
+	.map_err(SubstrateError::ResponseParseFailed)?
+	.map(Ok)
+	.unwrap_or(Err(SubstrateError::BridgePalletIsNotInitialized))
 }
 
 fn make_message_details_map<C: Chain>(
diff --git a/bridges/relays/lib-substrate-relay/src/parachains/target.rs b/bridges/relays/lib-substrate-relay/src/parachains/target.rs
index c2c1498eea0d0a5ff36b6c70decffe16832d4230..a96c0ba0ab692ceb27a31ca408e473cae094fbc9 100644
--- a/bridges/relays/lib-substrate-relay/src/parachains/target.rs
+++ b/bridges/relays/lib-substrate-relay/src/parachains/target.rs
@@ -107,14 +107,13 @@ where
 				Some(at_block.1),
 			)
 			.await?;
-		let decoded_best_finalized_source_block =
-			Option::<(BlockNumberOf<P::SourceRelayChain>, HashOf<P::SourceRelayChain>)>::decode(
-				&mut &encoded_best_finalized_source_block.0[..],
-			)
-			.map_err(SubstrateError::ResponseParseFailed)?
-			.map(Ok)
-			.unwrap_or(Err(SubstrateError::BridgePalletIsNotInitialized))?;
-		Ok(HeaderId(decoded_best_finalized_source_block.0, decoded_best_finalized_source_block.1))
+
+		Option::<HeaderId<HashOf<P::SourceRelayChain>, BlockNumberOf<P::SourceRelayChain>>>::decode(
+			&mut &encoded_best_finalized_source_block.0[..],
+		)
+		.map_err(SubstrateError::ResponseParseFailed)?
+		.map(Ok)
+		.unwrap_or(Err(SubstrateError::BridgePalletIsNotInitialized))
 	}
 
 	async fn parachain_head(