diff --git a/polkadot/node/collation-generation/src/lib.rs b/polkadot/node/collation-generation/src/lib.rs
index d3155177553cc6e953e91dd11b14df64a292829d..a07537f2448f702d8b38ca0231fa3abbc55fcffe 100644
--- a/polkadot/node/collation-generation/src/lib.rs
+++ b/polkadot/node/collation-generation/src/lib.rs
@@ -333,6 +333,7 @@ async fn handle_new_activations<Context: SubsystemContext>(
 						persisted_validation_data_hash,
 						pov_hash,
 						erasure_root,
+						para_head: commitments.head_data.hash(),
 					},
 				};
 
@@ -738,6 +739,7 @@ mod tests {
 				persisted_validation_data_hash: expect_validation_data_hash,
 				pov_hash: expect_pov_hash,
 				erasure_root: Default::default(), // this isn't something we're checking right now
+				para_head: test_collation().head_data.hash(),
 			};
 
 			assert_eq!(sent_messages.len(), 1);
diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs
index eec666ded1203c17768bf962d5958ab00d00bd6a..47405a208e9ff383c0e4f2472a3b0028c72d4360 100644
--- a/polkadot/node/core/candidate-validation/src/lib.rs
+++ b/polkadot/node/core/candidate-validation/src/lib.rs
@@ -459,6 +459,10 @@ fn validate_candidate_exhaustive<B: ValidationBackend, S: SpawnNamed + 'static>(
 			Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(e.to_string()))),
 		Err(ValidationError::Internal(e)) => Err(ValidationFailed(e.to_string())),
 		Ok(res) => {
+			if res.head_data.hash() != descriptor.para_head {
+				return Ok(ValidationResult::Invalid(InvalidCandidate::ParaHeadHashMismatch));
+			}
+
 			let outputs = CandidateCommitments {
 				head_data: res.head_data,
 				upward_messages: res.upward_messages,
@@ -887,15 +891,17 @@ mod tests {
 		let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
 
 		let pov = PoV { block_data: BlockData(vec![1; 32]) };
+		let head_data = HeadData(vec![1, 1, 1]);
 
 		let mut descriptor = CandidateDescriptor::default();
 		descriptor.pov_hash = pov.hash();
+		descriptor.para_head = head_data.hash();
 		collator_sign(&mut descriptor, Sr25519Keyring::Alice);
 
 		assert!(perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov).is_ok());
 
 		let validation_result = WasmValidationResult {
-			head_data: HeadData(vec![1, 1, 1]),
+			head_data,
 			new_validation_code: Some(vec![2, 2, 2].into()),
 			upward_messages: Vec::new(),
 			horizontal_messages: Vec::new(),
diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs
index e2195f59e2a16bec13a661559bce848ae4abeb47..5fb3f49308fb1056c16fef1a638003cc9fd4efaa 100644
--- a/polkadot/node/primitives/src/lib.rs
+++ b/polkadot/node/primitives/src/lib.rs
@@ -150,6 +150,8 @@ pub enum InvalidCandidate {
 	HashMismatch,
 	/// Bad collator signature.
 	BadSignature,
+	/// Para head hash does not match.
+	ParaHeadHashMismatch,
 }
 
 /// Result of the validation of the candidate.
diff --git a/polkadot/parachain/src/primitives.rs b/polkadot/parachain/src/primitives.rs
index a9b8f13c800d19d68d5fa1b51a3219dd8eae72cb..991bc260fb08c39387b716b60a93f0837323b19e 100644
--- a/polkadot/parachain/src/primitives.rs
+++ b/polkadot/parachain/src/primitives.rs
@@ -38,6 +38,15 @@ pub use polkadot_core_primitives::BlockNumber as RelayChainBlockNumber;
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Default, Hash))]
 pub struct HeadData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
 
+#[cfg(feature = "std")]
+impl HeadData {
+	/// Returns the hash of this head data.
+	pub fn hash(&self) -> Hash {
+		use sp_runtime::traits::Hash;
+		sp_runtime::traits::BlakeTwo256::hash(&self.0)
+	}
+}
+
 /// Parachain validation code.
 #[derive(Default, PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, derive_more::From)]
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash))]
diff --git a/polkadot/primitives/src/v1.rs b/polkadot/primitives/src/v1.rs
index 30a4b392656080e656856277ea774b49024d1a47..cf5cc1533a8ad02c236484fa0092ac049d03a20b 100644
--- a/polkadot/primitives/src/v1.rs
+++ b/polkadot/primitives/src/v1.rs
@@ -211,6 +211,8 @@ pub struct CandidateDescriptor<H = Hash> {
 	/// Signature on blake2-256 of components of this receipt:
 	/// The parachain index, the relay parent, the validation data hash, and the pov_hash.
 	pub signature: CollatorSignature,
+	/// Hash of the para header that is being generated by this candidate.
+	pub para_head: Hash,
 }
 
 impl<H: AsRef<[u8]>> CandidateDescriptor<H> {
diff --git a/polkadot/roadmap/implementers-guide/src/types/candidate.md b/polkadot/roadmap/implementers-guide/src/types/candidate.md
index 1e65daab532f94f0ccf74122a027ba8df3c0dc59..0ba556a20d1bc00e8bcb9f49e88c359500379d3c 100644
--- a/polkadot/roadmap/implementers-guide/src/types/candidate.md
+++ b/polkadot/roadmap/implementers-guide/src/types/candidate.md
@@ -85,6 +85,8 @@ struct CandidateDescriptor {
 	/// Signature on blake2-256 of components of this receipt:
 	/// The parachain index, the relay parent, the validation data hash, and the pov_hash.
 	signature: CollatorSignature,
+	/// Hash of the para header that is being generated by this candidate.
+	para_head: Hash,
 }
 ```