diff --git a/Cargo.lock b/Cargo.lock
index 014c5cc6..1166304e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -267,6 +267,7 @@ checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd"
 name = "conduit"
 version = "0.1.0"
 dependencies = [
+ "base64 0.12.3",
  "directories",
  "http",
  "image",
@@ -1559,7 +1560,7 @@ dependencies = [
 [[package]]
 name = "ruma"
 version = "0.0.1"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "ruma-api",
  "ruma-client-api",
@@ -1573,7 +1574,7 @@ dependencies = [
 [[package]]
 name = "ruma-api"
 version = "0.17.0-alpha.1"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "http",
  "percent-encoding",
@@ -1588,7 +1589,7 @@ dependencies = [
 [[package]]
 name = "ruma-api-macros"
 version = "0.17.0-alpha.1"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
@@ -1599,7 +1600,7 @@ dependencies = [
 [[package]]
 name = "ruma-client-api"
 version = "0.10.0-alpha.1"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "assign",
  "http",
@@ -1617,7 +1618,7 @@ dependencies = [
 [[package]]
 name = "ruma-common"
 version = "0.2.0"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "js_int",
  "ruma-identifiers",
@@ -1630,7 +1631,7 @@ dependencies = [
 [[package]]
 name = "ruma-events"
 version = "0.22.0-alpha.1"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "js_int",
  "ruma-common",
@@ -1645,7 +1646,7 @@ dependencies = [
 [[package]]
 name = "ruma-events-macros"
 version = "0.22.0-alpha.1"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
@@ -1656,7 +1657,7 @@ dependencies = [
 [[package]]
 name = "ruma-federation-api"
 version = "0.0.3"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "js_int",
  "ruma-api",
@@ -1671,7 +1672,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers"
 version = "0.17.4"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "rand",
  "ruma-identifiers-macros",
@@ -1683,7 +1684,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers-macros"
 version = "0.17.4"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1694,7 +1695,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers-validation"
 version = "0.1.1"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "ruma-serde",
  "serde",
@@ -1705,7 +1706,7 @@ dependencies = [
 [[package]]
 name = "ruma-serde"
 version = "0.2.3"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "form_urlencoded",
  "itoa",
@@ -1717,7 +1718,7 @@ dependencies = [
 [[package]]
 name = "ruma-signatures"
 version = "0.6.0-dev.1"
-source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#8868d2f72bc5d54f04154fb4fe71b08e4f69a0ae"
 dependencies = [
  "base64 0.12.3",
  "ring",
diff --git a/Cargo.toml b/Cargo.toml
index 90633ded..4945e3c8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,9 +16,10 @@ edition = "2018"
 #rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67", features = ["tls"] } # Used to handle requests
 rocket = { git = "https://github.com/timokoesters/Rocket.git", branch = "empty_parameters", features = ["tls"] }
 
-tokio = "0.2.22" # Used for long polling
-ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "987d48666cf166cf12100b5dbc61b5e3385c4014" } # Used for matrix spec type definitions and helpers
+#ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "987d48666cf166cf12100b5dbc61b5e3385c4014" } # Used for matrix spec type definitions and helpers
+ruma = { git = "https://github.com/timokoesters/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], branch = "timo-fixes" } # Used for matrix spec type definitions and helpers
 #ruma = { path = "../ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"] }
+tokio = "0.2.22" # Used for long polling
 sled = "0.32.0" # Used for storing data permanently
 log = "0.4.8" # Used for emitting log entries
 http = "0.2.1" # Used for rocket<->ruma conversions
@@ -31,6 +32,7 @@ rust-argon2 = "0.8.2" # Used to hash passwords
 reqwest = "0.10.6" # Used to send requests
 thiserror = "1.0.19" # Used for conduit::Error type
 image = { version = "0.23.4", default-features = false, features = ["jpeg", "png", "gif"] } # Used to generate thumbnails for images
+base64 = "0.12.3" # Used to encode server public key
 
 [features]
 default = ["conduit_bin"]
diff --git a/src/client_server/alias.rs b/src/client_server/alias.rs
index 087221bc..848b9352 100644
--- a/src/client_server/alias.rs
+++ b/src/client_server/alias.rs
@@ -1,9 +1,9 @@
 use super::State;
-use crate::{ConduitResult, Database, Error, Ruma};
-use ruma::api::client::{
+use crate::{ConduitResult, Database, Error, Ruma, server_server};
+use ruma::api::{federation, client::{
     error::ErrorKind,
     r0::alias::{create_alias, delete_alias, get_alias},
-};
+}};
 
 #[cfg(feature = "conduit_bin")]
 use rocket::{delete, get, put};
@@ -43,12 +43,25 @@ pub fn delete_alias_route(
     feature = "conduit_bin",
     get("/_matrix/client/r0/directory/room/<_>", data = "<body>")
 )]
-pub fn get_alias_route(
+pub async fn get_alias_route(
     db: State<'_, Database>,
     body: Ruma<get_alias::IncomingRequest>,
 ) -> ConduitResult<get_alias::Response> {
     if body.room_alias.server_name() != db.globals.server_name() {
-        todo!("ask remote server");
+        let response = server_server::send_request(
+            &db,
+            body.room_alias.server_name().to_string(),
+            federation::query::get_room_information::v1::Request {
+                room_alias: body.room_alias.to_string(),
+            },
+        )
+        .await?;
+
+        return Ok(get_alias::Response {
+            room_id: response.room_id,
+            servers: response.servers,
+        }
+        .into());
     }
 
     let room_id = db
diff --git a/src/client_server/directory.rs b/src/client_server/directory.rs
index 279df181..26188f73 100644
--- a/src/client_server/directory.rs
+++ b/src/client_server/directory.rs
@@ -1,15 +1,18 @@
 use super::State;
-use crate::{ConduitResult, Database, Error, Result, Ruma};
+use crate::{server_server, ConduitResult, Database, Error, Result, Ruma};
 use ruma::{
-    api::client::{
-        error::ErrorKind,
-        r0::{
-            directory::{
-                self, get_public_rooms, get_public_rooms_filtered, get_room_visibility,
-                set_room_visibility,
+    api::{
+        client::{
+            error::ErrorKind,
+            r0::{
+                directory::{
+                    self, get_public_rooms, get_public_rooms_filtered, get_room_visibility,
+                    set_room_visibility,
+                },
+                room,
             },
-            room,
         },
+        federation,
     },
     events::{
         room::{avatar, canonical_alias, guest_access, history_visibility, name, topic},
@@ -29,6 +32,46 @@ pub async fn get_public_rooms_filtered_route(
     db: State<'_, Database>,
     body: Ruma<get_public_rooms_filtered::IncomingRequest>,
 ) -> ConduitResult<get_public_rooms_filtered::Response> {
+    if let Some(other_server) = body
+        .server
+        .clone()
+        .filter(|server| server != &db.globals.server_name().as_str())
+    {
+        let response = server_server::send_request(
+            &db,
+            other_server,
+            federation::directory::get_public_rooms::v1::Request {
+                limit: body.limit,
+                since: body.since.clone(),
+                room_network: federation::directory::get_public_rooms::v1::RoomNetwork::Matrix,
+            },
+        )
+        .await?;
+
+        return Ok(get_public_rooms_filtered::Response {
+            chunk: response
+                .chunk
+                .into_iter()
+                .map(|c| {
+                    // Convert ruma::api::federation::directory::get_public_rooms::v1::PublicRoomsChunk
+                    // to ruma::api::client::r0::directory::PublicRoomsChunk
+                    Ok::<_, Error>(
+                        serde_json::from_str(
+                            &serde_json::to_string(&c)
+                                .expect("PublicRoomsChunk::to_string always works"),
+                        )
+                        .expect("federation and client-server PublicRoomsChunk are the same type"),
+                    )
+                })
+                .filter_map(|r| r.ok())
+                .collect(),
+            prev_batch: response.prev_batch,
+            next_batch: response.next_batch,
+            total_room_count_estimate: response.total_room_count_estimate,
+        }
+        .into());
+    }
+
     let limit = body.limit.map_or(10, u64::from);
     let mut since = 0_u64;
 
@@ -169,26 +212,6 @@ pub async fn get_public_rooms_filtered_route(
 
     all_rooms.sort_by(|l, r| r.num_joined_members.cmp(&l.num_joined_members));
 
-    /*
-    all_rooms.extend_from_slice(
-        &server_server::send_request(
-            &db,
-            "privacytools.io".to_owned(),
-            ruma::api::federation::v1::get_public_rooms::Request {
-                limit: Some(20_u32.into()),
-                since: None,
-                room_network: ruma::api::federation::v1::get_public_rooms::RoomNetwork::Matrix,
-            },
-        )
-        .await
-        ?
-        .chunk
-        .into_iter()
-        .map(|c| serde_json::from_str(&serde_json::to_string(&c)?)?)
-        .collect::<Vec<_>>(),
-    );
-    */
-
     let total_room_count_estimate = (all_rooms.len() as u32).into();
 
     let chunk = all_rooms
diff --git a/src/client_server/membership.rs b/src/client_server/membership.rs
index 0ada7c40..84c0ebd3 100644
--- a/src/client_server/membership.rs
+++ b/src/client_server/membership.rs
@@ -1,16 +1,24 @@
 use super::State;
-use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Ruma};
+use crate::{
+    client_server, pdu::PduBuilder, server_server, utils, ConduitResult, Database, Error, Ruma,
+};
 use ruma::{
-    api::client::{
-        error::ErrorKind,
-        r0::membership::{
-            ban_user, forget_room, get_member_events, invite_user, join_room_by_id,
-            join_room_by_id_or_alias, joined_members, joined_rooms, kick_user, leave_room,
-            unban_user,
+    api::{
+        client::{
+            error::ErrorKind,
+            r0::{
+                alias,
+                membership::{
+                    ban_user, forget_room, get_member_events, invite_user, join_room_by_id,
+                    join_room_by_id_or_alias, joined_members, joined_rooms, kick_user, leave_room,
+                    unban_user,
+                },
+            },
         },
+        federation,
     },
     events::{room::member, EventType},
-    Raw, RoomId,
+    EventId, Raw, RoomId, RoomVersionId,
 };
 use std::{collections::BTreeMap, convert::TryFrom};
 
@@ -21,13 +29,81 @@ use rocket::{get, post};
     feature = "conduit_bin",
     post("/_matrix/client/r0/rooms/<_>/join", data = "<body>")
 )]
-pub fn join_room_by_id_route(
+pub async fn join_room_by_id_route(
     db: State<'_, Database>,
     body: Ruma<join_room_by_id::IncomingRequest>,
 ) -> ConduitResult<join_room_by_id::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    // TODO: Ask a remote server if we don't have this room
+    // Ask a remote server if we don't have this room
+    if !db.rooms.exists(&body.room_id)? && body.room_id.server_name() != db.globals.server_name() {
+        let make_join_response = server_server::send_request(
+            &db,
+            body.room_id.server_name().to_string(),
+            federation::membership::create_join_event_template::v1::Request {
+                room_id: body.room_id.clone(),
+                user_id: sender_id.clone(),
+                ver: vec![RoomVersionId::Version5, RoomVersionId::Version6],
+            },
+        )
+        .await?;
+
+        let mut join_event_stub_value =
+            serde_json::from_str::<serde_json::Value>(make_join_response.event.json().get())
+                .map_err(|_| {
+                    Error::BadServerResponse("Invalid make_join event json received from server.")
+                })?;
+
+        let join_event_stub =
+            join_event_stub_value
+                .as_object_mut()
+                .ok_or(Error::BadServerResponse(
+                    "Invalid make join event object received from server.",
+                ))?;
+
+        join_event_stub.insert(
+            "origin".to_owned(),
+            db.globals.server_name().to_owned().to_string().into(),
+        );
+        join_event_stub.insert(
+            "origin_server_ts".to_owned(),
+            utils::millis_since_unix_epoch().into(),
+        );
+
+        // Generate event id
+        let event_id = EventId::try_from(&*format!(
+            "${}",
+            ruma::signatures::reference_hash(&join_event_stub_value)
+                .expect("ruma can calculate reference hashes")
+        ))
+        .expect("ruma's reference hashes are valid event ids");
+
+        // We don't leave the event id into the pdu because that's only allowed in v1 or v2 rooms
+        let join_event_stub = join_event_stub_value.as_object_mut().unwrap();
+        join_event_stub.remove("event_id");
+
+        ruma::signatures::hash_and_sign_event(
+            db.globals.server_name().as_str(),
+            db.globals.keypair(),
+            &mut join_event_stub_value,
+        )
+        .expect("event is valid, we just created it");
+
+        let send_join_response = server_server::send_request(
+            &db,
+            body.room_id.server_name().to_string(),
+            federation::membership::create_join_event::v2::Request {
+                room_id: body.room_id.clone(),
+                event_id,
+                pdu_stub: serde_json::from_value::<Raw<_>>(join_event_stub_value)
+                    .expect("Raw::from_value always works"),
+            },
+        )
+        .await?;
+
+        dbg!(send_join_response);
+        todo!("Take send_join_response and 'create' the room using that data");
+    }
 
     let event = member::MemberEventContent {
         membership: member::MembershipState::Join,
@@ -61,16 +137,28 @@ pub fn join_room_by_id_route(
     feature = "conduit_bin",
     post("/_matrix/client/r0/join/<_>", data = "<body>")
 )]
-pub fn join_room_by_id_or_alias_route(
+pub async fn join_room_by_id_or_alias_route(
     db: State<'_, Database>,
+    db2: State<'_, Database>,
     body: Ruma<join_room_by_id_or_alias::Request>,
 ) -> ConduitResult<join_room_by_id_or_alias::Response> {
-    let room_id = RoomId::try_from(body.room_id_or_alias.clone()).or_else(|alias| {
-        Ok::<_, Error>(db.rooms.id_from_alias(&alias)?.ok_or(Error::BadRequest(
-            ErrorKind::NotFound,
-            "Room not found (TODO: Federation).",
-        ))?)
-    })?;
+    let room_id = match RoomId::try_from(body.room_id_or_alias.clone()) {
+        Ok(room_id) => room_id,
+        Err(room_alias) => {
+            client_server::get_alias_route(
+                db,
+                Ruma {
+                    body: alias::get_alias::IncomingRequest { room_alias },
+                    sender_id: body.sender_id.clone(),
+                    device_id: body.device_id.clone(),
+                    json_body: None,
+                },
+            )
+            .await?
+            .0
+            .room_id
+        }
+    };
 
     let body = Ruma {
         sender_id: body.sender_id.clone(),
@@ -83,7 +171,7 @@ pub fn join_room_by_id_or_alias_route(
     };
 
     Ok(join_room_by_id_or_alias::Response {
-        room_id: join_room_by_id_route(db, body)?.0.room_id,
+        room_id: join_room_by_id_route(db2, body).await?.0.room_id,
     }
     .into())
 }
diff --git a/src/client_server/room.rs b/src/client_server/room.rs
index 54e57fd8..b5f1529e 100644
--- a/src/client_server/room.rs
+++ b/src/client_server/room.rs
@@ -92,13 +92,6 @@ pub fn create_room_route(
         &db.account_data,
     )?;
 
-    // Figure out preset. We need it for power levels and preset specific events
-    let visibility = body.visibility.unwrap_or(room::Visibility::Private);
-    let preset = body.preset.unwrap_or_else(|| match visibility {
-        room::Visibility::Private => create_room::RoomPreset::PrivateChat,
-        room::Visibility::Public => create_room::RoomPreset::PublicChat,
-    });
-
     // 3. Power levels
     let mut users = BTreeMap::new();
     users.insert(sender_id.clone(), 100.into());
@@ -142,6 +135,14 @@ pub fn create_room_route(
     )?;
 
     // 4. Events set by preset
+
+    // Figure out preset. We need it for preset specific events
+    let visibility = body.visibility.unwrap_or(room::Visibility::Private);
+    let preset = body.preset.unwrap_or_else(|| match visibility {
+        room::Visibility::Private => create_room::RoomPreset::PrivateChat,
+        room::Visibility::Public => create_room::RoomPreset::PublicChat,
+    });
+
     // 4.1 Join Rules
     db.rooms.append_pdu(
         PduBuilder {
diff --git a/src/error.rs b/src/error.rs
index af5405c2..623aa0ef 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -27,6 +27,13 @@ pub enum Error {
         #[from]
         source: image::error::ImageError,
     },
+    #[error("Could not connect to server.")]
+    ReqwestError {
+        #[from]
+        source: reqwest::Error,
+    },
+    #[error("{0}")]
+    BadServerResponse(&'static str),
     #[error("{0}")]
     BadConfig(&'static str),
     #[error("{0}")]
diff --git a/src/lib.rs b/src/lib.rs
index 96236bfc..f761413b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
 pub mod client_server;
+pub mod server_server;
 mod database;
 mod error;
 mod pdu;
diff --git a/src/main.rs b/src/main.rs
index c7cd837a..93ca74e2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,13 +1,13 @@
 #![warn(rust_2018_idioms)]
 
-pub mod push_rules;
+pub mod server_server;
+pub mod client_server;
 
-mod client_server;
+mod push_rules;
 mod database;
 mod error;
 mod pdu;
 mod ruma_wrapper;
-//mod server_server;
 mod utils;
 
 pub use database::Database;
@@ -110,10 +110,10 @@ fn setup_rocket() -> rocket::Rocket {
                 client_server::get_key_changes_route,
                 client_server::get_pushers_route,
                 client_server::set_pushers_route,
-                //server_server::well_known_server,
-                //server_server::get_server_version,
-                //server_server::get_server_keys,
-                //server_server::get_server_keys_deprecated,
+                server_server::well_known_server,
+                server_server::get_server_version,
+                server_server::get_server_keys,
+                server_server::get_server_keys_deprecated,
             ],
         )
         .attach(AdHoc::on_attach("Config", |mut rocket| async {
diff --git a/src/server_server.rs b/src/server_server.rs
index a2141433..0af5546a 100644
--- a/src/server_server.rs
+++ b/src/server_server.rs
@@ -1,16 +1,15 @@
-use crate::{Database, MatrixResult};
+use crate::{ConduitResult, Database, Result};
 use http::header::{HeaderValue, AUTHORIZATION};
-use log::error;
 use rocket::{get, response::content::Json, State};
-use ruma::api::Endpoint;
-use ruma::api::client::error::Error;
 use ruma::api::federation::discovery::{
-    get_server_keys::v2 as get_server_keys, get_server_version::v1 as get_server_version,
+    get_server_keys, get_server_version::v1 as get_server_version, ServerKey, VerifyKey,
 };
+use ruma::api::OutgoingRequest;
 use serde_json::json;
 use std::{
     collections::BTreeMap,
     convert::TryFrom,
+    fmt::Debug,
     time::{Duration, SystemTime},
 };
 
@@ -33,36 +32,51 @@ pub async fn request_well_known(db: &crate::Database, destination: &str) -> Opti
     Some(body.get("m.server")?.as_str()?.to_owned())
 }
 
-pub async fn send_request<T: Endpoint>(
+pub async fn send_request<T: OutgoingRequest>(
     db: &crate::Database,
     destination: String,
     request: T,
-) -> Option<T::Response> {
-    let mut http_request: http::Request<_> = request.try_into().unwrap();
-
+) -> Result<T::IncomingResponse>
+where
+    T: Debug,
+{
     let actual_destination = "https://".to_owned()
         + &request_well_known(db, &destination)
             .await
             .unwrap_or(destination.clone() + ":8448");
-    *http_request.uri_mut() = (actual_destination + T::METADATA.path).parse().unwrap();
+
+    let mut http_request = request
+        .try_into_http_request(&actual_destination, Some(""))
+        .unwrap();
 
     let mut request_map = serde_json::Map::new();
 
     if !http_request.body().is_empty() {
         request_map.insert(
             "content".to_owned(),
-            serde_json::to_value(http_request.body()).unwrap(),
+            serde_json::from_slice(http_request.body()).unwrap(),
         );
     };
 
     request_map.insert("method".to_owned(), T::METADATA.method.to_string().into());
-    request_map.insert("uri".to_owned(), T::METADATA.path.into());
-    request_map.insert("origin".to_owned(), db.globals.server_name().into());
+    request_map.insert(
+        "uri".to_owned(),
+        http_request
+            .uri()
+            .path_and_query()
+            .expect("all requests have a path")
+            .to_string()
+            .into(),
+    );
+    request_map.insert(
+        "origin".to_owned(),
+        db.globals.server_name().as_str().into(),
+    );
     request_map.insert("destination".to_owned(), destination.into());
 
     let mut request_json = request_map.into();
     ruma::signatures::sign_json(
-        db.globals.server_name(),
+        db.globals.server_name().as_str(),
         db.globals.keypair(),
         &mut request_json,
     )
@@ -72,31 +86,32 @@ pub async fn send_request<T: Endpoint>(
         .as_object()
         .unwrap()
         .values()
-        .next()
-        .unwrap()
-        .as_object()
-        .unwrap()
-        .iter()
-        .map(|(k, v)| (k, v.as_str().unwrap()));
+        .map(|v| {
+            v.as_object()
+                .unwrap()
+                .iter()
+                .map(|(k, v)| (k, v.as_str().unwrap()))
+        });
 
-    for s in signatures {
-        http_request.headers_mut().insert(
-            AUTHORIZATION,
-            HeaderValue::from_str(&format!(
-                "X-Matrix origin={},key=\"{}\",sig=\"{}\"",
-                db.globals.server_name(),
-                s.0,
-                s.1
-            ))
-            .unwrap(),
-        );
+    for signature_server in signatures {
+        for s in signature_server {
+            http_request.headers_mut().insert(
+                AUTHORIZATION,
+                HeaderValue::from_str(&format!(
+                    "X-Matrix origin={},key=\"{}\",sig=\"{}\"",
+                    db.globals.server_name(),
+                    s.0,
+                    s.1
+                ))
+                .unwrap(),
+            );
+        }
     }
 
-    let reqwest_response = db
-        .globals
-        .reqwest_client()
-        .execute(http_request.into())
-        .await;
+    let reqwest_request = reqwest::Request::try_from(http_request)
+        .expect("all http requests are valid reqwest requests");
+
+    let reqwest_response = db.globals.reqwest_client().execute(reqwest_request).await;
 
     // Because reqwest::Response -> http::Response is complicated:
     match reqwest_response {
@@ -117,59 +132,56 @@ pub async fn send_request<T: Endpoint>(
                 .unwrap()
                 .into_iter()
                 .collect();
-            Some(
-                <T::Response>::try_from(http_response.body(body).unwrap())
-                    .ok()
-                    .unwrap(),
+            Ok(
+                T::IncomingResponse::try_from(http_response.body(body).unwrap())
+                    .expect("TODO: error handle other server errors"),
             )
         }
-        Err(e) => {
-            error!("{}", e);
-            None
-        }
+        Err(e) => Err(e.into()),
     }
 }
 
-#[cfg_attr(feature = "conduit_bin",get("/.well-known/matrix/server"))]
+#[cfg_attr(feature = "conduit_bin", get("/.well-known/matrix/server"))]
 pub fn well_known_server() -> Json<String> {
-    rocket::response::content::Json(
-        json!({ "m.server": "matrixtesting.koesters.xyz:14004"}).to_string(),
-    )
+    rocket::response::content::Json(json!({ "m.server": "pc.koesters.xyz:59003"}).to_string())
 }
 
-#[cfg_attr(feature = "conduit_bin",get("/_matrix/federation/v1/version"))]
-pub fn get_server_version() -> MatrixResult<get_server_version::Response, Error> {
-    MatrixResult(Ok(get_server_version::Response {
+#[cfg_attr(feature = "conduit_bin", get("/_matrix/federation/v1/version"))]
+pub fn get_server_version() -> ConduitResult<get_server_version::Response> {
+    Ok(get_server_version::Response {
         server: Some(get_server_version::Server {
             name: Some("Conduit".to_owned()),
             version: Some(env!("CARGO_PKG_VERSION").to_owned()),
         }),
-    }))
+    }
+    .into())
 }
 
-#[cfg_attr(feature = "conduit_bin",get("/_matrix/key/v2/server"))]
+#[cfg_attr(feature = "conduit_bin", get("/_matrix/key/v2/server"))]
 pub fn get_server_keys(db: State<'_, Database>) -> Json<String> {
     let mut verify_keys = BTreeMap::new();
     verify_keys.insert(
         format!("ed25519:{}", db.globals.keypair().version()),
-        get_server_keys::VerifyKey {
+        VerifyKey {
             key: base64::encode_config(db.globals.keypair().public_key(), base64::STANDARD_NO_PAD),
         },
     );
     let mut response = serde_json::from_slice(
-        http::Response::try_from(get_server_keys::Response {
-            server_name: db.globals.server_name().to_owned(),
-            verify_keys,
-            old_verify_keys: BTreeMap::new(),
-            signatures: BTreeMap::new(),
-            valid_until_ts: SystemTime::now() + Duration::from_secs(60 * 2),
+        http::Response::try_from(get_server_keys::v2::Response {
+            server_key: ServerKey {
+                server_name: db.globals.server_name().to_owned(),
+                verify_keys,
+                old_verify_keys: BTreeMap::new(),
+                signatures: BTreeMap::new(),
+                valid_until_ts: SystemTime::now() + Duration::from_secs(60 * 2),
+            },
         })
         .unwrap()
         .body(),
     )
     .unwrap();
     ruma::signatures::sign_json(
-        db.globals.server_name(),
+        db.globals.server_name().as_str(),
         db.globals.keypair(),
         &mut response,
     )
@@ -177,7 +189,7 @@ pub fn get_server_keys(db: State<'_, Database>) -> Json<String> {
     Json(response.to_string())
 }
 
-#[cfg_attr(feature = "conduit_bin",get("/_matrix/key/v2/server/<_key_id>"))]
-pub fn get_server_keys_deprecated(db: State<'_, Database>, _key_id: String) -> Json<String> {
+#[cfg_attr(feature = "conduit_bin", get("/_matrix/key/v2/server/<_>"))]
+pub fn get_server_keys_deprecated(db: State<'_, Database>) -> Json<String> {
     get_server_keys(db)
 }