diff --git a/Sources/ElixxirDAppsSDK/ServiceProcessor.swift b/Sources/ElixxirDAppsSDK/ServiceProcessor.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f6798fa23662c0875ef2d33879937b1dc468afa9
--- /dev/null
+++ b/Sources/ElixxirDAppsSDK/ServiceProcessor.swift
@@ -0,0 +1,54 @@
+import Bindings
+import XCTestDynamicOverlay
+
+public struct ServiceProcessor {
+  public typealias Process = (
+    _ message: Data,
+    _ receptionId: Data,
+    _ ephemeralId: Int64,
+    _ roundId: Int64
+  ) -> Void
+
+  public init(serviceTag: String, process: @escaping Process) {
+    self.serviceTag = serviceTag
+    self.process = process
+  }
+
+  public var serviceTag: String
+  public var process: Process
+}
+
+extension ServiceProcessor {
+  public static let unimplemented = ServiceProcessor(
+    serviceTag: "unimplemented",
+    process: XCTUnimplemented("\(Self.self).process")
+  )
+}
+
+extension ServiceProcessor {
+  func makeBindingsProcessor() -> BindingsProcessorProtocol {
+    class Processor: NSObject, BindingsProcessorProtocol {
+      init(_ serviceProcessor: ServiceProcessor) {
+        self.serviceProcessor = serviceProcessor
+      }
+
+      let serviceProcessor: ServiceProcessor
+
+      func process(_ message: Data?, receptionId: Data?, ephemeralId: Int64, roundId: Int64) {
+        guard let message = message else {
+          fatalError("BindingsProcessor.process received `nil` message")
+        }
+        guard let receptionId = receptionId else {
+          fatalError("BindingsProcessor.process received `nil` receptionId")
+        }
+        serviceProcessor.process(message, receptionId, ephemeralId, roundId)
+      }
+
+      func string() -> String {
+        serviceProcessor.serviceTag
+      }
+    }
+
+    return Processor(self)
+  }
+}