diff --git a/Sources/ElixxirDAppsSDK/Connection.swift b/Sources/ElixxirDAppsSDK/Connection.swift
index a723764bbfd3cfef97d5dbda159bf64ad019c58c..473b897146f721bf656ae1617a7cb4fa92886b2d 100644
--- a/Sources/ElixxirDAppsSDK/Connection.swift
+++ b/Sources/ElixxirDAppsSDK/Connection.swift
@@ -3,6 +3,7 @@ import Bindings
 public struct Connection {
   public var isAuthenticated: () -> Bool
   public var send: MessageSender
+  public var listen: MessageListener
 }
 
 extension Connection {
@@ -11,7 +12,8 @@ extension Connection {
   ) -> Connection {
     Connection(
       isAuthenticated: { false },
-      send: .live(bindingsConnection: bindingsConnection)
+      send: .live(bindingsConnection: bindingsConnection),
+      listen: .live(bindingsConnection: bindingsConnection)
     )
   }
 
@@ -20,7 +22,8 @@ extension Connection {
   ) -> Connection {
     Connection(
       isAuthenticated: bindingsAuthenticatedConnection.isAuthenticated,
-      send: .live(bindingsAuthenticatedConnection: bindingsAuthenticatedConnection)
+      send: .live(bindingsAuthenticatedConnection: bindingsAuthenticatedConnection),
+      listen: .live(bindingsAuthenticatedConnection: bindingsAuthenticatedConnection)
     )
   }
 }
@@ -29,7 +32,8 @@ extension Connection {
 extension Connection {
   public static let failing = Connection(
     isAuthenticated: { false },
-    send: .failing
+    send: .failing,
+    listen: .failing
   )
 }
 #endif
diff --git a/Sources/ElixxirDAppsSDK/MessageListener.swift b/Sources/ElixxirDAppsSDK/MessageListener.swift
new file mode 100644
index 0000000000000000000000000000000000000000..67e1fcd50d90c6c66baefa7df5a6a5abeb00c6cc
--- /dev/null
+++ b/Sources/ElixxirDAppsSDK/MessageListener.swift
@@ -0,0 +1,72 @@
+import Bindings
+
+public struct MessageListener {
+  public var listen: (Int, String, @escaping (Data) -> Void) -> Data
+
+  public func callAsFunction(
+    messageType: Int,
+    listenerName: String = "MessageListener",
+    callback: @escaping (Data) -> Void
+  ) -> Data {
+    listen(messageType, listenerName, callback)
+  }
+}
+
+extension MessageListener {
+  public static func live(
+    bindingsConnection: BindingsConnection
+  ) -> MessageListener {
+    MessageListener.live(
+      register: bindingsConnection.registerListener(_:newListener:)
+    )
+  }
+
+  public static func live(
+    bindingsAuthenticatedConnection: BindingsAuthenticatedConnection
+  ) -> MessageListener {
+    MessageListener.live(
+      register: bindingsAuthenticatedConnection.registerListener(_:newListener:)
+    )
+  }
+
+  private static func live(
+    register: @escaping (Int, BindingsListenerProtocol) -> Data?
+  ) -> MessageListener {
+    MessageListener { messageType, listenerName, callback in
+      let listener = Listener(listenerName: listenerName, onHear: callback)
+      let listenerId = register(messageType, listener)
+      guard let listenerId = listenerId else {
+        fatalError("BindingsConnection.registerListener returned `nil`")
+      }
+      return listenerId
+    }
+  }
+}
+
+private class Listener: NSObject, BindingsListenerProtocol {
+  init(listenerName: String, onHear: @escaping (Data) -> Void) {
+    self.listenerName = listenerName
+    self.onHear = onHear
+    super.init()
+  }
+
+  let listenerName: String
+  let onHear: (Data) -> Void
+
+  func hear(_ item: Data?) {
+    guard let item = item else { return }
+    onHear(item)
+  }
+
+  func name() -> String {
+    listenerName
+  }
+}
+
+#if DEBUG
+extension MessageListener {
+  public static let failing = MessageListener { _, _, _ in
+    Data()
+  }
+}
+#endif