diff --git a/.changelog/unreleased/features/832-header.md b/.changelog/unreleased/features/832-header.md
new file mode 100644
index 000000000..1d464f653
--- /dev/null
+++ b/.changelog/unreleased/features/832-header.md
@@ -0,0 +1,3 @@
+- `[tendermint-rpc]` Add support for the `/header` RPC endpoint. See
+  <https://docs.tendermint.com/v0.35/rpc/#/Info/header> for details 
+  ([#832](https://github.com/informalsystems/tendermint-rs/issues/832)).
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index a066462b4..311d0d335 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,9 @@
 # will have compiled files and executables
 target/
 
+# IDE specific files
+.idea/
+
 # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
 # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
 Cargo.lock
diff --git a/config/src/config.rs b/config/src/config.rs
index 5f254046d..e7a7e25ce 100644
--- a/config/src/config.rs
+++ b/config/src/config.rs
@@ -176,7 +176,7 @@ impl LogLevel {
     {
         self.components
             .get(key.as_ref())
-            .or_else(|| self.global.as_ref())
+            .or(self.global.as_ref())
             .map(AsRef::as_ref)
     }
 
diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs
index e90168c55..eab281b0f 100644
--- a/light-client/src/light_client.rs
+++ b/light-client/src/light_client.rs
@@ -2,6 +2,9 @@
 //!
 //! [1]: https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification/verification.md
 
+// FIXME(hu55a1n1): Remove below line once clippy errors in `contracts::post` derive macro are fixed
+#![allow(clippy::nonminimal_bool)]
+
 use contracts::*;
 use core::fmt;
 
diff --git a/light-client/src/peer_list.rs b/light-client/src/peer_list.rs
index d1f3d58f4..4296f6345 100644
--- a/light-client/src/peer_list.rs
+++ b/light-client/src/peer_list.rs
@@ -1,5 +1,8 @@
 //! Provides a peer list for use within the `Supervisor`
 
+// FIXME(hu55a1n1): Remove below line once clippy errors in `contracts::post` derive macro are fixed
+#![allow(clippy::nonminimal_bool)]
+
 use contracts::{post, pre};
 use std::collections::{BTreeSet, HashMap};
 
diff --git a/p2p/src/error.rs b/p2p/src/error.rs
index a0530c68d..69f9d55ba 100644
--- a/p2p/src/error.rs
+++ b/p2p/src/error.rs
@@ -1,5 +1,8 @@
 //! Error types
 
+// FIXME(hu55a1n1): Remove below line once flex-error solves this clippy error
+#![allow(clippy::use_self)]
+
 use flex_error::{define_error, DisplayOnly};
 use prost::DecodeError;
 use signature::Error as SignatureError;
diff --git a/rpc/src/client.rs b/rpc/src/client.rs
index a1ce9ad3a..0b91963fe 100644
--- a/rpc/src/client.rs
+++ b/rpc/src/client.rs
@@ -59,6 +59,14 @@ pub trait Client {
             .response)
     }
 
+    /// `/header`: get header at a given height.
+    async fn header<H>(&self, height: H) -> Result<header::Response, Error>
+    where
+        H: Into<Height> + Send,
+    {
+        self.perform(header::Request::new(height.into())).await
+    }
+
     /// `/block`: get block at a given height.
     async fn block<H>(&self, height: H) -> Result<block::Response, Error>
     where
diff --git a/rpc/src/client/bin/main.rs b/rpc/src/client/bin/main.rs
index eab7664f0..935dce752 100644
--- a/rpc/src/client/bin/main.rs
+++ b/rpc/src/client/bin/main.rs
@@ -77,6 +77,8 @@ enum ClientRequest {
         #[structopt(long)]
         prove: bool,
     },
+    /// Get a header at a given height.
+    Header { height: u32 },
     /// Get a block at a given height.
     Block { height: u32 },
     /// Get block headers between two heights (min <= height <= max).
@@ -314,6 +316,9 @@ where
                 .await?,
         )
         .map_err(Error::serde)?,
+        ClientRequest::Header { height } => {
+            serde_json::to_string_pretty(&client.header(height).await?).map_err(Error::serde)?
+        }
         ClientRequest::Block { height } => {
             serde_json::to_string_pretty(&client.block(height).await?).map_err(Error::serde)?
         }
diff --git a/rpc/src/endpoint.rs b/rpc/src/endpoint.rs
index 8720be10b..8b5d987d6 100644
--- a/rpc/src/endpoint.rs
+++ b/rpc/src/endpoint.rs
@@ -12,6 +12,7 @@ pub mod consensus_params;
 pub mod consensus_state;
 pub mod evidence;
 pub mod genesis;
+pub mod header;
 pub mod health;
 pub mod net_info;
 pub mod status;
diff --git a/rpc/src/endpoint/header.rs b/rpc/src/endpoint/header.rs
new file mode 100644
index 000000000..94e8416b0
--- /dev/null
+++ b/rpc/src/endpoint/header.rs
@@ -0,0 +1,43 @@
+//! `/header` endpoint JSON-RPC wrapper
+
+use serde::{Deserialize, Serialize};
+
+use tendermint::block::{Header, Height};
+
+/// Get information about a specific header
+#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
+pub struct Request {
+    /// Height of the header to request.
+    ///
+    /// If no height is provided, it will fetch results for the latest header.
+    pub height: Option<Height>,
+}
+
+impl Request {
+    /// Create a new request for information about a particular header
+    pub fn new(height: Height) -> Self {
+        Self {
+            height: Some(height),
+        }
+    }
+}
+
+impl crate::Request for Request {
+    type Response = Response;
+
+    fn method(&self) -> crate::Method {
+        crate::Method::Header
+    }
+}
+
+impl crate::SimpleRequest for Request {}
+
+/// Header responses
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(transparent)]
+pub struct Response {
+    /// Header
+    pub header: Header,
+}
+
+impl crate::Response for Response {}
diff --git a/rpc/src/method.rs b/rpc/src/method.rs
index f2c821202..267086011 100644
--- a/rpc/src/method.rs
+++ b/rpc/src/method.rs
@@ -19,6 +19,9 @@ pub enum Method {
     /// Get ABCI query
     AbciQuery,
 
+    /// Get block header
+    Header,
+
     /// Get block info
     Block,
 
@@ -86,6 +89,7 @@ impl Method {
         match self {
             Method::AbciInfo => "abci_info",
             Method::AbciQuery => "abci_query",
+            Method::Header => "header",
             Method::Block => "block",
             Method::BlockResults => "block_results",
             Method::BlockSearch => "block_search",
@@ -117,6 +121,7 @@ impl FromStr for Method {
         Ok(match s {
             "abci_info" => Method::AbciInfo,
             "abci_query" => Method::AbciQuery,
+            "header" => Method::Header,
             "block" => Method::Block,
             "block_results" => Method::BlockResults,
             "block_search" => Method::BlockSearch,
diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs
index 1fb40251d..c8858a0e7 100644
--- a/rpc/tests/kvstore_fixtures.rs
+++ b/rpc/tests/kvstore_fixtures.rs
@@ -72,6 +72,12 @@ fn outgoing_fixtures() {
                 assert!(wrapped.params().height.is_none());
                 assert!(!wrapped.params().prove);
             }
+            "header_at_height_1" => {
+                let wrapped =
+                    serde_json::from_str::<RequestWrapper<endpoint::header::Request>>(&content)
+                        .unwrap();
+                assert_eq!(wrapped.params().height.unwrap().value(), 1);
+            }
             "block_at_height_0" => {
                 let wrapped =
                     serde_json::from_str::<RequestWrapper<endpoint::block::Request>>(&content)
@@ -346,6 +352,37 @@ fn incoming_fixtures() {
                 assert!(result.response.proof.is_none());
                 assert!(result.response.value.is_empty());
             }
+            "header_at_height_1" => {
+                let result = endpoint::header::Response::from_string(content).unwrap();
+                assert!(result.header.app_hash.value().is_empty());
+                assert_eq!(result.header.chain_id.as_str(), CHAIN_ID);
+                assert!(!result.header.consensus_hash.is_empty());
+                assert_eq!(result.header.data_hash, empty_merkle_root_hash);
+                assert_eq!(result.header.evidence_hash, empty_merkle_root_hash);
+                assert_eq!(result.header.height.value(), 1);
+                assert!(result.header.last_block_id.is_none());
+                assert_eq!(result.header.last_commit_hash, empty_merkle_root_hash);
+                assert_eq!(result.header.last_results_hash, empty_merkle_root_hash);
+                assert!(!result.header.next_validators_hash.is_empty());
+                assert_ne!(
+                    result.header.proposer_address.as_bytes(),
+                    [0u8; tendermint::account::LENGTH]
+                );
+                assert!(
+                    result
+                        .header
+                        .time
+                        .duration_since(informal_epoch)
+                        .unwrap()
+                        .as_secs()
+                        > 0
+                );
+                assert!(!result.header.validators_hash.is_empty());
+                assert_eq!(
+                    result.header.version,
+                    tendermint::block::header::Version { block: 11, app: 1 }
+                );
+            }
             "block_at_height_0" => {
                 let res = endpoint::block::Response::from_string(&content);
 
diff --git a/rpc/tests/kvstore_fixtures/incoming/header_at_height_1.json b/rpc/tests/kvstore_fixtures/incoming/header_at_height_1.json
new file mode 100644
index 000000000..b9076f92f
--- /dev/null
+++ b/rpc/tests/kvstore_fixtures/incoming/header_at_height_1.json
@@ -0,0 +1,29 @@
+{
+  "id": "0166b641-4967-4b3c-a36a-73ea4e5a737a",
+  "jsonrpc": "2.0",
+  "result": {
+    "app_hash": "",
+    "chain_id": "dockerchain",
+    "consensus_hash": "048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F",
+    "data_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
+    "evidence_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
+    "height": "1",
+    "last_block_id": {
+      "hash": "",
+      "parts": {
+        "hash": "",
+        "total": 0
+      }
+    },
+    "last_commit_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
+    "last_results_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
+    "next_validators_hash": "ADFA3B40824D69EAD7828B9A78D16D80DFA93499D1DB0EC362916AE61182A64D",
+    "proposer_address": "ABA577531E6D6F4119E7E1E0EE1909B908A8346D",
+    "time": "2021-07-16T12:16:29.232984022Z",
+    "validators_hash": "ADFA3B40824D69EAD7828B9A78D16D80DFA93499D1DB0EC362916AE61182A64D",
+    "version": {
+      "app": "1",
+      "block": "11"
+    }
+  }
+}
\ No newline at end of file
diff --git a/rpc/tests/kvstore_fixtures/outgoing/header_at_height_1.json b/rpc/tests/kvstore_fixtures/outgoing/header_at_height_1.json
new file mode 100644
index 000000000..888325f9b
--- /dev/null
+++ b/rpc/tests/kvstore_fixtures/outgoing/header_at_height_1.json
@@ -0,0 +1,8 @@
+{
+  "id": "0166b641-4967-4b3c-a36a-73ea4e5a737a",
+  "jsonrpc": "2.0",
+  "method": "header",
+  "params": {
+    "height": "1"
+  }
+}
\ No newline at end of file
diff --git a/tendermint/src/abci/transaction.rs b/tendermint/src/abci/transaction.rs
index 546a1026d..3a93628ac 100644
--- a/tendermint/src/abci/transaction.rs
+++ b/tendermint/src/abci/transaction.rs
@@ -118,7 +118,7 @@ impl Data {
 
 impl AsRef<[Transaction]> for Data {
     fn as_ref(&self) -> &[Transaction] {
-        self.txs.as_deref().unwrap_or_else(|| &[])
+        self.txs.as_deref().unwrap_or(&[])
     }
 }
 
diff --git a/tendermint/src/evidence.rs b/tendermint/src/evidence.rs
index dac677981..77bf09460 100644
--- a/tendermint/src/evidence.rs
+++ b/tendermint/src/evidence.rs
@@ -205,7 +205,7 @@ impl Data {
 
 impl AsRef<[Evidence]> for Data {
     fn as_ref(&self) -> &[Evidence] {
-        self.evidence.as_deref().unwrap_or_else(|| &[])
+        self.evidence.as_deref().unwrap_or(&[])
     }
 }
 
diff --git a/tools/kvstore-test/tests/tendermint.rs b/tools/kvstore-test/tests/tendermint.rs
index 38ddca661..75dd2dd18 100644
--- a/tools/kvstore-test/tests/tendermint.rs
+++ b/tools/kvstore-test/tests/tendermint.rs
@@ -92,6 +92,17 @@ mod rpc {
         assert_eq!(abci_query.codespace, String::new());
     }
 
+    /// `/header` endpoint
+    #[tokio::test]
+    async fn header() {
+        let height = 1u64;
+        let res = localhost_http_client()
+            .header(Height::try_from(height).unwrap())
+            .await
+            .unwrap();
+        assert_eq!(res.header.height.value(), height);
+    }
+
     /// `/block` endpoint
     #[tokio::test]
     async fn block() {
diff --git a/tools/rpc-probe/src/common.rs b/tools/rpc-probe/src/common.rs
index 1557485f1..61517c4df 100644
--- a/tools/rpc-probe/src/common.rs
+++ b/tools/rpc-probe/src/common.rs
@@ -20,6 +20,16 @@ pub fn abci_query(key: &str) -> PlannedInteraction {
     .into()
 }
 
+pub fn header(height: u64) -> PlannedInteraction {
+    Request::new(
+        "header",
+        json!({
+            "height": format!("{}", height),
+        }),
+    )
+    .into()
+}
+
 pub fn block(height: u64) -> PlannedInteraction {
     Request::new(
         "block",
diff --git a/tools/rpc-probe/src/kvstore.rs b/tools/rpc-probe/src/kvstore.rs
index fb0f6aec6..5d4dca67e 100644
--- a/tools/rpc-probe/src/kvstore.rs
+++ b/tools/rpc-probe/src/kvstore.rs
@@ -16,6 +16,7 @@ pub fn quick_probe_plan(output_path: &Path, request_wait: Duration) -> Result<Pl
             in_series(vec![
                 abci_info(),
                 abci_query("non_existent_key").with_name("abci_query_with_non_existent_key"),
+                header(1).with_name("header_at_height_1"),
                 block(0).with_name("block_at_height_0").expect_error(),
                 block(1).with_name("block_at_height_1"),
                 block(10)