diff --git a/src/bma/wot/mod.rs b/src/bma/wot/mod.rs
index 9046a951b044790176a2c9a2acadb4ccf9408c0c..6eaac050d288223032e4cc9979cb618ba7d9efb5 100644
--- a/src/bma/wot/mod.rs
+++ b/src/bma/wot/mod.rs
@@ -9,6 +9,11 @@ struct BmaCertification {
     cert: String
 }
 
+#[derive(Serialize, Deserialize)]
+struct BmaMembership {
+    membership: String
+}
+
 impl Display for BmaCertification {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
         f.write_str(self.cert.as_str())
@@ -24,6 +29,21 @@ pub fn certify(node: &BmaNode, idty: &String, sig: &String) -> Result<(), String
         .send()
         .expect("Error during POST /wot/certify");
 
+    if resp.status() != 200 {
+        return Err(format!("KO status : {}, message: {}", resp.status(), resp.text().unwrap()));
+    }
+    Ok(())
+}
+
+pub fn membership(node: &BmaNode, idty: &String, sig: &String) -> Result<(), String> {
+    let ms = BmaMembership { membership: format!("{}{}\n", idty, sig) };
+    let address = node.get_address();
+    let client = reqwest::blocking::Client::new();
+    let resp = client.post(format!("{}/blockchain/membership", address))
+        .json(&ms)
+        .send()
+        .expect("Error during POST /blockchain/membership");
+
     if resp.status() != 200 {
         return Err(format!("KO status : {}, message: {}", resp.status(), resp.text().unwrap()));
     }
diff --git a/src/cli/adhere.rs b/src/cli/adhere.rs
new file mode 100644
index 0000000000000000000000000000000000000000..41bb49db9be8fd39def2bb0895dcc7fa06e923ab
--- /dev/null
+++ b/src/cli/adhere.rs
@@ -0,0 +1,49 @@
+use crate::bma::BmaNode;
+use crate::bma::lookup_identity::{LookupResult, lookup2identities, lookup_print};
+use crate::crypto::duniter_key::scrypt_duniter_key::ScryptDuniterKey;
+use crate::crypto::duniter_key::ToDuniterKey;
+use crate::dubp::documents::membership::Membership;
+use crate::dubp::signable::Signable;
+
+pub fn adhere(node: &BmaNode) -> Result<(), String> {
+
+    println!("Deriving key for signature...");
+    let keyring = ScryptDuniterKey::new(String::from("fakesalt"), String::from("fakepasswd")).derive(); // TODO: get salt/passwd from file/cli
+
+    let uid_or_pub = keyring.get_public_base58();
+    let address = node.get_address();
+    println!("Fetching identity using \"{}\" pattern...", uid_or_pub);
+    let resp = reqwest::blocking::get(format!("{}/wot/lookup/{}", address, uid_or_pub)).expect("Could not fetch lookup data from distant node");
+    let res_json = &resp.json().expect("Could not get JSON result from distant node");
+    let res_json: LookupResult = LookupResult(res_json);
+    let results = lookup2identities(&res_json);
+    let idty = match results.len() {
+        0 => return Err(String::from("No matching identity found")),
+        1 => &results[0],
+        _ => {
+            lookup_print(&res_json);
+            return Err(format!("Too much identities found ({})", results.len()))
+        },
+    };
+
+    if idty.revoked {
+        return Err(format!("Identity {} {} is revoked and cannot be certified anymore.", idty.public, idty.uid))
+    }
+
+    println!("Get current block for blockstamp...");
+    let current = crate::bma::blockchain::current(&node);
+    let currency = &current.currency;
+    let issuer = keyring.get_public_base58();
+    let current_blockstamp = &current.blockstamp();
+
+    let membership = Membership {
+        version: 10,
+        currency: currency.clone(),
+        issuer,
+        idty_uid: (&idty).uid.clone(),
+        idty_blockstamp: (&idty).blockstamp.clone(),
+        adhere_blockstamp: current_blockstamp.clone(),
+    };
+    let signature = keyring.sign(&membership);
+    crate::bma::wot::membership(&node, &membership.to_signable(), &signature.to_string())
+}
\ No newline at end of file
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index a6c4df6b0f503ffdae80394f5a77fd320643c7a0..311154d5f5d3f07b6548a6a3095bb2e311a05902 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -8,10 +8,15 @@ use crate::crypto::duniter_key::ScryptDuniterKey;
 use core::fmt;
 
 pub mod certify;
+pub mod adhere;
 
 const DEFAULT_NODE: &str = "https://g1-test.duniter.org";
-
-// TODO: constantize pub, sec, etc
+const CMD_PUB: &str = "pub";
+const CMD_SEC: &str = "sec";
+const CMD_KEYRING: &str = "keyring";
+const CMD_LOOKUP: &str = "lookup";
+const CMD_CERTIFY: &str = "certify";
+const CMD_ADHERE: &str = "adhere";
 
 pub enum Command {
     /// Compute the public key from salt/passwd and displays it
@@ -24,6 +29,8 @@ pub enum Command {
     LOOKUP(String, BmaNode, String),
     /// Search an identity on a Duniter node (BMA API) and certify it
     CERTIFY(String, BmaNode, String),
+    /// Search an identity matching local pubkey on a Duniter node (BMA API) and send a membership based on it
+    ADHERE(String, BmaNode),
     /// Some unknown command
     UNKNOWN(String),
 }
@@ -32,11 +39,12 @@ impl Command {
 
     pub fn name(&self) -> String {
         let str_name = match self {
-            PUB(_, _, _) => "pub",
-            SEC(_, _, _) => "sec",
-            KEYRING(_, _, _) => "keyring",
-            LOOKUP(_, _, _) => "lookup",
-            CERTIFY(_, _, _) => "certify",
+            PUB(..) => CMD_PUB,
+            SEC(..) => CMD_SEC,
+            KEYRING(..) => CMD_KEYRING,
+            LOOKUP(..) => CMD_LOOKUP,
+            CERTIFY(..) => CMD_CERTIFY,
+            ADHERE(..) => CMD_ADHERE,
             UNKNOWN(name) => name.as_str(),
         };
         str_name.to_string()
@@ -51,8 +59,8 @@ impl Command {
             KEYRING(_, salt, passwd) => println!("{}", compute_key(ScryptDuniterKey::new(salt.to_string(), passwd.to_string()))),
             LOOKUP(_, bma_node, uid_or_pub) => bma::lookup_identity::lookup(bma_node, uid_or_pub),
             CERTIFY(_, bma_node, uid_or_pub) => cli::certify::certify(bma_node, uid_or_pub).unwrap_or_else(|e| eprintln!("{}", e)),
+            ADHERE(_, bma_node) => cli::adhere::adhere(bma_node).unwrap_or_else(|e| eprintln!("{}", e)),
             UNKNOWN(cmd) => eprintln!("Unknown command {}", cmd),
-            _ => return Err("Not handled command yet".to_string())
         }
         Ok(())
     }
@@ -67,33 +75,38 @@ impl Command {
 
         let command = command.as_str();
         match command {
-            "pub" => {
+            CMD_PUB => {
                 let salt = args.next().expect("Salt must be provided");
                 let passwd = args.next().expect("Password must be provided");
                 Some(PUB(command.to_string(), salt, passwd))
             },
-            "sec" => {
+            CMD_SEC => {
                 let salt = args.next().expect("Salt must be provided");
                 let passwd = args.next().expect("Password must be provided");
                 Some(SEC(command.to_string(), salt, passwd))
             },
-            "keyring" => {
+            CMD_KEYRING => {
                 let salt = args.next().expect("Salt must be provided");
                 let passwd = args.next().expect("Password must be provided");
                 Some(KEYRING(command.to_string(), salt, passwd))
             },
-            "lookup" => {
+            CMD_LOOKUP => {
                 let uid_or_pub = args.next().expect("UID or pubkey must be provided");
                 let default = String::from(DEFAULT_NODE);
                 let bma_node = BmaNode::new(env::var("DUNITER_NODE").unwrap_or(default).as_str());
                 Some(LOOKUP(command.to_string(), bma_node, uid_or_pub))
             },
-            "cert" => {
+            CMD_CERTIFY => {
                 let uid_or_pub = args.next().expect("UID or pubkey must be provided");
                 let default = String::from(DEFAULT_NODE);
                 let bma_node = BmaNode::new(env::var("DUNITER_NODE").unwrap_or(default).as_str());
                 Some(CERTIFY(command.to_string(), bma_node, uid_or_pub))
             },
+            CMD_ADHERE => {
+                let default = String::from(DEFAULT_NODE);
+                let bma_node = BmaNode::new(env::var("DUNITER_NODE").unwrap_or(default).as_str());
+                Some(ADHERE(command.to_string(), bma_node))
+            },
             _ => Some(UNKNOWN(command.to_string())),
         }
     }
diff --git a/src/dubp/documents/membership.rs b/src/dubp/documents/membership.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4d39f3a162a6d770ff87d057b52fab01f173edf4
--- /dev/null
+++ b/src/dubp/documents/membership.rs
@@ -0,0 +1,23 @@
+#[derive(Debug)]
+pub struct Membership {
+    pub version: u32,
+    pub currency: String,
+    pub issuer: String,
+    pub idty_uid: String,
+    pub idty_blockstamp: String,
+    pub adhere_blockstamp: String,
+}
+
+impl crate::dubp::signable::Signable for Membership {
+    fn to_signable(self: &Self) -> String {
+        format!("Version: {}
+Type: Membership
+Currency: {}
+Issuer: {}
+Block: {}
+Membership: IN
+UserID: {}
+CertTS: {}
+", self.version, self.currency, self.issuer, self.adhere_blockstamp, self.idty_uid, self.idty_blockstamp)
+    }
+}
\ No newline at end of file
diff --git a/src/dubp/documents/mod.rs b/src/dubp/documents/mod.rs
index 17d2a223483393bc9986a09b6a9bf66500193f4d..891883c01beaff6e7454d7cc1780674d47b4f55c 100644
--- a/src/dubp/documents/mod.rs
+++ b/src/dubp/documents/mod.rs
@@ -1 +1,2 @@
-pub mod certification;
\ No newline at end of file
+pub mod certification;
+pub mod membership;
\ No newline at end of file