diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs
index 21fff0e564e4c13b8bb0c401cf3f7cd152328702..574b0569875c65fb5972c308663080b4916d17d1 100644
--- a/bridges/bin/millau/runtime/src/lib.rs
+++ b/bridges/bin/millau/runtime/src/lib.rs
@@ -393,6 +393,7 @@ impl pallet_bridge_relayers::Config for Runtime {
 
 pub type RialtoGrandpaInstance = ();
 impl pallet_bridge_grandpa::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
 	type BridgedChain = bp_rialto::Rialto;
 	// This is a pretty unscientific cap.
 	//
@@ -405,6 +406,7 @@ impl pallet_bridge_grandpa::Config for Runtime {
 
 pub type WestendGrandpaInstance = pallet_bridge_grandpa::Instance1;
 impl pallet_bridge_grandpa::Config<WestendGrandpaInstance> for Runtime {
+	type RuntimeEvent = RuntimeEvent;
 	type BridgedChain = bp_westend::Westend;
 	type MaxRequests = ConstU32<50>;
 	type HeadersToKeep = ConstU32<{ bp_westend::DAYS }>;
@@ -559,11 +561,11 @@ construct_runtime!(
 
 		// Rialto bridge modules.
 		BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event<T>},
-		BridgeRialtoGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
+		BridgeRialtoGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage, Event<T>},
 		BridgeRialtoMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
 
 		// Westend bridge modules.
-		BridgeWestendGrandpa: pallet_bridge_grandpa::<Instance1>::{Pallet, Call, Config<T>, Storage},
+		BridgeWestendGrandpa: pallet_bridge_grandpa::<Instance1>::{Pallet, Call, Config<T>, Storage, Event<T>},
 		BridgeWestendParachains: pallet_bridge_parachains::<Instance1>::{Pallet, Call, Storage, Event<T>},
 
 		// RialtoParachain bridge modules.
diff --git a/bridges/bin/rialto-parachain/runtime/src/lib.rs b/bridges/bin/rialto-parachain/runtime/src/lib.rs
index d8e087c5a6a9e95936a0690d9d410224cfe9e234..2621b0096a7f470823b76c7b00884eaa89d8e733 100644
--- a/bridges/bin/rialto-parachain/runtime/src/lib.rs
+++ b/bridges/bin/rialto-parachain/runtime/src/lib.rs
@@ -525,6 +525,7 @@ impl pallet_bridge_relayers::Config for Runtime {
 
 pub type MillauGrandpaInstance = ();
 impl pallet_bridge_grandpa::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
 	type BridgedChain = bp_millau::Millau;
 	/// This is a pretty unscientific cap.
 	///
@@ -604,7 +605,7 @@ construct_runtime!(
 
 		// Millau bridge modules.
 		BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event<T>},
-		BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
+		BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage, Event<T>},
 		BridgeMillauMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
 	}
 );
diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs
index 9db181e2840354f5738088e91d9e4c86eb35195c..bdc205bec1405b12787069ff3fb43ebe8e98189d 100644
--- a/bridges/bin/rialto/runtime/src/lib.rs
+++ b/bridges/bin/rialto/runtime/src/lib.rs
@@ -390,6 +390,7 @@ impl pallet_bridge_relayers::Config for Runtime {
 
 pub type MillauGrandpaInstance = ();
 impl pallet_bridge_grandpa::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
 	type BridgedChain = bp_millau::Millau;
 	/// This is a pretty unscientific cap.
 	///
@@ -479,7 +480,7 @@ construct_runtime!(
 
 		// Millau bridge modules.
 		BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event<T>},
-		BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
+		BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage, Event<T>},
 		BridgeMillauMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
 
 		// Millau bridge modules (BEEFY based).
diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs
index c2cd8e9ba8308fccb316452e9849c48d5812f82e..056fd3a2ccf09d8de48ddc276753ec8b27251bd8 100644
--- a/bridges/bin/runtime-common/src/mock.rs
+++ b/bridges/bin/runtime-common/src/mock.rs
@@ -107,7 +107,7 @@ frame_support::construct_runtime! {
 		Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
 		TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
 		BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event<T>},
-		BridgeGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
+		BridgeGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage, Event<T>},
 		BridgeParachains: pallet_bridge_parachains::{Pallet, Call, Storage, Event<T>},
 		BridgeMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
 	}
@@ -194,6 +194,7 @@ impl pallet_transaction_payment::Config for TestRuntime {
 }
 
 impl pallet_bridge_grandpa::Config for TestRuntime {
+	type RuntimeEvent = RuntimeEvent;
 	type BridgedChain = BridgedUnderlyingChain;
 	type MaxRequests = ConstU32<50>;
 	type HeadersToKeep = ConstU32<8>;
diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs
index 2b9e49d4c95e114feb8ea1c98889ee251902cb5b..2d81f3106fc9df972bec54890674f5c2bfad3780 100644
--- a/bridges/modules/grandpa/src/lib.rs
+++ b/bridges/modules/grandpa/src/lib.rs
@@ -96,6 +96,10 @@ pub mod pallet {
 
 	#[pallet::config]
 	pub trait Config<I: 'static = ()>: frame_system::Config {
+		/// The overarching event type.
+		type RuntimeEvent: From<Event<Self, I>>
+			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
+
 		/// The chain we are bridging to here.
 		type BridgedChain: ChainWithGrandpa;
 
@@ -164,19 +168,19 @@ pub mod pallet {
 
 			ensure!(Self::request_count() < T::MaxRequests::get(), <Error<T, I>>::TooManyRequests);
 
-			let (hash, number) = (finality_target.hash(), finality_target.number());
+			let (hash, number) = (finality_target.hash(), *finality_target.number());
 			log::trace!(
 				target: LOG_TARGET,
 				"Going to try and finalize header {:?}",
 				finality_target
 			);
 
-			SubmitFinalityProofHelper::<T, I>::check_obsolete(*number)?;
+			SubmitFinalityProofHelper::<T, I>::check_obsolete(number)?;
 
 			let authority_set = <CurrentAuthoritySet<T, I>>::get();
 			let unused_proof_size = authority_set.unused_proof_size();
 			let set_id = authority_set.set_id;
-			verify_justification::<T, I>(&justification, hash, *number, authority_set.into())?;
+			verify_justification::<T, I>(&justification, hash, number, authority_set.into())?;
 
 			let is_authorities_change_enacted =
 				try_enact_authority_change::<T, I>(&finality_target, set_id)?;
@@ -212,6 +216,8 @@ pub mod pallet {
 			let actual_weight = pre_dispatch_weight
 				.set_proof_size(pre_dispatch_weight.proof_size().saturating_sub(unused_proof_size));
 
+			Self::deposit_event(Event::UpdatedBestFinalizedHeader { number, hash });
+
 			Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
 		}
 
@@ -370,6 +376,16 @@ pub mod pallet {
 		}
 	}
 
+	#[pallet::event]
+	#[pallet::generate_deposit(pub(super) fn deposit_event)]
+	pub enum Event<T: Config<I>, I: 'static = ()> {
+		/// Best finalized chain header has been updated to the header with given number and hash.
+		UpdatedBestFinalizedHeader {
+			number: BridgedBlockNumber<T, I>,
+			hash: BridgedBlockHash<T, I>,
+		},
+	}
+
 	#[pallet::error]
 	pub enum Error<T, I = ()> {
 		/// The given justification is invalid for the given header.
@@ -635,8 +651,8 @@ pub fn initialize_for_benchmarks<T: Config<I>, I: 'static>(header: BridgedHeader
 mod tests {
 	use super::*;
 	use crate::mock::{
-		run_test, test_header, RuntimeOrigin, TestBridgedChain, TestHeader, TestNumber,
-		TestRuntime, MAX_BRIDGED_AUTHORITIES,
+		run_test, test_header, RuntimeEvent as TestEvent, RuntimeOrigin, System, TestBridgedChain,
+		TestHeader, TestNumber, TestRuntime, MAX_BRIDGED_AUTHORITIES,
 	};
 	use bp_header_chain::BridgeGrandpaCall;
 	use bp_runtime::BasicOperatingMode;
@@ -649,10 +665,14 @@ mod tests {
 		assert_err, assert_noop, assert_ok, dispatch::PostDispatchInfo,
 		storage::generator::StorageValue,
 	};
+	use frame_system::{EventRecord, Phase};
 	use sp_core::Get;
 	use sp_runtime::{Digest, DigestItem, DispatchError};
 
 	fn initialize_substrate_bridge() {
+		System::set_block_number(1);
+		System::reset_events();
+
 		assert_ok!(init_with_origin(RuntimeOrigin::root()));
 	}
 
@@ -847,6 +867,18 @@ mod tests {
 			let header = test_header(1);
 			assert_eq!(<BestFinalized<TestRuntime>>::get().unwrap().1, header.hash());
 			assert!(<ImportedHeaders<TestRuntime>>::contains_key(header.hash()));
+
+			assert_eq!(
+				System::events(),
+				vec![EventRecord {
+					phase: Phase::Initialization,
+					event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader {
+						number: *header.number(),
+						hash: header.hash(),
+					}),
+					topics: vec![],
+				}],
+			);
 		})
 	}
 
diff --git a/bridges/modules/grandpa/src/mock.rs b/bridges/modules/grandpa/src/mock.rs
index acedfc3582c2498ef8b78488dcd003c9aec2260f..b10f5d86c3de2bcc49db3abb014db541800ae970 100644
--- a/bridges/modules/grandpa/src/mock.rs
+++ b/bridges/modules/grandpa/src/mock.rs
@@ -49,7 +49,7 @@ construct_runtime! {
 		UncheckedExtrinsic = UncheckedExtrinsic,
 	{
 		System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
-		Grandpa: grandpa::{Pallet, Call},
+		Grandpa: grandpa::{Pallet, Call, Event<T>},
 	}
 }
 
@@ -69,7 +69,7 @@ impl frame_system::Config for TestRuntime {
 	type AccountId = AccountId;
 	type Lookup = IdentityLookup<Self::AccountId>;
 	type Header = Header;
-	type RuntimeEvent = ();
+	type RuntimeEvent = RuntimeEvent;
 	type BlockHashCount = ConstU64<250>;
 	type Version = ();
 	type PalletInfo = PalletInfo;
@@ -94,6 +94,7 @@ parameter_types! {
 }
 
 impl grandpa::Config for TestRuntime {
+	type RuntimeEvent = RuntimeEvent;
 	type BridgedChain = TestBridgedChain;
 	type MaxRequests = MaxRequests;
 	type HeadersToKeep = HeadersToKeep;
diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs
index 20a9ef9b95bd398d401b0710a8216cb845a98828..e72e2aec8c202bde6c2e237ef150e41c9507cb55 100644
--- a/bridges/modules/parachains/src/lib.rs
+++ b/bridges/modules/parachains/src/lib.rs
@@ -735,14 +735,18 @@ mod tests {
 			},
 		)
 		.unwrap();
+
+		System::<TestRuntime>::set_block_number(1);
+		System::<TestRuntime>::reset_events();
 	}
 
-	fn proceed(num: RelayBlockNumber, state_root: RelayBlockHash) {
+	fn proceed(num: RelayBlockNumber, state_root: RelayBlockHash) -> ParaHash {
 		pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::on_initialize(
 			0,
 		);
 
 		let header = test_relay_header(num, state_root);
+		let hash = header.hash();
 		let justification = make_default_justification(&header);
 		assert_ok!(
 			pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof(
@@ -751,6 +755,8 @@ mod tests {
 				justification,
 			)
 		);
+
+		hash
 	}
 
 	fn prepare_parachain_heads_proof(
@@ -1010,7 +1016,7 @@ mod tests {
 			);
 
 			// import head#10 of parachain#1 at relay block #1
-			proceed(1, state_root_10);
+			let relay_1_hash = proceed(1, state_root_10);
 			assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10));
 			assert_eq!(
 				ParasInfo::<TestRuntime>::get(ParaId(1)),
@@ -1043,6 +1049,16 @@ mod tests {
 						}),
 						topics: vec![],
 					},
+					EventRecord {
+						phase: Phase::Initialization,
+						event: TestEvent::Grandpa1(
+							pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
+								number: 1,
+								hash: relay_1_hash,
+							}
+						),
+						topics: vec![],
+					},
 					EventRecord {
 						phase: Phase::Initialization,
 						event: TestEvent::Parachains(Event::UpdatedParachainHead {
@@ -1155,7 +1171,7 @@ mod tests {
 
 			// try to import head#0 of parachain#1 at relay block#1
 			// => call succeeds, but nothing is changed
-			proceed(1, state_root);
+			let relay_1_hash = proceed(1, state_root);
 			assert_ok!(import_parachain_1_head(1, state_root, parachains, proof));
 			assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
 			assert_eq!(
@@ -1169,6 +1185,16 @@ mod tests {
 						}),
 						topics: vec![],
 					},
+					EventRecord {
+						phase: Phase::Initialization,
+						event: TestEvent::Grandpa1(
+							pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
+								number: 1,
+								hash: relay_1_hash,
+							}
+						),
+						topics: vec![],
+					},
 					EventRecord {
 						phase: Phase::Initialization,
 						event: TestEvent::Parachains(Event::RejectedObsoleteParachainHead {
@@ -1193,7 +1219,7 @@ mod tests {
 			initialize(state_root_5);
 
 			// head#10 of parachain#1 at relay block#1
-			proceed(1, state_root_10);
+			let relay_1_hash = proceed(1, state_root_10);
 			assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10));
 			assert_eq!(
 				ParasInfo::<TestRuntime>::get(ParaId(1)),
@@ -1207,14 +1233,26 @@ mod tests {
 			);
 			assert_eq!(
 				System::<TestRuntime>::events(),
-				vec![EventRecord {
-					phase: Phase::Initialization,
-					event: TestEvent::Parachains(Event::UpdatedParachainHead {
-						parachain: ParaId(1),
-						parachain_head_hash: head_data(1, 10).hash(),
-					}),
-					topics: vec![],
-				}],
+				vec![
+					EventRecord {
+						phase: Phase::Initialization,
+						event: TestEvent::Grandpa1(
+							pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
+								number: 1,
+								hash: relay_1_hash,
+							}
+						),
+						topics: vec![],
+					},
+					EventRecord {
+						phase: Phase::Initialization,
+						event: TestEvent::Parachains(Event::UpdatedParachainHead {
+							parachain: ParaId(1),
+							parachain_head_hash: head_data(1, 10).hash(),
+						}),
+						topics: vec![],
+					}
+				],
 			);
 
 			// now try to import head#5 at relay block#0
@@ -1233,6 +1271,16 @@ mod tests {
 			assert_eq!(
 				System::<TestRuntime>::events(),
 				vec![
+					EventRecord {
+						phase: Phase::Initialization,
+						event: TestEvent::Grandpa1(
+							pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
+								number: 1,
+								hash: relay_1_hash,
+							}
+						),
+						topics: vec![],
+					},
 					EventRecord {
 						phase: Phase::Initialization,
 						event: TestEvent::Parachains(Event::UpdatedParachainHead {
diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs
index 83d347018e45ed35fd534f7515543af06cb73431..c19ce98eba2fe2f8d2f0ed44d60edb5e1abceaa2 100644
--- a/bridges/modules/parachains/src/mock.rs
+++ b/bridges/modules/parachains/src/mock.rs
@@ -150,8 +150,8 @@ construct_runtime! {
 		UncheckedExtrinsic = UncheckedExtrinsic,
 	{
 		System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
-		Grandpa1: pallet_bridge_grandpa::<Instance1>::{Pallet},
-		Grandpa2: pallet_bridge_grandpa::<Instance2>::{Pallet},
+		Grandpa1: pallet_bridge_grandpa::<Instance1>::{Pallet, Event<T>},
+		Grandpa2: pallet_bridge_grandpa::<Instance2>::{Pallet, Event<T>},
 		Parachains: pallet_bridge_parachains::{Call, Pallet, Event<T>},
 	}
 }
@@ -197,6 +197,7 @@ parameter_types! {
 }
 
 impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance1> for TestRuntime {
+	type RuntimeEvent = RuntimeEvent;
 	type BridgedChain = TestBridgedChain;
 	type MaxRequests = ConstU32<2>;
 	type HeadersToKeep = HeadersToKeep;
@@ -204,6 +205,7 @@ impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance1> for TestRun
 }
 
 impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance2> for TestRuntime {
+	type RuntimeEvent = RuntimeEvent;
 	type BridgedChain = TestBridgedChain;
 	type MaxRequests = ConstU32<2>;
 	type HeadersToKeep = HeadersToKeep;