diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml
index aa642a81ea..97e859c3b9 100644
--- a/crates/examples/Cargo.toml
+++ b/crates/examples/Cargo.toml
@@ -12,9 +12,15 @@ tlsn-prover = { workspace = true }
 tlsn-utils = { workspace = true }
 tlsn-verifier = { workspace = true }
 tlsn-formats = { workspace = true }
+tlsn-tls-core = { workspace = true }
+tls-server-fixture = { workspace = true }
+tlsn-server-fixture = { workspace = true }
+tlsn-server-fixture-certs = { workspace = true }
+spansy = { workspace = true }
 
 bincode = { workspace = true }
 chrono = { workspace = true }
+clap = { version = "4.5", features = ["derive"] }
 dotenv = { version = "0.15.0" }
 futures = { workspace = true }
 http-body-util = { workspace = true }
@@ -50,7 +56,3 @@ path = "attestation/verify.rs"
 [[example]]
 name = "interactive"
 path = "interactive/interactive.rs"
-
-[[example]]
-name = "discord_dm"
-path = "discord/discord_dm.rs"
diff --git a/crates/examples/README.md b/crates/examples/README.md
index bff6bcb9e9..feb54d9c36 100644
--- a/crates/examples/README.md
+++ b/crates/examples/README.md
@@ -1,10 +1,8 @@
 # Examples
 
-This folder contains examples showing how to use the TLSNotary protocol. 
+This folder contains examples demonstrating how to use the TLSNotary protocol.
 
-* [attestation](./attestation/README.md) shows how to perform a simple notarization.
-* [interactive](./interactive/README.md) interactive Prover and Verifier, without a trusted notary.
-* [twitter](./twitter/README.md) shows how to notarize a Twitter DM.
-* [discord](./discord/README.md) shows how to notarize a Discord DM.
+* [Interactive](./interactive/README.md): Interactive Prover and Verifier session without a trusted notary.
+* [Attestation](./attestation/README.md): Performing a simple notarization with a trusted notary.
 
-Refer to <https://docs.tlsnotary.org/quick_start/index.html> for a quick start with TLSNotary using these examples.
+Refer to <https://docs.tlsnotary.org/quick_start/index.html> for a quick start guide to using TLSNotary with these examples.
\ No newline at end of file
diff --git a/crates/examples/attestation/README.md b/crates/examples/attestation/README.md
index 6cfb1db3de..e4552884df 100644
--- a/crates/examples/attestation/README.md
+++ b/crates/examples/attestation/README.md
@@ -1,75 +1,110 @@
 ## Simple Attestation Example: Notarize Public Data from example.com (Rust) <a name="rust-simple"></a>
 
-This example demonstrates the simplest possible use case for TLSNotary:
-1. Fetch <https://example.com/> and acquire an attestation of its content.
-2. Create a verifiable presentation using the attestation, while redacting the value of a header.
-3. Verify the presentation.
-
-### 1. Notarize <https://example.com/>
+This example demonstrates the simplest possible use case for TLSNotary. A Prover notarizes data from a local test server with a local Notary.
 
-Run the `prove` binary.
+**Overview**:
+1. Notarize a request and response from the test server and acquire an attestation of its content.
+2. Create a redacted, verifiable presentation using the attestation.
+3. Verify the presentation.
 
+### 1. Notarize
+
+Before starting the notarization, set up the local test server and local notary.
+
+1. Run the test server:
+    ```shell
+    PORT=4000 cargo run --bin tlsn-server-fixture
+    ```
+2. Run the notary server:
+    ```shell
+    cd crates/notary/server
+    cargo run -r -- --tls-enabled false
+    ```
+3. Run the prove example:
+    ```shell
+    SERVER_PORT=4000 cargo run --release --example attestation_prove
+    ```
+
+To see more details, run with additional debug information:
 ```shell
-cargo run --release --example attestation_prove
+RUST_LOG=debug,yamux=info,uid_mux=info SERVER_PORT=4000 cargo run --release --example attestation_prove
 ```
 
-If the notarization was successful, you should see this output in the console:
-
+If notarization is successful, you should see the following output in the console:
 ```log
 Starting an MPC TLS connection with the server
 Got a response from the server
 Notarization completed successfully!
-The attestation has been written to `example.attestation.tlsn` and the corresponding secrets to `example.secrets.tlsn`.
+The attestation has been written to `example-json.attestation.tlsn` and the corresponding secrets to `example-json.secrets.tlsn`.
 ```
 
-⚠️ In this simple example the `Notary` server is automatically started in the background. Note that this is for demonstration purposes only. In a real world example, the notary should be run by a trusted party. Consult the [Notary Server Docs](https://docs.tlsnotary.org/developers/notary_server.html) for more details on how to run a notary server.
-
-### 2. Build a verifiable presentation
+⚠️ Note: In this example, we run a local Notary server for demonstration purposes. In real-world applications, the Notary should be operated by a trusted third party. Refer to the [Notary Server Documentation](https://docs.tlsnotary.org/developers/notary_server.html) for more details on running a Notary server.
 
-This will build a verifiable presentation with the `User-Agent` header redacted from the request. This presentation can be shared with any verifier you wish to present the data to.
+### 2. Build a Verifiable Presentation
 
-Run the `present` binary.
+This step creates a verifiable presentation with optional redactions, which can be shared with any verifier.
 
+Run the present example:
 ```shell
 cargo run --release --example attestation_present
 ```
 
-If successful, you should see this output in the console:
+If successful, you’ll see this output in the console:
 
 ```log
 Presentation built successfully!
-The presentation has been written to `example.presentation.tlsn`.
+The presentation has been written to `example-json.presentation.tlsn`.
 ```
 
-### 3. Verify the presentation
+You can create multiple presentations from the attestation and secrets in the notarization step, each with customized data redactions. You are invited to experiment!
 
-This will read the presentation from the previous step, verify it, and print the disclosed data to console.
+### 3. Verify the Presentation
 
-Run the `verify` binary.
+This step reads the presentation created above, verifies it, and prints the disclosed data to the console.
 
+Run the verify binary:
 ```shell
 cargo run --release --example attestation_verify
 ```
 
-If successful, you should see this output in the console:
-
+Upon success, you should see output similar to:
 ```log
 Verifying presentation with {key algorithm} key: { hex encoded key }
 
 **Ask yourself, do you trust this key?**
 
 -------------------------------------------------------------------
-Successfully verified that the data below came from a session with example.com at 2024-10-03 03:01:40 UTC.
+Successfully verified that the data below came from a session with test-server.io at 2024-10-03 03:01:40 UTC.
 Note that the data which the Prover chose not to disclose are shown as X.
 
 Data sent:
 ...
 ```
 
-⚠️ Notice that the presentation comes with a "verifying key". This is the key the Notary used when issuing the attestation that the presentation was built from. If you trust the Notary, or more specifically the verifying key, then you can trust that the presented data is authentic.
+⚠️ The presentation includes a “verifying key,” which the Notary used when issuing the attestation. If you trust this key, you can trust the authenticity of the presented data.
 
-### Next steps
+### HTML
 
-Try out the [Discord example](../discord/README.md) and notarize a Discord conversations.
+In the example above, we notarized a JSON response. TLSNotary also supports notarizing HTML content. To run an HTML example, use:
 
+```shell
+# notarize
+SERVER_PORT=4000 cargo run --release --example attestation_prove -- html
+# present
+cargo run --release --example attestation_present -- html
+# verify
+cargo run --release --example attestation_verify -- html
+```
+
+### Private Data
 
+The examples above demonstrate how to use TLSNotary with publicly accessible data. TLSNotary can also be utilized for private data that requires authentication. To access this data, you can add the necessary headers (such as an authentication token) or cookies to your request. To run an example that uses an authentication token, execute the following command:
+
+```shell
+# notarize
+SERVER_PORT=4000 cargo run --release --example attestation_prove -- authenticated
+# present
+cargo run --release --example attestation_present -- authenticated
+# verify
+cargo run --release --example attestation_verify -- authenticated
+```
\ No newline at end of file
diff --git a/crates/examples/attestation/present.rs b/crates/examples/attestation/present.rs
index 802c2c0d26..a798f593b8 100644
--- a/crates/examples/attestation/present.rs
+++ b/crates/examples/attestation/present.rs
@@ -2,16 +2,37 @@
 // attestation and the corresponding connection secrets. See the `prove.rs`
 // example to learn how to acquire an attestation from a Notary.
 
+use hyper::header;
 use tlsn_core::{attestation::Attestation, presentation::Presentation, CryptoProvider, Secrets};
+use tlsn_examples::ExampleType;
 use tlsn_formats::http::HttpTranscript;
 
-fn main() -> Result<(), Box<dyn std::error::Error>> {
+use clap::Parser;
+
+#[derive(Parser, Debug)]
+#[command(version, about, long_about = None)]
+struct Args {
+    /// What data to notarize
+    #[clap(default_value_t, value_enum)]
+    example_type: ExampleType,
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let args = Args::parse();
+
+    create_presentation(&args.example_type).await
+}
+
+async fn create_presentation(example_type: &ExampleType) -> Result<(), Box<dyn std::error::Error>> {
+    let attestation_path = tlsn_examples::get_file_path(example_type, "attestation");
+    let secrets_path = tlsn_examples::get_file_path(example_type, "secrets");
+
     // Read attestation from disk.
-    let attestation: Attestation =
-        bincode::deserialize(&std::fs::read("example.attestation.tlsn")?)?;
+    let attestation: Attestation = bincode::deserialize(&std::fs::read(attestation_path)?)?;
 
     // Read secrets from disk.
-    let secrets: Secrets = bincode::deserialize(&std::fs::read("example.secrets.tlsn")?)?;
+    let secrets: Secrets = bincode::deserialize(&std::fs::read(secrets_path)?)?;
 
     // Parse the HTTP transcript.
     let transcript = HttpTranscript::parse(secrets.transcript())?;
@@ -24,16 +45,48 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     builder.reveal_sent(&request.without_data())?;
     // Reveal the request target.
     builder.reveal_sent(&request.request.target)?;
-    // Reveal all headers except the value of the User-Agent header.
+    // Reveal all headers except the values of User-Agent and Authorization.
     for header in &request.headers {
-        if !header.name.as_str().eq_ignore_ascii_case("User-Agent") {
+        if !(header
+            .name
+            .as_str()
+            .eq_ignore_ascii_case(header::USER_AGENT.as_str())
+            || header
+                .name
+                .as_str()
+                .eq_ignore_ascii_case(header::AUTHORIZATION.as_str()))
+        {
             builder.reveal_sent(header)?;
         } else {
             builder.reveal_sent(&header.without_value())?;
         }
     }
-    // Reveal the entire response.
-    builder.reveal_recv(&transcript.responses[0])?;
+
+    // Reveal only parts of the response
+    let response = &transcript.responses[0];
+    builder.reveal_recv(&response.without_data())?;
+    for header in &response.headers {
+        builder.reveal_recv(header)?;
+    }
+
+    let content = &response.body.as_ref().unwrap().content;
+    match content {
+        tlsn_formats::http::BodyContent::Json(json) => {
+            // For experimentation, reveal the entire response or just a selection
+            let reveal_all = false;
+            if reveal_all {
+                builder.reveal_recv(response)?;
+            } else {
+                builder.reveal_recv(json.get("id").unwrap())?;
+                builder.reveal_recv(json.get("information.name").unwrap())?;
+                builder.reveal_recv(json.get("meta.version").unwrap())?;
+            }
+        }
+        tlsn_formats::http::BodyContent::Unknown(span) => {
+            builder.reveal_recv(span)?;
+        }
+        _ => {}
+    }
 
     let transcript_proof = builder.build()?;
 
@@ -48,14 +101,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 
     let presentation: Presentation = builder.build()?;
 
+    let presentation_path = tlsn_examples::get_file_path(example_type, "presentation");
+
     // Write the presentation to disk.
-    std::fs::write(
-        "example.presentation.tlsn",
-        bincode::serialize(&presentation)?,
-    )?;
+    std::fs::write(&presentation_path, bincode::serialize(&presentation)?)?;
 
     println!("Presentation built successfully!");
-    println!("The presentation has been written to `example.presentation.tlsn`.");
+    println!("The presentation has been written to `{presentation_path}`.");
 
     Ok(())
 }
diff --git a/crates/examples/attestation/prove.rs b/crates/examples/attestation/prove.rs
index 23eb733750..36966f8bf2 100644
--- a/crates/examples/attestation/prove.rs
+++ b/crates/examples/attestation/prove.rs
@@ -2,49 +2,117 @@
 // an HTTP request sent to example.com. The attestation and secrets are saved to
 // disk.
 
+use std::env;
+
 use http_body_util::Empty;
 use hyper::{body::Bytes, Request, StatusCode};
 use hyper_util::rt::TokioIo;
+use spansy::Spanned;
+use tlsn_examples::ExampleType;
 use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
 
+use notary_client::{Accepted, NotarizationRequest, NotaryClient};
+use tls_server_fixture::SERVER_DOMAIN;
 use tlsn_common::config::ProtocolConfig;
 use tlsn_core::{request::RequestConfig, transcript::TranscriptCommitConfig};
-use tlsn_examples::run_notary;
 use tlsn_formats::http::{DefaultHttpCommitter, HttpCommit, HttpTranscript};
 use tlsn_prover::{Prover, ProverConfig};
+use tlsn_server_fixture::DEFAULT_FIXTURE_PORT;
+use tracing::debug;
+
+use clap::Parser;
 
 // Setting of the application server
-const SERVER_DOMAIN: &str = "example.com";
 const USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36";
 
+#[derive(Parser, Debug)]
+#[command(version, about, long_about = None)]
+struct Args {
+    /// What data to notarize
+    #[clap(default_value_t, value_enum)]
+    example_type: ExampleType,
+}
+
 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let args = Args::parse();
+
+    let (uri, extra_headers) = match args.example_type {
+        ExampleType::Json => ("/formats/json", vec![]),
+        ExampleType::Html => ("/formats/html", vec![]),
+        ExampleType::Authenticated => ("/protected", vec![("Authorization", "random_auth_token")]),
+    };
+
+    notarize(uri, extra_headers, &args.example_type).await
+}
+
+async fn notarize(
+    uri: &str,
+    extra_headers: Vec<(&str, &str)>,
+    example_type: &ExampleType,
+) -> Result<(), Box<dyn std::error::Error>> {
     tracing_subscriber::fmt::init();
 
-    let (prover_socket, notary_socket) = tokio::io::duplex(1 << 16);
+    let notary_host: String = env::var("NOTARY_HOST").unwrap_or("127.0.0.1".into());
+    let notary_port: u16 = env::var("NOTARY_PORT")
+        .map(|port| port.parse().expect("port should be valid integer"))
+        .unwrap_or(7047);
+    let server_host: String = env::var("SERVER_HOST").unwrap_or("127.0.0.1".into());
+    let server_port: u16 = env::var("SERVER_PORT")
+        .map(|port| port.parse().expect("port should be valid integer"))
+        .unwrap_or(DEFAULT_FIXTURE_PORT);
+
+    // Build a client to connect to the notary server.
+    let notary_client = NotaryClient::builder()
+        .host(notary_host)
+        .port(notary_port)
+        // WARNING: Always use TLS to connect to notary server, except if notary is running locally
+        // e.g. this example, hence `enable_tls` is set to False (else it always defaults to True).
+        .enable_tls(false)
+        .build()
+        .unwrap();
+
+    // Send requests for configuration and notarization to the notary server.
+    let notarization_request = NotarizationRequest::builder()
+        // We must configure the amount of data we expect to exchange beforehand, which will
+        // be preprocessed prior to the connection. Reducing these limits will improve
+        // performance.
+        .max_sent_data(tlsn_examples::MAX_SENT_DATA)
+        .max_recv_data(tlsn_examples::MAX_RECV_DATA)
+        .build()?;
 
-    // Start a local simple notary service
-    tokio::spawn(run_notary(notary_socket.compat()));
+    let Accepted {
+        io: notary_connection,
+        id: _session_id,
+        ..
+    } = notary_client
+        .request_notarization(notarization_request)
+        .await
+        .expect("Could not connect to notary. Make sure it is running.");
 
+    // Set up protocol configuration for prover.
     // Prover configuration.
-    let config = ProverConfig::builder()
+    let prover_config = ProverConfig::builder()
         .server_name(SERVER_DOMAIN)
         .protocol_config(
             ProtocolConfig::builder()
                 // We must configure the amount of data we expect to exchange beforehand, which will
                 // be preprocessed prior to the connection. Reducing these limits will improve
                 // performance.
-                .max_sent_data(1024)
-                .max_recv_data(4096)
+                .max_sent_data(tlsn_examples::MAX_SENT_DATA)
+                .max_recv_data(tlsn_examples::MAX_RECV_DATA)
                 .build()?,
         )
+        .crypto_provider(tlsn_examples::get_crypto_provider_with_server_fixture())
         .build()?;
 
     // Create a new prover and perform necessary setup.
-    let prover = Prover::new(config).setup(prover_socket.compat()).await?;
+    let prover = Prover::new(prover_config)
+        .setup(notary_connection.compat())
+        .await?;
 
     // Open a TCP connection to the server.
-    let client_socket = tokio::net::TcpStream::connect((SERVER_DOMAIN, 443)).await?;
+    let client_socket = tokio::net::TcpStream::connect((server_host, server_port)).await?;
 
     // Bind the prover to the server connection.
     // The returned `mpc_tls_connection` is an MPC TLS connection to the server: all
@@ -64,23 +132,27 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
     tokio::spawn(connection);
 
     // Build a simple HTTP request with common headers
-    let request = Request::builder()
-        .uri("/")
+    let request_builder = Request::builder()
+        .uri(uri)
         .header("Host", SERVER_DOMAIN)
         .header("Accept", "*/*")
         // Using "identity" instructs the Server not to use compression for its HTTP response.
         // TLSNotary tooling does not support compression.
         .header("Accept-Encoding", "identity")
         .header("Connection", "close")
-        .header("User-Agent", USER_AGENT)
-        .body(Empty::<Bytes>::new())?;
+        .header("User-Agent", USER_AGENT);
+    let mut request_builder = request_builder;
+    for (key, value) in extra_headers {
+        request_builder = request_builder.header(key, value);
+    }
+    let request = request_builder.body(Empty::<Bytes>::new())?;
 
     println!("Starting an MPC TLS connection with the server");
 
     // Send the request to the server and wait for the response.
     let response = request_sender.send_request(request).await?;
 
-    println!("Got a response from the server");
+    println!("Got a response from the server: {}", response.status());
 
     assert!(response.status() == StatusCode::OK);
 
@@ -92,6 +164,21 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
 
     // Parse the HTTP transcript.
     let transcript = HttpTranscript::parse(prover.transcript())?;
+    // dbg!(&transcript);
+
+    let body_content = &transcript.responses[0].body.as_ref().unwrap().content;
+    let body = String::from_utf8_lossy(body_content.span().as_bytes());
+
+    match body_content {
+        tlsn_formats::http::BodyContent::Json(_json) => {
+            let parsed = serde_json::from_str::<serde_json::Value>(&body)?;
+            debug!("{}", serde_json::to_string_pretty(&parsed)?);
+        }
+        tlsn_formats::http::BodyContent::Unknown(_span) => {
+            debug!("{}", &body);
+        }
+        _ => {}
+    }
 
     // Commit to the transcript.
     let mut builder = TranscriptCommitConfig::builder(prover.transcript());
@@ -101,24 +188,25 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
     prover.transcript_commit(builder.build()?);
 
     // Request an attestation.
-    let config = RequestConfig::default();
+    let request_config = RequestConfig::default();
 
-    let (attestation, secrets) = prover.finalize(&config).await?;
+    let (attestation, secrets) = prover.finalize(&request_config).await?;
+
+    println!("Notarization complete!");
 
     // Write the attestation to disk.
-    tokio::fs::write(
-        "example.attestation.tlsn",
-        bincode::serialize(&attestation)?,
-    )
-    .await?;
+    let attestation_path = tlsn_examples::get_file_path(example_type, "attestation");
+    let secrets_path = tlsn_examples::get_file_path(example_type, "secrets");
+
+    tokio::fs::write(&attestation_path, bincode::serialize(&attestation)?).await?;
 
     // Write the secrets to disk.
-    tokio::fs::write("example.secrets.tlsn", bincode::serialize(&secrets)?).await?;
+    tokio::fs::write(&secrets_path, bincode::serialize(&secrets)?).await?;
 
     println!("Notarization completed successfully!");
     println!(
-        "The attestation has been written to `example.attestation.tlsn` and the \
-        corresponding secrets to `example.secrets.tlsn`."
+        "The attestation has been written to `{attestation_path}` and the \
+        corresponding secrets to `{secrets_path}`."
     );
 
     Ok(())
diff --git a/crates/examples/attestation/verify.rs b/crates/examples/attestation/verify.rs
index 6b715c924f..fc06015ba0 100644
--- a/crates/examples/attestation/verify.rs
+++ b/crates/examples/attestation/verify.rs
@@ -7,15 +7,33 @@ use std::time::Duration;
 use tlsn_core::{
     presentation::{Presentation, PresentationOutput},
     signing::VerifyingKey,
-    CryptoProvider,
 };
 
-fn main() -> Result<(), Box<dyn std::error::Error>> {
+use clap::Parser;
+use tlsn_examples::ExampleType;
+
+#[derive(Parser, Debug)]
+#[command(version, about, long_about = None)]
+struct Args {
+    /// What data to notarize
+    #[clap(default_value_t, value_enum)]
+    example_type: ExampleType,
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let args = Args::parse();
+
+    verify_presentation(&args.example_type).await
+}
+
+async fn verify_presentation(example_type: &ExampleType) -> Result<(), Box<dyn std::error::Error>> {
     // Read the presentation from disk.
-    let presentation: Presentation =
-        bincode::deserialize(&std::fs::read("example.presentation.tlsn")?)?;
+    let presentation_path = tlsn_examples::get_file_path(example_type, "presentation");
+
+    let presentation: Presentation = bincode::deserialize(&std::fs::read(presentation_path)?)?;
 
-    let provider = CryptoProvider::default();
+    let provider = tlsn_examples::get_crypto_provider_with_server_fixture();
 
     let VerifyingKey {
         alg,
diff --git a/crates/examples/discord/.env.example b/crates/examples/discord/.env.example
deleted file mode 100644
index ae339ce858..0000000000
--- a/crates/examples/discord/.env.example
+++ /dev/null
@@ -1,3 +0,0 @@
-USER_AGENT=
-AUTHORIZATION=
-CHANNEL_ID=
diff --git a/crates/examples/discord/README.md b/crates/examples/discord/README.md
deleted file mode 100644
index 79ce461242..0000000000
--- a/crates/examples/discord/README.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# Notarize Discord DMs
-
-The `discord_dm.rs` example sets up a TLS connection with Discord and notarizes the requested DMs. The attestation and secrets are saved to disk.
-
-This involves 3 steps:
-1. Configure the inputs
-2. Start the (local) notary server
-3. Notarize
-
-## Inputs
-
-In this tlsn/examples/discord folder, create a `.env` file.
-Then in that `.env` file, set the values of the following constants by following the format shown in this [example env file](./.env.example).
-
-| Name          | Example                                                                          | Location                                      |
-| ------------- | -------------------------------------------------------------------------------- | --------------------------------------------- |
-| USER_AGENT    | `Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0` | Look for `User-Agent` in a request headers    |
-| AUTHORIZATION | `MTE1NDe1Otg4N6NxNjczOTM2OA.GYbUBf.aDtcMUKDOmg6C2kxxFtlFSN1pgdMMBtpHgBBEs`       | Look for `Authorization` in a request headers |
-| CHANNEL_ID    | `1154750485639745567`                                                            | URL                                           |
-
-You can obtain these parameters by opening [Discord](https://discord.com/channels/@me) in your browser and accessing the message history you want to notarize. Please note that notarizing only works for short transcripts at the moment, so choose a contact with a short history.
-
-Next, open the **Developer Tools**, go to the **Network** tab, and refresh the page. Then, click on **Search** and type `/api` to filter results to Discord API requests. From there you can copy the needed information into your `.env` as indicated above.
-
-You can find the `CHANNEL_ID` directly in the url:
-
-`https://discord.com/channels/@me/{CHANNEL_ID)`
-
-## Start the notary server
-1. Edit the notary server [config file](../../notary/server/config/config.yaml) to turn off TLS so that self-signed certificates can be avoided (⚠️ this is only for local development purposes — TLS must be used in production).
-   ```yaml
-    tls:
-        enabled: false
-        ...
-   ```
-2. Run the following at the root level of this repository to start the notary server:
-   ```shell
-   cd crates/notary/server
-   cargo run --release
-   ```
-
-The notary server will now be running in the background waiting for connections.
-
-For more information on how to configure the `Notary` server, please refer to [this](../../notary/server/README.md#running-the-server).
-
-## Notarize
-
-In this tlsn/examples/discord folder, run the following command:
-
-```sh
-RUST_LOG=DEBUG,uid_mux=INFO,yamux=INFO cargo run --release --example discord_dm
-```
-
-If everything goes well, you should see output similar to the following:
-
-```log
-...
-2024-06-26T08:49:47.017439Z DEBUG connect:tls_connection: tls_client_async: handshake complete
-2024-06-26T08:49:48.676459Z DEBUG connect:tls_connection: tls_client_async: server closed connection
-2024-06-26T08:49:48.676481Z DEBUG connect:commit: tls_mpc::leader: committing to transcript
-2024-06-26T08:49:48.676503Z DEBUG connect:tls_connection: tls_client_async: client shutdown
-2024-06-26T08:49:48.676466Z DEBUG discord_dm: Sent request
-2024-06-26T08:49:48.676550Z DEBUG discord_dm: Request OK
-2024-06-26T08:49:48.676598Z DEBUG connect:close_connection: tls_mpc::leader: closing connection
-2024-06-26T08:49:48.676613Z DEBUG connect: tls_mpc::leader: leader actor stopped
-2024-06-26T08:49:48.676618Z DEBUG discord_dm: [
-  {
-    "attachments": [],
-    ...
-    "channel_id": "1154750485639745567",
-    ...
-  }
-]
-2024-06-26T08:49:48.678621Z DEBUG finalize: tlsn_prover::tls::notarize: starting finalization
-2024-06-26T08:49:48.680839Z DEBUG finalize: tlsn_prover::tls::notarize: received OT secret
-2024-06-26T08:49:50.004432Z  INFO finalize:poll{role=Client}:handle_shutdown: uid_mux::yamux: mux connection closed
-2024-06-26T08:49:50.004448Z  INFO finalize:poll{role=Client}: uid_mux::yamux: connection complete
-2024-06-26T08:49:50.004583Z DEBUG discord_dm: Notarization complete!
-```
-
-If the transcript was too long, you may encounter the following error. This occurs because there is a default limit of notarization size to 16kB:
-
-```
-thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: IOError(Custom { kind: InvalidData, error: BackendError(DecryptionError("Other: KOSReceiverActor is not setup")) })', /Users/heeckhau/tlsnotary/tlsn/tlsn/tlsn-prover/src/lib.rs:173:50
-```
-
-# Verify
-
-See the [`present`](../attestation/present.rs) and [`verify`](../attestation/verify.rs) examples for a demonstration of how to construct a presentation and verify it.
\ No newline at end of file
diff --git a/crates/examples/discord/discord_dm.rs b/crates/examples/discord/discord_dm.rs
deleted file mode 100644
index dd13198063..0000000000
--- a/crates/examples/discord/discord_dm.rs
+++ /dev/null
@@ -1,216 +0,0 @@
-// This example shows how to notarize Discord DMs.
-//
-// The example uses the notary server implemented in ../../notary/server
-
-use http_body_util::{BodyExt, Empty};
-use hyper::{body::Bytes, Request, StatusCode};
-use hyper_util::rt::TokioIo;
-use notary_client::{Accepted, NotarizationRequest, NotaryClient};
-use std::{env, str};
-use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
-use tracing::debug;
-use utils::range::RangeSet;
-
-use tlsn_common::config::ProtocolConfig;
-use tlsn_core::{request::RequestConfig, transcript::TranscriptCommitConfig};
-use tlsn_prover::{Prover, ProverConfig};
-
-// Setting of the application server
-const SERVER_DOMAIN: &str = "discord.com";
-
-// Setting of the notary server — make sure these are the same with the config
-// in ../../notary/server
-const NOTARY_HOST: &str = "127.0.0.1";
-const NOTARY_PORT: u16 = 7047;
-
-// Maximum number of bytes that can be sent from prover to server
-const MAX_SENT_DATA: usize = 1 << 12;
-// Maximum number of bytes that can be received by prover from server
-const MAX_RECV_DATA: usize = 1 << 14;
-
-#[tokio::main]
-async fn main() {
-    tracing_subscriber::fmt::init();
-
-    // Load secret variables frome environment for discord server connection
-    dotenv::dotenv().ok();
-    let channel_id = env::var("CHANNEL_ID").unwrap();
-    let auth_token = env::var("AUTHORIZATION").unwrap();
-    let user_agent = env::var("USER_AGENT").unwrap();
-
-    // Build a client to connect to the notary server.
-    let notary_client = NotaryClient::builder()
-        .host(NOTARY_HOST)
-        .port(NOTARY_PORT)
-        // WARNING: Always use TLS to connect to notary server, except if notary is running locally
-        // e.g. this example, hence `enable_tls` is set to False (else it always defaults to True).
-        .enable_tls(false)
-        .build()
-        .unwrap();
-
-    // Send requests for configuration and notarization to the notary server.
-    let notarization_request = NotarizationRequest::builder()
-        .max_sent_data(MAX_SENT_DATA)
-        .max_recv_data(MAX_RECV_DATA)
-        .build()
-        .unwrap();
-
-    let Accepted {
-        io: notary_connection,
-        id: _session_id,
-        ..
-    } = notary_client
-        .request_notarization(notarization_request)
-        .await
-        .expect("Could not connect to notary. Make sure it is running.");
-
-    // Set up protocol configuration for prover.
-    let protocol_config = ProtocolConfig::builder()
-        .max_sent_data(MAX_SENT_DATA)
-        .max_recv_data(MAX_RECV_DATA)
-        .build()
-        .unwrap();
-
-    // Create a new prover and set up the MPC backend.
-    let prover_config = ProverConfig::builder()
-        .server_name(SERVER_DOMAIN)
-        .protocol_config(protocol_config)
-        .build()
-        .unwrap();
-    let prover = Prover::new(prover_config)
-        .setup(notary_connection.compat())
-        .await
-        .unwrap();
-
-    // Open a new socket to the application server.
-    let client_socket = tokio::net::TcpStream::connect((SERVER_DOMAIN, 443))
-        .await
-        .unwrap();
-
-    // Bind the Prover to server connection
-    let (tls_connection, prover_fut) = prover.connect(client_socket.compat()).await.unwrap();
-
-    // Spawn the Prover to be run concurrently
-    let prover_task = tokio::spawn(prover_fut);
-
-    // Attach the hyper HTTP client to the TLS connection
-    let (mut request_sender, connection) =
-        hyper::client::conn::http1::handshake(TokioIo::new(tls_connection.compat()))
-            .await
-            .unwrap();
-
-    // Spawn the HTTP task to be run concurrently
-    tokio::spawn(connection);
-
-    // Build the HTTP request to fetch the DMs
-    let request = Request::builder()
-        .uri(format!(
-            "https://{SERVER_DOMAIN}/api/v9/channels/{channel_id}/messages?limit=2"
-        ))
-        .header("Host", SERVER_DOMAIN)
-        .header("Accept", "*/*")
-        .header("Accept-Language", "en-US,en;q=0.5")
-        .header("Accept-Encoding", "identity")
-        .header("User-Agent", user_agent)
-        .header("Authorization", &auth_token)
-        .header("Connection", "close")
-        .body(Empty::<Bytes>::new())
-        .unwrap();
-
-    debug!("Sending request");
-
-    let response = request_sender.send_request(request).await.unwrap();
-
-    debug!("Sent request");
-
-    assert!(response.status() == StatusCode::OK, "{}", response.status());
-
-    debug!("Request OK");
-
-    // Pretty printing :)
-    let payload = response.into_body().collect().await.unwrap().to_bytes();
-    let parsed =
-        serde_json::from_str::<serde_json::Value>(&String::from_utf8_lossy(&payload)).unwrap();
-    debug!("{}", serde_json::to_string_pretty(&parsed).unwrap());
-
-    // The Prover task should be done now, so we can grab it.
-    let prover = prover_task.await.unwrap().unwrap();
-
-    // Prepare for notarization
-    let mut prover = prover.start_notarize();
-
-    // Identify the ranges in the transcript that contain secrets
-    let sent_transcript = prover.transcript().sent();
-    let recv_transcript = prover.transcript().received();
-
-    // Identify the ranges in the outbound data which contain data which we want to
-    // disclose
-    let (sent_public_ranges, _) = find_ranges(sent_transcript, &[auth_token.as_bytes()]);
-    #[allow(clippy::single_range_in_vec_init)]
-    let recv_public_ranges = RangeSet::from([0..recv_transcript.len()]);
-
-    let mut builder = TranscriptCommitConfig::builder(prover.transcript());
-
-    // Commit to public ranges
-    builder.commit_sent(&sent_public_ranges).unwrap();
-    builder.commit_recv(&recv_public_ranges).unwrap();
-
-    let config = builder.build().unwrap();
-
-    prover.transcript_commit(config);
-
-    // Finalize, returning the notarized session
-    let request_config = RequestConfig::default();
-    let (attestation, secrets) = prover.finalize(&request_config).await.unwrap();
-
-    debug!("Notarization complete!");
-
-    tokio::fs::write(
-        "discord.attestation.tlsn",
-        bincode::serialize(&attestation).unwrap(),
-    )
-    .await
-    .unwrap();
-
-    tokio::fs::write(
-        "discord.secrets.tlsn",
-        bincode::serialize(&secrets).unwrap(),
-    )
-    .await
-    .unwrap();
-}
-
-/// Find the ranges of the public and private parts of a sequence.
-///
-/// Returns a tuple of `(public, private)` ranges.
-fn find_ranges(seq: &[u8], sub_seq: &[&[u8]]) -> (RangeSet<usize>, RangeSet<usize>) {
-    let mut private_ranges = Vec::new();
-    for s in sub_seq {
-        for (idx, w) in seq.windows(s.len()).enumerate() {
-            if w == *s {
-                private_ranges.push(idx..(idx + w.len()));
-            }
-        }
-    }
-
-    let mut sorted_ranges = private_ranges.clone();
-    sorted_ranges.sort_by_key(|r| r.start);
-
-    let mut public_ranges = Vec::new();
-    let mut last_end = 0;
-    for r in sorted_ranges {
-        if r.start > last_end {
-            public_ranges.push(last_end..r.start);
-        }
-        last_end = r.end;
-    }
-
-    if last_end < seq.len() {
-        public_ranges.push(last_end..seq.len());
-    }
-
-    (
-        RangeSet::from(public_ranges),
-        RangeSet::from(private_ranges),
-    )
-}
diff --git a/crates/examples/interactive/README.md b/crates/examples/interactive/README.md
index bed068da40..e2a99c4446 100644
--- a/crates/examples/interactive/README.md
+++ b/crates/examples/interactive/README.md
@@ -2,4 +2,17 @@
 
 This example demonstrates how to use TLSNotary in a simple interactive session between a Prover and a Verifier. It involves the Verifier first verifying the MPC-TLS session and then confirming the correctness of the data.
 
-Note: In this example, the Prover and the Verifier run on the same machine. In real-world scenarios, the Prover and Verifier would be separate entities.
+This example fetches data from a local test server. To start this server, run:
+```shell
+PORT=4000 cargo run --bin tlsn-server-fixture
+```
+Next, run the interactive example with:
+```shell
+SERVER_PORT=4000 cargo run --release --example interactive
+```
+To view more detailed debug information, use the following command:
+```
+RUST_LOG=debug,yamux=info,uid_mux=info SERVER_PORT=4000 cargo run --release --example interactive
+```
+
+> ℹ️ Note: In this example, the Prover and Verifier run on the same machine. In real-world scenarios, the Prover and Verifier would typically operate on separate machines.
\ No newline at end of file
diff --git a/crates/examples/interactive/interactive.rs b/crates/examples/interactive/interactive.rs
index dd6fd86a35..7befd4ddda 100644
--- a/crates/examples/interactive/interactive.rs
+++ b/crates/examples/interactive/interactive.rs
@@ -1,16 +1,24 @@
+use std::{
+    env,
+    net::{IpAddr, SocketAddr},
+};
+
 use http_body_util::Empty;
 use hyper::{body::Bytes, Request, StatusCode, Uri};
 use hyper_util::rt::TokioIo;
 use tlsn_common::config::{ProtocolConfig, ProtocolConfigValidator};
 use tlsn_core::transcript::Idx;
+use tlsn_examples::get_crypto_provider_with_server_fixture;
 use tlsn_prover::{state::Prove, Prover, ProverConfig};
+
+use tlsn_server_fixture::DEFAULT_FIXTURE_PORT;
+use tlsn_server_fixture_certs::SERVER_DOMAIN;
 use tlsn_verifier::{SessionInfo, Verifier, VerifierConfig};
 use tokio::io::{AsyncRead, AsyncWrite};
 use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
 use tracing::instrument;
 
 const SECRET: &str = "TLSNotary's private key 🤡";
-const SERVER_DOMAIN: &str = "example.com";
 
 // Maximum number of bytes that can be sent from prover to server
 const MAX_SENT_DATA: usize = 1 << 12;
@@ -21,13 +29,21 @@ const MAX_RECV_DATA: usize = 1 << 14;
 async fn main() {
     tracing_subscriber::fmt::init();
 
-    let uri = "https://example.com";
-    let id = "interactive verifier demo";
+    let server_host: String = env::var("SERVER_HOST").unwrap_or("127.0.0.1".into());
+    let server_port: u16 = env::var("SERVER_PORT")
+        .map(|port| port.parse().expect("port should be valid integer"))
+        .unwrap_or(DEFAULT_FIXTURE_PORT);
+
+    // we use SERVER_DOMAIN here to make sure it matches the domain in the test
+    // server's certificate
+    let uri = format!("https://{SERVER_DOMAIN}:{server_port}/formats/html");
+    let server_ip: IpAddr = server_host.parse().expect("Invalid IP address");
+    let server_addr = SocketAddr::from((server_ip, server_port));
 
     // Connect prover and verifier.
     let (prover_socket, verifier_socket) = tokio::io::duplex(1 << 23);
-    let prover = prover(prover_socket, uri, id);
-    let verifier = verifier(verifier_socket, id);
+    let prover = prover(prover_socket, &server_addr, &uri);
+    let verifier = verifier(verifier_socket);
     let (_, (sent, received, _session_info)) = tokio::join!(prover, verifier);
 
     println!("Successfully verified {}", &uri);
@@ -41,13 +57,12 @@ async fn main() {
 #[instrument(skip(verifier_socket))]
 async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
     verifier_socket: T,
+    server_addr: &SocketAddr,
     uri: &str,
-    id: &str,
 ) {
     let uri = uri.parse::<Uri>().unwrap();
     assert_eq!(uri.scheme().unwrap().as_str(), "https");
     let server_domain = uri.authority().unwrap().host();
-    let server_port = uri.port_u16().unwrap_or(443);
 
     // Create prover and connect to verifier.
     //
@@ -62,6 +77,7 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
                     .build()
                     .unwrap(),
             )
+            .crypto_provider(get_crypto_provider_with_server_fixture())
             .build()
             .unwrap(),
     )
@@ -70,9 +86,7 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
     .unwrap();
 
     // Connect to TLS Server.
-    let tls_client_socket = tokio::net::TcpStream::connect((server_domain, server_port))
-        .await
-        .unwrap();
+    let tls_client_socket = tokio::net::TcpStream::connect(server_addr).await.unwrap();
 
     // Pass server connection into the prover.
     let (mpc_tls_connection, prover_fut) =
@@ -109,10 +123,9 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
     // Create proof for the Verifier.
     let mut prover = prover_task.await.unwrap().unwrap().start_prove();
 
-    let idx_sent = redact_and_reveal_sent_data(&mut prover);
-    let idx_recv = redact_and_reveal_received_data(&mut prover);
-
     // Reveal parts of the transcript
+    let idx_sent = revealed_ranges_sent(&mut prover);
+    let idx_recv = revealed_ranges_received(&mut prover);
     prover.prove_transcript(idx_sent, idx_recv).await.unwrap();
 
     // Finalize.
@@ -122,7 +135,6 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
 #[instrument(skip(socket))]
 async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
     socket: T,
-    id: &str,
 ) -> (Vec<u8>, Vec<u8>, SessionInfo) {
     // Setup Verifier.
     let config_validator = ProtocolConfigValidator::builder()
@@ -133,6 +145,7 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
 
     let verifier_config = VerifierConfig::builder()
         .protocol_config_validator(config_validator)
+        .crypto_provider(get_crypto_provider_with_server_fixture())
         .build()
         .unwrap();
     let verifier = Verifier::new(verifier_config);
@@ -141,19 +154,19 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
     let (mut partial_transcript, session_info) = verifier.verify(socket.compat()).await.unwrap();
     partial_transcript.set_unauthed(0);
 
-    // Check sent data: check host.
+    // Check sent data:
     let sent = partial_transcript.sent_unsafe().to_vec();
     let sent_data = String::from_utf8(sent.clone()).expect("Verifier expected sent data");
     sent_data
         .find(SERVER_DOMAIN)
         .unwrap_or_else(|| panic!("Verification failed: Expected host {}", SERVER_DOMAIN));
 
-    // Check received data: check json and version number.
+    // Check received data:
     let received = partial_transcript.received_unsafe().to_vec();
     let response = String::from_utf8(received.clone()).expect("Verifier expected received data");
     response
-        .find("Example Domain")
-        .expect("Expected valid data from example.com");
+        .find("Herman Melville")
+        .unwrap_or_else(|| panic!("Expected valid data from {}", SERVER_DOMAIN));
 
     // Check Session info: server name.
     assert_eq!(session_info.server_name.as_str(), SERVER_DOMAIN);
@@ -161,8 +174,8 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
     (sent, received, session_info)
 }
 
-/// Redacts and reveals received data to the verifier.
-fn redact_and_reveal_received_data(prover: &mut Prover<Prove>) -> Idx {
+/// Returns the received ranges to be revealed to the verifier.
+fn revealed_ranges_received(prover: &mut Prover<Prove>) -> Idx {
     let recv_transcript = prover.transcript().received();
     let recv_transcript_len = recv_transcript.len();
 
@@ -170,15 +183,15 @@ fn redact_and_reveal_received_data(prover: &mut Prover<Prove>) -> Idx {
     let received_string = String::from_utf8(recv_transcript.to_vec()).unwrap();
     // Find the substring "illustrative".
     let start = received_string
-        .find("illustrative")
-        .expect("Error: The substring 'illustrative' was not found in the received data.");
-    let end = start + "illustrative".len();
+        .find("Dick")
+        .expect("Error: The substring 'Dick' was not found in the received data.");
+    let end = start + "Dick".len();
 
     Idx::new([0..start, end..recv_transcript_len])
 }
 
-/// Redacts and reveals sent data to the verifier.
-fn redact_and_reveal_sent_data(prover: &mut Prover<Prove>) -> Idx {
+/// Returns the sent ranges to be revealed to the verifier.
+fn revealed_ranges_sent(prover: &mut Prover<Prove>) -> Idx {
     let sent_transcript = prover.transcript().sent();
     let sent_transcript_len = sent_transcript.len();
 
diff --git a/crates/examples/src/lib.rs b/crates/examples/src/lib.rs
index f74b9c10d0..4b6d4b8db0 100644
--- a/crates/examples/src/lib.rs
+++ b/crates/examples/src/lib.rs
@@ -1,46 +1,45 @@
-use futures::{AsyncRead, AsyncWrite};
-use k256::{pkcs8::DecodePrivateKey, SecretKey};
-use tlsn_common::config::ProtocolConfigValidator;
-use tlsn_core::{attestation::AttestationConfig, signing::SignatureAlgId, CryptoProvider};
-use tlsn_verifier::{Verifier, VerifierConfig};
-
-/// The private key used by the Notary for signing attestations.
-pub const NOTARY_PRIVATE_KEY: &[u8] = &[1u8; 32];
+use std::fmt;
+use tls_core::verify::WebPkiVerifier;
+use tls_server_fixture::CA_CERT_DER;
+use tlsn_core::CryptoProvider;
 
 // Maximum number of bytes that can be sent from prover to server
-const MAX_SENT_DATA: usize = 1 << 12;
+pub const MAX_SENT_DATA: usize = 1 << 12;
 // Maximum number of bytes that can be received by prover from server
-const MAX_RECV_DATA: usize = 1 << 14;
-
-/// Runs a simple Notary with the provided connection to the Prover.
-pub async fn run_notary<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(conn: T) {
-    let pem_data = include_str!("../../notary/server/fixture/notary/notary.key");
-    let secret_key = SecretKey::from_pkcs8_pem(pem_data).unwrap().to_bytes();
-
-    let mut provider = CryptoProvider::default();
-    provider.signer.set_secp256k1(&secret_key).unwrap();
-
-    // Setup the config. Normally a different ID would be generated
-    // for each notarization.
-    let config_validator = ProtocolConfigValidator::builder()
-        .max_sent_data(MAX_SENT_DATA)
-        .max_recv_data(MAX_RECV_DATA)
-        .build()
+pub const MAX_RECV_DATA: usize = 1 << 14;
+
+/// crypto provider accepting the server-fixture's self-signed certificate
+///
+/// This is only required for offline testing with the server-fixture. In
+/// production, use `CryptoProvider::default()` instead.
+pub fn get_crypto_provider_with_server_fixture() -> CryptoProvider {
+    // custom root store with server-fixture
+    let mut root_store = tls_core::anchors::RootCertStore::empty();
+    root_store
+        .add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
         .unwrap();
 
-    let config = VerifierConfig::builder()
-        .protocol_config_validator(config_validator)
-        .crypto_provider(provider)
-        .build()
-        .unwrap();
+    CryptoProvider {
+        cert: WebPkiVerifier::new(root_store, None),
+        ..Default::default()
+    }
+}
 
-    let attestation_config = AttestationConfig::builder()
-        .supported_signature_algs(vec![SignatureAlgId::SECP256K1])
-        .build()
-        .unwrap();
+#[derive(clap::ValueEnum, Clone, Default, Debug)]
+pub enum ExampleType {
+    #[default]
+    Json,
+    Html,
+    Authenticated,
+}
 
-    Verifier::new(config)
-        .notarize(conn, &attestation_config)
-        .await
-        .unwrap();
+impl fmt::Display for ExampleType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
+pub fn get_file_path(example_type: &ExampleType, content_type: &str) -> String {
+    let example_type = example_type.to_string().to_ascii_lowercase();
+    format!("example-{}.{}.tlsn", example_type, content_type)
 }
diff --git a/crates/server-fixture/server/src/data/protected_data.json b/crates/server-fixture/server/src/data/protected_data.json
new file mode 100644
index 0000000000..6187ffe32c
--- /dev/null
+++ b/crates/server-fixture/server/src/data/protected_data.json
@@ -0,0 +1,28 @@
+{
+  "id": 1234567890,
+  "information": {
+    "name": "John Doe",
+    "address": {
+      "street": "123 Elm Street",
+      "city": "Anytown",
+      "state": "XY",
+      "postalCode": "12345"
+    },
+    "favoriteColors": [
+      "blue",
+      "red",
+      "green",
+      "yellow"
+    ],
+    "description": "John is a software engineer. He enjoys hiking, playing video games, and reading books. His favorite book is 'Moby Dick'.",
+    "education": {
+      "degree": "Bachelor's in Computer Science",
+      "school": "Anytown University"
+    }
+  },
+  "meta": {
+    "createdAt": "2022-01-15T14:52:55Z",
+    "lastUpdatedAt": "2023-01-12T16:42:10Z",
+    "version": 1.2
+  }
+}
\ No newline at end of file
diff --git a/crates/server-fixture/server/src/lib.rs b/crates/server-fixture/server/src/lib.rs
index a28e5cafa8..d33a5902d9 100644
--- a/crates/server-fixture/server/src/lib.rs
+++ b/crates/server-fixture/server/src/lib.rs
@@ -26,6 +26,9 @@ use serde_json::Value;
 use tokio_util::compat::FuturesAsyncReadCompatExt;
 use tower_service::Service;
 
+use axum::{async_trait, extract::FromRequest};
+use hyper::header;
+
 use tlsn_server_fixture_certs::*;
 
 pub const DEFAULT_FIXTURE_PORT: u16 = 3000;
@@ -40,6 +43,7 @@ fn app(state: AppState) -> Router {
         .route("/bytes", get(bytes))
         .route("/formats/json", get(json))
         .route("/formats/html", get(html))
+        .route("/protected", get(protected_route))
         .with_state(Arc::new(Mutex::new(state)))
 }
 
@@ -136,6 +140,43 @@ async fn html(
 
     Html(include_str!("data/4kb.html"))
 }
+
+struct AuthenticatedUser;
+
+#[async_trait]
+impl<B> FromRequest<B> for AuthenticatedUser
+where
+    B: Send,
+{
+    type Rejection = (StatusCode, &'static str);
+
+    async fn from_request(
+        req: axum::extract::Request,
+        _state: &B,
+    ) -> Result<Self, Self::Rejection> {
+        // Expected token (hardcoded for simplicity in the demo)
+        let expected_token = "random_auth_token";
+
+        let auth_header = req
+            .headers()
+            .get(header::AUTHORIZATION)
+            .and_then(|value| value.to_str().ok());
+
+        if let Some(auth_token) = auth_header {
+            let token = auth_token.trim_start_matches("Bearer ");
+            if token == expected_token {
+                return Ok(AuthenticatedUser);
+            }
+        }
+
+        Err((StatusCode::UNAUTHORIZED, "Invalid or missing token"))
+    }
+}
+
+async fn protected_route(_: AuthenticatedUser) -> Result<Json<Value>, StatusCode> {
+    get_json_value(include_str!("data/protected_data.json"))
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;