diff --git a/Split.xcodeproj/project.pbxproj b/Split.xcodeproj/project.pbxproj
index 2e0582f60..4455e26ba 100644
--- a/Split.xcodeproj/project.pbxproj
+++ b/Split.xcodeproj/project.pbxproj
@@ -353,6 +353,8 @@
 		59FB7C35220329B900ECC96A /* SplitFactoryBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FB7C34220329B900ECC96A /* SplitFactoryBuilderTests.swift */; };
 		59FB7C3C2203795F00ECC96A /* LocalhostSplitsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FB7C3B2203795F00ECC96A /* LocalhostSplitsParser.swift */; };
 		59FB7C3E22037B9400ECC96A /* SpaceDelimitedLocalhostSplitsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FB7C3D22037B9400ECC96A /* SpaceDelimitedLocalhostSplitsParser.swift */; };
+		5B91B8362DD84276000510F0 /* SplitMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B91B8352DD84272000510F0 /* SplitMetadata.swift */; };
+		5B91B8372DD84276000510F0 /* SplitMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B91B8352DD84272000510F0 /* SplitMetadata.swift */; };
 		9500D9922C56F9BA00383593 /* HostDomainFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9500D9912C56F9BA00383593 /* HostDomainFilterTests.swift */; };
 		9500D9A92C59297400383593 /* HostDomainFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9500D9A82C59297400383593 /* HostDomainFilter.swift */; };
 		9500D9AA2C59382000383593 /* HostDomainFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9500D9A82C59297400383593 /* HostDomainFilter.swift */; };
@@ -1494,6 +1496,7 @@
 		59FB7C34220329B900ECC96A /* SplitFactoryBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitFactoryBuilderTests.swift; sourceTree = "<group>"; };
 		59FB7C3B2203795F00ECC96A /* LocalhostSplitsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalhostSplitsParser.swift; sourceTree = "<group>"; };
 		59FB7C3D22037B9400ECC96A /* SpaceDelimitedLocalhostSplitsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceDelimitedLocalhostSplitsParser.swift; sourceTree = "<group>"; };
+		5B91B8352DD84272000510F0 /* SplitMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitMetadata.swift; sourceTree = "<group>"; };
 		9500D9912C56F9BA00383593 /* HostDomainFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostDomainFilterTests.swift; sourceTree = "<group>"; };
 		9500D9A82C59297400383593 /* HostDomainFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostDomainFilter.swift; sourceTree = "<group>"; };
 		9500D9AC2C5A918300383593 /* split_cache_v5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = split_cache_v5.xcdatamodel; sourceTree = "<group>"; };
@@ -2296,6 +2299,7 @@
 			isa = PBXGroup;
 			children = (
 				3B6DEEC420EA6AE20067435E /* SplitEvent.swift */,
+				5B91B8352DD84272000510F0 /* SplitMetadata.swift */,
 				3B6DEEC520EA6AE20067435E /* SplitEventsManager.swift */,
 				9530FD7927F24306005027AA /* EventsManagerCoordinator.swift */,
 				3B6DEEC620EA6AE20067435E /* SplitEventTask.swift */,
@@ -3954,6 +3958,7 @@
 				957E674C25E48753006F5B19 /* Bundle+Finder.swift in Sources */,
 				95F3F09525AE1F3800084AF8 /* ServiceConstants.swift in Sources */,
 				95CED0362B459A10005E3C34 /* LocalhostSynchronizer.swift in Sources */,
+				5B91B8372DD84276000510F0 /* SplitMetadata.swift in Sources */,
 				9505682426836B20001D7B10 /* ImpressionsCountRecorder.swift in Sources */,
 				3B6DEF3020EA6AE50067435E /* Evaluator.swift in Sources */,
 				598EDE78224BB3E9005D4762 /* InternalSplitClient.swift in Sources */,
@@ -4875,6 +4880,7 @@
 				95B02D9828D0BDC30030EC8B /* RestClient+UniqueKeys.swift in Sources */,
 				95B02D9928D0BDC30030EC8B /* RestClient+ImpressionsCount.swift in Sources */,
 				95B02D9A28D0BDC30030EC8B /* RestClient+SplitChanges.swift in Sources */,
+				5B91B8362DD84276000510F0 /* SplitMetadata.swift in Sources */,
 				95B02D9B28D0BDC30030EC8B /* DataResult.swift in Sources */,
 				958AD2132CA457C100E3DD43 /* RetryableSegmentsSyncWorker.swift in Sources */,
 				9566744729F03662001B4FA5 /* DbCipher.swift in Sources */,
diff --git a/Split/Api/DefaultSplitClient.swift b/Split/Api/DefaultSplitClient.swift
index bbf3aa8a7..95526ae67 100644
--- a/Split/Api/DefaultSplitClient.swift
+++ b/Split/Api/DefaultSplitClient.swift
@@ -55,49 +55,45 @@ public final class DefaultSplitClient: NSObject, SplitClient, TelemetrySplitClie
     }
 }
 
-// MARK: Events
+// MARK: Events Listeners
 extension DefaultSplitClient {
+    
+    private func onWithMetadata(event: SplitEventWithMetadata, runInBackground: Bool, queue: DispatchQueue?, execute actionWithMetadata: @escaping SplitActionWithMetadata) {
+        guard let factory = clientManager?.splitFactory else { return }
+        let task = SplitEventActionTask(action: actionWithMetadata, event: event.type, runInBackground: runInBackground, factory: factory, queue: queue)
+        on(event: event.type, executeTask: task)
+    }
+    
+    public func on(event: SplitEvent, executeWithMetadata action: SplitActionWithMetadata?) {
+        guard let action = action else { return }
+        onWithMetadata(event: SplitEventWithMetadata(type: event, metadata: nil), runInBackground: true, queue: nil, execute: action)
+    }
+    
+    private func on(event: SplitEvent, executeTask task: SplitEventActionTask) {
+        if  event != .sdkReadyFromCache, eventsManager.eventAlreadyTriggered(event: event) {
+            Logger.w("A handler was added for \(event.toString()) on the SDK, which has already fired and won’t be emitted again. The callback won’t be executed.")
+            return
+        }
+        eventsManager.register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task)
+    }
 
     public func on(event: SplitEvent, execute action: @escaping SplitAction) {
         on(event: event, runInBackground: false, queue: nil, execute: action)
     }
 
-    public func on(event: SplitEvent, runInBackground: Bool,
-                   execute action: @escaping SplitAction) {
+    public func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) {
         on(event: event, runInBackground: runInBackground, queue: nil, execute: action)
     }
 
-    public func on(event: SplitEvent,
-                   queue: DispatchQueue, execute action: @escaping SplitAction) {
+    public func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) {
         on(event: event, runInBackground: true, queue: queue, execute: action)
     }
 
-    private func on(event: SplitEvent,
-                    runInBackground: Bool,
-                    queue: DispatchQueue?,
-                    execute action: @escaping SplitAction) {
-
-        guard let factory = clientManager?.splitFactory else {
-            return
-        }
-
-        let task = SplitEventActionTask(action: action, event: event,
-                                        runInBackground: runInBackground,
-                                        factory: factory,
-                                        queue: queue)
-        task.event = event
+    private func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitAction) {
+        guard let factory = clientManager?.splitFactory else { return }
+        let task = SplitEventActionTask(action: action, event: event, runInBackground: runInBackground, factory: factory, queue: queue)
         on(event: event, executeTask: task)
     }
-
-    private func on(event: SplitEvent, executeTask task: SplitEventTask) {
-        if  event != .sdkReadyFromCache,
-            eventsManager.eventAlreadyTriggered(event: event) {
-            Logger.w("A handler was added for \(event.toString()) on the SDK, " +
-                     "which has already fired and won’t be emitted again. The callback won’t be executed.")
-            return
-        }
-        eventsManager.register(event: event, task: task)
-    }
 }
 
 // MARK: Treatment / Evaluation
diff --git a/Split/Api/FailHelpers.swift b/Split/Api/FailHelpers.swift
index 6f90ba7b7..b3e5496f8 100644
--- a/Split/Api/FailHelpers.swift
+++ b/Split/Api/FailHelpers.swift
@@ -10,10 +10,10 @@ import Foundation
 
 /// To avoid crashing host app this dummy components will be returned
 /// on Failed init
-///
 
 class FailedClient: SplitClient {
 
+    // MARK: Treatments
     func getTreatment(_ split: String) -> String {
         return SplitConstants.control
     }
@@ -54,17 +54,16 @@ class FailedClient: SplitClient {
         return [:]
     }
 
-    func on(event: SplitEvent, execute action: @escaping SplitAction) {
-    }
+    // MARK: Events Listeners
+    func on(event: SplitEvent, execute action: @escaping SplitAction) {}
 
-    func on(event: SplitEvent, runInBackground: Bool,
-            execute action: @escaping SplitAction) {
-    }
+    func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) {}
 
-    func on(event: SplitEvent,
-            queue: DispatchQueue, execute action: @escaping SplitAction) {
-    }
+    func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) {}
+    
+    func on(event: SplitEvent, executeWithMetadata: SplitActionWithMetadata) {}
 
+    // MARK: Tracking
     func track(trafficType: String, eventType: String) -> Bool {
         return false
     }
@@ -105,6 +104,7 @@ class FailedClient: SplitClient {
         return false
     }
     
+    // MARK: By FlagSets
     func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: String] {
         return [:]
     }
@@ -137,19 +137,18 @@ class FailedClient: SplitClient {
         return [:]
     }
 
-    func setUserConsent(enabled: Bool) {
-    }
+    func setUserConsent(enabled: Bool) {}
 
-    func flush() {
-    }
+    // MARK: Lifeycle
+    func flush() {}
 
-    func destroy() {
-    }
+    func destroy() {}
 
     func destroy(completion: (() -> Void)?) {
         completion?()
     }
 
+    // MARK: Tracking
     func track(trafficType: String, eventType: String, properties: [String: Any]?) -> Bool {
         return false
     }
diff --git a/Split/Api/LocalhostSplitClient.swift b/Split/Api/LocalhostSplitClient.swift
index 2aaacca6b..71144b854 100644
--- a/Split/Api/LocalhostSplitClient.swift
+++ b/Split/Api/LocalhostSplitClient.swift
@@ -64,6 +64,7 @@ public final class LocalhostSplitClient: NSObject, SplitClient {
         super.init()
     }
 
+    // MARK: Treatments
     public func getTreatment(_ split: String, attributes: [String: Any]?) -> String {
         return getTreatmentWithConfig(split).treatment
     }
@@ -121,25 +122,44 @@ public final class LocalhostSplitClient: NSObject, SplitClient {
         return results
     }
 
-    public func on(event: SplitEvent, runInBackground: Bool,
-                   execute action: @escaping SplitAction) {
+    // MARK: Events Listeners
+    public func on(event: SplitEvent, execute action: @escaping SplitAction) {
+        on(event: event, runInBackground: false, queue: nil, execute: action)
+    }
+    
+    public func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) {
+        on(eventWithMetadata: SplitEventWithMetadata(type: event, metadata: nil), runInBackground: false, queue: nil, execute: executeWithMetadata)
+    }
+    
+    public func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) {
         on(event: event, runInBackground: runInBackground, queue: nil, execute: action)
     }
 
     public func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) {
         on(event: event, runInBackground: true, queue: queue, execute: action)
     }
-
-    public func on(event: SplitEvent, execute action: @escaping SplitAction) {
-        on(event: event, runInBackground: false, queue: nil, execute: action)
+    
+    private func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitAction) {
+        on(eventWithMetadata: SplitEventWithMetadata(type: event, metadata: nil), runInBackground: runInBackground, queue: queue, execute: action)
     }
 
-    private func on(event: SplitEvent, runInBackground: Bool,
-                    queue: DispatchQueue?, execute action: @escaping SplitAction) {
+    private func on(eventWithMetadata event: SplitEventWithMetadata, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitAction) {
+
+        guard let factory = clientManger?.splitFactory else { return }
+        if let eventsManager = self.eventsManager {
+            let task = SplitEventActionTask(action: action, event: event.type,
+                                            runInBackground: runInBackground,
+                                            factory: factory,
+                                            queue: queue)
+            eventsManager.register(event: event, task: task)
+        }
+    }
+    
+    private func on(eventWithMetadata event: SplitEventWithMetadata, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitActionWithMetadata) {
 
         guard let factory = clientManger?.splitFactory else { return }
         if let eventsManager = self.eventsManager {
-            let task = SplitEventActionTask(action: action, event: event,
+            let task = SplitEventActionTask(action: action, event: event.type,
                                             runInBackground: runInBackground,
                                             factory: factory,
                                             queue: queue)
@@ -147,6 +167,7 @@ public final class LocalhostSplitClient: NSObject, SplitClient {
         }
     }
 
+    // MARK: Tracking
     public func track(trafficType: String, eventType: String) -> Bool {
         return true
     }
@@ -179,11 +200,10 @@ public final class LocalhostSplitClient: NSObject, SplitClient {
         return true
     }
 
-    public func setUserConsent(enabled: Bool) {
-    }
+    public func setUserConsent(enabled: Bool) {}
 
-    public func flush() {
-    }
+    // MARK: Lifecycle
+    public func flush() {}
 
     public func destroy() {
         splitsStorage.destroy()
@@ -195,7 +215,7 @@ public final class LocalhostSplitClient: NSObject, SplitClient {
     }
 }
 
-// MARK: Persistent attributes feature
+// MARK: Persistence
 extension LocalhostSplitClient {
 
     public func setAttribute(name: String, value: Any) -> Bool {
@@ -223,7 +243,7 @@ extension LocalhostSplitClient {
     }
 }
 
-// MARK: TreatmentBySets Feature
+// MARK: By FlagSet
 extension LocalhostSplitClient {
     public func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: String] {
         return [String: String]()
@@ -249,13 +269,11 @@ extension LocalhostSplitClient {
         return [String: String]()
     }
 
-    public func getTreatmentsWithConfigByFlagSet(_ flagSet: String,
-                                                 attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] {
+    public func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] {
         return [String: SplitResult]()
     }
 
-    public func getTreatmentsWithConfigByFlagSets(_ flagSets: [String],
-                                                  attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] {
+    public func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] {
         return [String: SplitResult]()
     }
 }
diff --git a/Split/Api/SplitClient.swift b/Split/Api/SplitClient.swift
index ff4df2005..86a7fdae5 100644
--- a/Split/Api/SplitClient.swift
+++ b/Split/Api/SplitClient.swift
@@ -8,91 +8,78 @@
 
 import Foundation
 
-public typealias SplitAction = () -> Void
-
 @objc public protocol SplitClient {
+    
+    // MARK: Listeners for customer
+    func on(event: SplitEvent, execute action: @escaping SplitAction)
+    func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) -> Void
+    func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction)
+    func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction)
+    
 
-    // MARK: Evaluation feature
+    // MARK: Treatments
     func getTreatment(_ split: String, attributes: [String: Any]?) -> String
     func getTreatment(_ split: String) -> String
-    @objc(getTreatmentsForSplits:attributes:) func getTreatments(splits: [String],
-                                                                 attributes: [String: Any]?) -> [String: String]
-
+    @objc(getTreatmentsForSplits:attributes:)
+    func getTreatments(splits: [String], attributes: [String: Any]?) -> [String: String]
+    
+    // With config
     func getTreatmentWithConfig(_ split: String) -> SplitResult
     func getTreatmentWithConfig(_ split: String, attributes: [String: Any]?) -> SplitResult
-
     @objc(getTreatmentsWithConfigForSplits:attributes:)
     func getTreatmentsWithConfig(splits: [String], attributes: [String: Any]?) -> [String: SplitResult]
     
-    // MARK: Evaluation with Properties
+    // With Properties
     func getTreatment(_ split: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> String
-    @objc(getTreatmentsForSplits:attributes:evaluationOptions:) func getTreatments(splits: [String],
-                                                                 attributes: [String: Any]?,
-                                                                 evaluationOptions: EvaluationOptions?) -> [String: String]
+    @objc(getTreatmentsForSplits:attributes:evaluationOptions:)
+    func getTreatments(splits: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String]
     func getTreatmentWithConfig(_ split: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> SplitResult
     @objc(getTreatmentsWithConfigForSplits:attributes:evaluationOptions:)
     func getTreatmentsWithConfig(splits: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult]
-
-    func on(event: SplitEvent, execute action: @escaping SplitAction)
-    func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction)
-    func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction)
-
-    // MARK: Track feature
+    
+    // By Flagset
+    func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: String]
+    func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: String]
+    func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: SplitResult]
+    func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: SplitResult]
+    func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String]
+    func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String]
+    func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult]
+    func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult]
+    
+    
+    // MARK: Tracking
     func track(trafficType: String, eventType: String) -> Bool
     func track(trafficType: String, eventType: String, value: Double) -> Bool
     func track(eventType: String) -> Bool
     func track(eventType: String, value: Double) -> Bool
+    @objc(trackWithTrafficType:eventType:properties:)
+    func track(trafficType: String, eventType: String, properties: [String: Any]?) -> Bool
+    @objc(trackWithTrafficType:eventType:value:properties:)
+    func track(trafficType: String, eventType: String, value: Double, properties: [String: Any]?) -> Bool
+    @objc(trackWithEventType:properties:)
+    func track(eventType: String, properties: [String: Any]?) -> Bool
+    @objc(trackWithEventType:value:properties:)
+    func track(eventType: String, value: Double, properties: [String: Any]?) -> Bool
 
-    // MARK: Persistent attributes feature
-
-    /// Creates or updates the value for the given attribute
+    
+    // MARK: Persistence
+    /// Creates or updates the value for the given attribute/
     func setAttribute(name: String, value: Any) -> Bool
-
-    /// Retrieves the value of a given attribute so it can be checked by the customer if needed
+    /// Retrieves the value of a given attribute so it can be checked by the customer if needed/
     func getAttribute(name: String) -> Any?
-
-    /// It will create or update all the given attributes
+    /// It will create or update all the given attributes/
     func setAttributes(_ values: [String: Any]) -> Bool
-
-    /// Retrieve the full attributes map
+    /// Retrieve the full attributes map/
     func getAttributes() -> [String: Any]?
-
-    /// Removes a given attribute from the map
+    /// Removes a given attribute from the map/
     func removeAttribute(name: String) -> Bool
-
-    /// Clears all attributes stored in the SDK.
+    /// Clears all attributes stored in the SDK./
     func clearAttributes() -> Bool
-
-    // MARK: Client lifecycle
+    
+    
+    // MARK: Lifecycle
     func flush()
     func destroy()
     func destroy(completion: (() -> Void)?)
-
-    @objc(trackWithTrafficType:eventType:properties:) func track(trafficType: String,
-                                                                 eventType: String,
-                                                                 properties: [String: Any]?) -> Bool
-
-    @objc(trackWithTrafficType:eventType:value:properties:) func track(trafficType: String,
-                                                                       eventType: String,
-                                                                       value: Double,
-                                                                       properties: [String: Any]?) -> Bool
-
-    @objc(trackWithEventType:properties:) func track(eventType: String,
-                                                     properties: [String: Any]?) -> Bool
-
-    @objc(trackWithEventType:value:properties:) func track(eventType: String,
-                                                           value: Double,
-                                                           properties: [String: Any]?) -> Bool
-
-    // MARK: Evaluation with flagsets
-    func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: String]
-    func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: String]
-    func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: SplitResult]
-    func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: SplitResult]
-    
-    // MARK: Evaluation with flagsets and properties
-    func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String]
-    func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String]
-    func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult]
-    func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult]
 }
diff --git a/Split/Common/Structs/BlockingQueue.swift b/Split/Common/Structs/BlockingQueue.swift
index 8fd45f67f..428e8532b 100644
--- a/Split/Common/Structs/BlockingQueue.swift
+++ b/Split/Common/Structs/BlockingQueue.swift
@@ -73,18 +73,23 @@ class GenericBlockingQueue<Item> {
 
 // Protocol to allow mocking
 protocol InternalEventBlockingQueue {
-    func add(_ item: SplitInternalEvent)
-    func take() throws -> SplitInternalEvent
+    func add(_ item: SplitInternalEventWithMetadata)
+    func take() throws -> SplitInternalEventWithMetadata
     func stop()
 }
 
 class DefaultInternalEventBlockingQueue: InternalEventBlockingQueue {
-    let blockingQueue = GenericBlockingQueue<SplitInternalEvent>()
+    let blockingQueue = GenericBlockingQueue<SplitInternalEventWithMetadata>()
+    
     func add(_ item: SplitInternalEvent) {
+        blockingQueue.add(SplitInternalEventWithMetadata(item))
+    }
+    
+    func add(_ item: SplitInternalEventWithMetadata) {
         blockingQueue.add(item)
     }
 
-    func take() throws -> SplitInternalEvent {
+    func take() throws -> SplitInternalEventWithMetadata {
         let value =  try blockingQueue.take()
         return value
     }
diff --git a/Split/Events/EventsManagerCoordinator.swift b/Split/Events/EventsManagerCoordinator.swift
index 3ddf4fe93..0978581f3 100644
--- a/Split/Events/EventsManagerCoordinator.swift
+++ b/Split/Events/EventsManagerCoordinator.swift
@@ -14,6 +14,7 @@ protocol SplitEventsManagerCoordinator: SplitEventsManager {
 }
 
 class MainSplitEventsManager: SplitEventsManagerCoordinator {
+    
     private var defaultManager: SplitEventsManager?
     private var managers = [Key: SplitEventsManager]()
     private var triggered = Set<SplitInternalEvent>()
@@ -23,20 +24,30 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator {
         .splitsUpdated,
         .splitKilledNotification]
     )
-
+    
+    // MARK: Notifications
     func notifyInternalEvent(_ event: SplitInternalEvent) {
+        notifyInternalEvent(event, metadata: nil)
+    }
+
+    func notifyInternalEvent(_ event: SplitInternalEvent, metadata: SplitMetadata? = nil) {
         if !eventsToHandle.contains(event) {
             return
         }
+        
         queue.async { [weak self] in
             guard let self = self else { return }
 
             self.triggered.insert(event)
             self.managers.forEach { _, manager in
-                manager.notifyInternalEvent(event)
+                manager.notifyInternalEvent(event, metadata: metadata)
             }
         }
     }
+    
+    // MARK: Registers
+    func register(event: SplitEvent, task: SplitEventActionTask) {}
+    func register(event: SplitEventWithMetadata, task: SplitEventActionTask) {}
 
     func start() {}
 
@@ -75,6 +86,4 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator {
             }
         }
     }
-
-    func register(event: SplitEvent, task: SplitEventTask) {}
 }
diff --git a/Split/Events/SplitEvent.swift b/Split/Events/SplitEvent.swift
index d2561e2d9..6c23ea792 100644
--- a/Split/Events/SplitEvent.swift
+++ b/Split/Events/SplitEvent.swift
@@ -1,28 +1,44 @@
-//
-//  SplitEvent.swift
-//  Split
-//
 //  Created by Sebastian Arrubia on 4/17/18.
-//
 
 import Foundation
 
+// All events (internal & external) support metadata.
+// Internal errors are propagated to the customer as events "(.sdkError)". The error info will travel as the event metadata.
+
+@objcMembers public class SplitEventWithMetadata: NSObject {
+    let type: SplitEvent
+    let metadata: SplitMetadata?
+    
+    @objc public init(type: SplitEvent, metadata: SplitMetadata? = nil) {
+        self.type = type
+        self.metadata = metadata
+    }
+    
+    public override func isEqual(_ object: Any?) -> Bool {
+        guard let other = object as? SplitEventWithMetadata else { return false }
+        return self.type == other.type
+    }
+}
+
 @objc public enum SplitEvent: Int {
     case sdkReady
     case sdkReadyTimedOut
     case sdkReadyFromCache
     case sdkUpdated
+    case sdkError
 
     public func toString() -> String {
         switch self {
-        case .sdkReady:
-            return "SDK_READY"
-        case .sdkUpdated:
-            return "SDK_UPDATE"
-        case .sdkReadyTimedOut:
-            return "SDK_READY_TIMED_OUT"
-        case .sdkReadyFromCache:
-            return "SDK_READY_FROM_CACHE"
+            case .sdkReady:
+                return "SDK_READY"
+            case .sdkUpdated:
+                return "SDK_UPDATE"
+            case .sdkReadyTimedOut:
+                return "SDK_READY_TIMED_OUT"
+            case .sdkReadyFromCache:
+                return "SDK_READY_FROM_CACHE"
+            case .sdkError:
+                return "SDK_ERROR"
         }
     }
 }
diff --git a/Split/Events/SplitEventActionTask.swift b/Split/Events/SplitEventActionTask.swift
index be1368d04..a70a1f4d1 100644
--- a/Split/Events/SplitEventActionTask.swift
+++ b/Split/Events/SplitEventActionTask.swift
@@ -7,20 +7,30 @@
 
 import Foundation
 
-class SplitEventActionTask: SplitEventTask {
+public typealias SplitAction = () -> Void
+public typealias SplitActionWithMetadata = (_ metadata: SplitMetadata?) -> Void
 
+class SplitEventActionTask: SplitEventTask {
+ 
+    // Private
     private var eventHandler: SplitAction?
+    private var eventHandlerWithMetadata: SplitActionWithMetadata?
     private var queue: DispatchQueue?
+    
+    // Public
     var event: SplitEvent
     var runInBackground: Bool = false
     var factory: SplitFactory
+    
+    init(action: @escaping SplitActionWithMetadata, event: SplitEvent, runInBackground: Bool = false, factory: SplitFactory, queue: DispatchQueue? = nil) {
+        self.eventHandlerWithMetadata = action
+        self.event = event
+        self.runInBackground = runInBackground
+        self.queue = queue
+        self.factory = factory
+    }
 
-    init(action: @escaping SplitAction,
-         event: SplitEvent,
-         runInBackground: Bool = false,
-         factory: SplitFactory,
-         queue: DispatchQueue? = nil) {
-
+    init(action: @escaping SplitAction, event: SplitEvent, runInBackground: Bool = false, factory: SplitFactory, queue: DispatchQueue? = nil) {
         self.eventHandler = action
         self.event = event
         self.runInBackground = runInBackground
@@ -33,7 +43,11 @@ class SplitEventActionTask: SplitEventTask {
         return queue
     }
 
-    func run() {
+    func run(_ metadata: SplitMetadata?) {
         eventHandler?()
+        
+        if let metadata = metadata {
+            eventHandlerWithMetadata?(metadata)
+        }
     }
 }
diff --git a/Split/Events/SplitEventTask.swift b/Split/Events/SplitEventTask.swift
index 1655e2b25..63421932f 100644
--- a/Split/Events/SplitEventTask.swift
+++ b/Split/Events/SplitEventTask.swift
@@ -7,9 +7,10 @@
 
 import Foundation
 
+// This protocol exists just for a dummy SplitEventTask por testing
 protocol SplitEventTask {
     var event: SplitEvent { get }
     var runInBackground: Bool { get }
     func takeQueue() -> DispatchQueue?
-    func run()
+    func run(_ metadata: SplitMetadata?)
 }
diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift
index 56774453b..994934ef0 100644
--- a/Split/Events/SplitEventsManager.swift
+++ b/Split/Events/SplitEventsManager.swift
@@ -9,21 +9,24 @@
 import Foundation
 
 protocol SplitEventsManager: AnyObject {
-    func register(event: SplitEvent, task: SplitEventTask)
+    func register(event: SplitEvent, task: SplitEventActionTask)
+    func register(event: SplitEventWithMetadata, task: SplitEventActionTask)
     func notifyInternalEvent(_ event: SplitInternalEvent)
+    func notifyInternalEvent(_ event: SplitInternalEvent, metadata: SplitMetadata?)
     func start()
     func stop()
     func eventAlreadyTriggered(event: SplitEvent) -> Bool
 }
 
 class DefaultSplitEventsManager: SplitEventsManager {
+
     private let readingRefreshTime: Int
 
     private var sdkReadyTimeStart: Int64
 
     private var subscriptions = [SplitEvent: [SplitEventTask]]()
     private var executionTimes: [String: Int]
-    private var triggered: [SplitInternalEvent]
+    private var triggered: [SplitInternalEventWithMetadata]
     private let processQueue: DispatchQueue
     private let dataAccessQueue: DispatchQueue
     private var isStarted: Bool
@@ -35,7 +38,7 @@ class DefaultSplitEventsManager: SplitEventsManager {
         self.isStarted = false
         self.sdkReadyTimeStart = Date().unixTimestampInMiliseconds()
         self.readingRefreshTime = 300
-        self.triggered = [SplitInternalEvent]()
+        self.triggered = [SplitInternalEventWithMetadata]()
         self.eventsQueue = DefaultInternalEventBlockingQueue()
         self.executionTimes = [String: Int]()
         registerMaxAllowedExecutionTimesPerEvent()
@@ -48,18 +51,10 @@ class DefaultSplitEventsManager: SplitEventsManager {
             }
         }
     }
-
-    func notifyInternalEvent(_ event: SplitInternalEvent) {
-        processQueue.async { [weak self] in
-            if let self = self {
-                Logger.v("Event \(event) notified")
-                self.eventsQueue.add(event)
-            }
-        }
-    }
-
-    func register(event: SplitEvent, task: SplitEventTask) {
-        let eventName = event.toString()
+    
+    // MARK: Registers
+    func register(event: SplitEventWithMetadata, task: SplitEventActionTask) {
+        let eventName = event.type.toString()
         processQueue.async { [weak self] in
             guard let self = self else { return }
             // If event is already triggered, execute the task
@@ -67,10 +62,33 @@ class DefaultSplitEventsManager: SplitEventsManager {
                 self.executeTask(event: event, task: task)
                 return
             }
-            self.subscribe(task: task, to: event)
+            self.subscribe(task: task, to: event.type)
         }
     }
+    
+    // Method kept for backwards compatibility. Allows registering an event without metadata.
+    func register(event: SplitEvent, task: SplitEventActionTask) {
+        register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task)
+    }
 
+    // MARK: Notifiers
+    
+    func notifyInternalEvent(_ event: SplitInternalEvent, metadata: SplitMetadata? = nil) {
+        let event = SplitInternalEventWithMetadata(event, metadata: metadata)
+        
+        processQueue.async { [weak self] in
+            if let self = self {
+                self.eventsQueue.add(event)
+            }
+        }
+    }
+    
+    // Method kept for backwards compatibility. Allows notifying an event without metadata.
+    func notifyInternalEvent(_ event: SplitInternalEvent) {
+        notifyInternalEvent(event, metadata: nil)
+    }
+
+    // MARK: Cycle
     func start() {
         dataAccessQueue.sync {
             if self.isStarted {
@@ -128,7 +146,7 @@ class DefaultSplitEventsManager: SplitEventsManager {
         return isRunning
     }
 
-    private func takeEvent() -> SplitInternalEvent? {
+    private func takeEvent() -> SplitInternalEventWithMetadata? {
         do {
             return try eventsQueue.take()
         } catch BlockingQueueError.hasBeenStopped {
@@ -145,10 +163,10 @@ class DefaultSplitEventsManager: SplitEventsManager {
                 return
             }
             self.triggered.append(event)
-            switch event {
+            switch event.type {
             case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated:
                 if isTriggered(external: .sdkReady) {
-                    trigger(event: .sdkUpdated)
+                    trigger(event: SplitEventWithMetadata(type: .sdkUpdated, metadata: event.metadata))
                     continue
                 }
                 self.triggerSdkReadyIfNeeded()
@@ -160,7 +178,7 @@ class DefaultSplitEventsManager: SplitEventsManager {
                    isTriggered(internal: .mySegmentsLoadedFromCache),
                    isTriggered(internal: .myLargeSegmentsLoadedFromCache),
                    isTriggered(internal: .attributesLoadedFromCache) {
-                    trigger(event: SplitEvent.sdkReadyFromCache)
+                    trigger(event: .sdkReadyFromCache)
                 }
             case .splitKilledNotification:
                 if isTriggered(external: .sdkReady) {
@@ -169,13 +187,13 @@ class DefaultSplitEventsManager: SplitEventsManager {
                 }
             case .sdkReadyTimeoutReached:
                 if !isTriggered(external: .sdkReady) {
-                    trigger(event: SplitEvent.sdkReadyTimedOut)
+                    trigger(event: .sdkReadyTimedOut)
                 }
             }
         }
     }
 
-    // MARK: Helper functions.
+    // MARK: Helper functions
     func isTriggered(external event: SplitEvent) -> Bool {
         var triggered = false
         dataAccessQueue.sync {
@@ -196,9 +214,13 @@ class DefaultSplitEventsManager: SplitEventsManager {
             self.trigger(event: SplitEvent.sdkReady)
         }
     }
-
+    
     private func trigger(event: SplitEvent) {
-        let eventName = event.toString()
+        trigger(event: SplitEventWithMetadata(type: event, metadata: nil))
+    }
+
+    private func trigger(event: SplitEventWithMetadata) {
+        let eventName = event.type.toString()
 
         // If executionTimes is zero, maximum executions has been reached
         if executionTimes(for: eventName) == 0 {
@@ -212,14 +234,14 @@ class DefaultSplitEventsManager: SplitEventsManager {
 
         Logger.d("Triggering SDK event \(eventName)")
         // If executionTimes is lower than zero, execute it without limitation
-        if let subscriptions = getSubscriptions(for: event) {
+        if let subscriptions = getSubscriptions(for: event.type) {
             for task in subscriptions {
                 executeTask(event: event, task: task)
             }
         }
     }
 
-    private func executeTask(event: SplitEvent, task: SplitEventTask) {
+    private func executeTask(event: SplitEventWithMetadata, task: SplitEventTask) {
 
         let eventName = task.event.toString()
 
@@ -229,7 +251,7 @@ class DefaultSplitEventsManager: SplitEventsManager {
             let queue = task.takeQueue() ?? DispatchQueue.general
             queue.async {
                 TimeChecker.logInterval("Running \(eventName) in Background queue \(queue)")
-                task.run()
+                task.run(event.metadata)
             }
             return
         }
@@ -237,13 +259,17 @@ class DefaultSplitEventsManager: SplitEventsManager {
         DispatchQueue.main.async {
             TimeChecker.logInterval("Running event on main: \(eventName)")
             // UI Updates
-            task.run()
+            task.run(event.metadata)
         }
     }
 
-    private func isTriggered(internal event: SplitInternalEvent) -> Bool {
+    private func isTriggered(internal event: SplitInternalEventWithMetadata) -> Bool {
         return triggered.filter { $0 == event }.count > 0
     }
+    
+    private func isTriggered(internal event: SplitInternalEvent) -> Bool {
+        return isTriggered(internal: SplitInternalEventWithMetadata(event, metadata: nil))
+    }
 
     // MARK: Safe Data Access
     func executionTimes(for eventName: String) -> Int? {
diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift
index 4c9521204..e4ad78b74 100644
--- a/Split/Events/SplitInternalEvent.swift
+++ b/Split/Events/SplitInternalEvent.swift
@@ -7,6 +7,20 @@
 
 import Foundation
 
+struct SplitInternalEventWithMetadata {
+    let type: SplitInternalEvent
+    let metadata: SplitMetadata?
+    
+    init(_ type: SplitInternalEvent, metadata: SplitMetadata? = nil) {
+        self.type = type
+        self.metadata = metadata
+    }
+    
+    static func == (lhs: SplitInternalEventWithMetadata, rhs: SplitInternalEventWithMetadata) -> Bool {
+        return lhs.type == rhs.type
+    }
+}
+
 enum SplitInternalEvent {
     case mySegmentsUpdated
     case myLargeSegmentsUpdated
diff --git a/Split/Events/SplitMetadata.swift b/Split/Events/SplitMetadata.swift
new file mode 100644
index 000000000..5d276e72c
--- /dev/null
+++ b/Split/Events/SplitMetadata.swift
@@ -0,0 +1,29 @@
+//  Created by Martin Cardozo on 17/05/2025
+//  Copyright © 2025 Split. All rights reserved.
+
+import Foundation
+
+@objc public class SplitMetadata: NSObject {
+    var type: SplitMetadataType
+    var data: String = ""
+    
+    init(type: SplitMetadataType, data: String) {
+        self.type = type
+        self.data = data
+    }
+}
+
+enum SplitMetadataType: Int {
+    case FEATURE_FLAGS_SYNC_ERROR
+    case SEGMENTS_SYNC_ERROR
+    
+    public func toString() -> String {
+        switch self {
+            case .FEATURE_FLAGS_SYNC_ERROR:
+                return "FEATURE_FLAGS_SYNC_ERROR"
+            case .SEGMENTS_SYNC_ERROR:
+                return "SEGMENTS_SYNC_ERROR"
+            
+        }
+    }
+}
diff --git a/Split/FetcherEngine/Recorder/TelemetryStatsRecorderWorker.swift b/Split/FetcherEngine/Recorder/TelemetryStatsRecorderWorker.swift
index 0bee73e79..c0c0ebcd3 100644
--- a/Split/FetcherEngine/Recorder/TelemetryStatsRecorderWorker.swift
+++ b/Split/FetcherEngine/Recorder/TelemetryStatsRecorderWorker.swift
@@ -16,11 +16,7 @@ class TelemetryStatsRecorderWorker: RecorderWorker {
     private let mySegmentsStorage: MySegmentsStorage
     private let myLargeSegmentsStorage: MySegmentsStorage
 
-    init(telemetryStatsRecorder: HttpTelemetryStatsRecorder,
-         telemetryConsumer: TelemetryConsumer,
-         splitsStorage: SplitsStorage,
-         mySegmentsStorage: MySegmentsStorage,
-         myLargeSegmentsStorage: MySegmentsStorage) {
+    init(telemetryStatsRecorder: HttpTelemetryStatsRecorder, telemetryConsumer: TelemetryConsumer, splitsStorage: SplitsStorage, mySegmentsStorage: MySegmentsStorage, myLargeSegmentsStorage: MySegmentsStorage) {
 
         self.telemetryConsumer = telemetryConsumer
         self.statsRecorder = telemetryStatsRecorder
@@ -30,7 +26,7 @@ class TelemetryStatsRecorderWorker: RecorderWorker {
     }
 
     func flush() {
-        if !statsRecorder.isEndpointAvailable() {
+        guard statsRecorder.isEndpointAvailable() else {
             Logger.d("Endpoint not reachable. Telemetry stats post will be delayed")
             return
         }
diff --git a/SplitTests/Collections/BlockingQueueTest.swift b/SplitTests/Collections/BlockingQueueTest.swift
index 04c4b5df3..24cfa4710 100644
--- a/SplitTests/Collections/BlockingQueueTest.swift
+++ b/SplitTests/Collections/BlockingQueueTest.swift
@@ -25,7 +25,7 @@ class BlockingQueueTest: XCTestCase {
             while true {
                 do {
                     let event = try queue.take()
-                    local.append(event)
+                    local.append(event.type)
                     if local.count == 4 {
                         endExp.fulfill()
                     }
@@ -34,19 +34,19 @@ class BlockingQueueTest: XCTestCase {
             }
         }
         globalQ.asyncAfter(deadline: .now() + 1) {
-            queue.add(SplitInternalEvent.mySegmentsLoadedFromCache)
+            queue.add(.mySegmentsLoadedFromCache)
             globalQ.asyncAfter(deadline: .now() + 1) {
-                queue.add(SplitInternalEvent.splitsLoadedFromCache)
+                queue.add(.splitsLoadedFromCache)
             }
         }
 
-        queue.add(SplitInternalEvent.splitsUpdated)
-        queue.add(SplitInternalEvent.mySegmentsUpdated)
+        queue.add(.splitsUpdated)
+        queue.add(.mySegmentsUpdated)
         wait(for: [endExp], timeout: 10)
-        XCTAssertEqual(SplitInternalEvent.splitsUpdated, local[0])
-        XCTAssertEqual(SplitInternalEvent.mySegmentsUpdated, local[1])
-        XCTAssertEqual(SplitInternalEvent.mySegmentsLoadedFromCache, local[2])
-        XCTAssertEqual(SplitInternalEvent.splitsLoadedFromCache, local[3])
+        XCTAssertEqual(.splitsUpdated, local[0])
+        XCTAssertEqual(.mySegmentsUpdated, local[1])
+        XCTAssertEqual(.mySegmentsLoadedFromCache, local[2])
+        XCTAssertEqual(.splitsLoadedFromCache, local[3])
     }
 
     func testInterrupt() {
@@ -60,7 +60,7 @@ class BlockingQueueTest: XCTestCase {
             while true {
                 do {
                     let event = try queue.take()
-                    local.append(event)
+                    local.append(event.type)
                 } catch {
                     endExp.fulfill()
                     interrupted = true
@@ -68,19 +68,19 @@ class BlockingQueueTest: XCTestCase {
             }
         }
         globalQ.asyncAfter(deadline: .now() + 1) {
-            queue.add(SplitInternalEvent.mySegmentsLoadedFromCache)
+            queue.add(.mySegmentsLoadedFromCache)
             globalQ.asyncAfter(deadline: .now() + 1) {
                 queue.stop()
             }
         }
 
-        queue.add(SplitInternalEvent.splitsUpdated)
-        queue.add(SplitInternalEvent.mySegmentsUpdated)
+        queue.add(.splitsUpdated)
+        queue.add(.mySegmentsUpdated)
 
         wait(for: [endExp], timeout: 10)
-        XCTAssertEqual(SplitInternalEvent.splitsUpdated, local[0])
-        XCTAssertEqual(SplitInternalEvent.mySegmentsUpdated, local[1])
-        XCTAssertEqual(SplitInternalEvent.mySegmentsLoadedFromCache, local[2])
+        XCTAssertEqual(.splitsUpdated, local[0])
+        XCTAssertEqual(.mySegmentsUpdated, local[1])
+        XCTAssertEqual(.mySegmentsLoadedFromCache, local[2])
         XCTAssertTrue(interrupted)
     }
 
@@ -105,8 +105,8 @@ class BlockingQueueTest: XCTestCase {
             for _ in 0..<50000 {
                 do {
                     let event = try queue.take()
-                    local.append(event)
-                    print("Took: \(event)")
+                    local.append(event.type)
+                    print("Took: \(event.type)")
                 } catch {
                 }
             }
@@ -117,8 +117,8 @@ class BlockingQueueTest: XCTestCase {
             for _ in 0..<50000 {
                 do {
                     let event = try queue.take()
-                    local.append(event)
-                    print("Took QA1: \(event)")
+                    local.append(event.type)
+                    print("Took QA1: \(event.type)")
                 } catch {
                     print("\n\n\nERROR!!!!: \(error) \n\n\n")
                 }
@@ -129,9 +129,9 @@ class BlockingQueueTest: XCTestCase {
             for _ in 0..<50000 {
                 do {
                     let event = try queue.take()
-                    local.append(event)
+                    local.append(event.type)
                     Thread.sleep(forTimeInterval: 0.3)
-                    print("Took QA2: \(event)")
+                    print("Took QA2: \(event.type)")
                 } catch {
                 }
             }
@@ -142,8 +142,8 @@ class BlockingQueueTest: XCTestCase {
                 do {
                     Thread.sleep(forTimeInterval: 0.5)
                     let event = try queue.take()
-                    local.append(event)
-                    print("Took QA3: \(event)")
+                    local.append(event.type)
+                    print("Took QA3: \(event.type)")
                 } catch {
                 }
             }
@@ -151,7 +151,7 @@ class BlockingQueueTest: XCTestCase {
 
         qu1.async {
             for _ in 1..<100000 {
-                queue.add(SplitInternalEvent.splitsUpdated)
+                queue.add(.splitsUpdated)
                 print("qu1 add")
                 Thread.sleep(forTimeInterval: 0.2)
             }
@@ -160,7 +160,7 @@ class BlockingQueueTest: XCTestCase {
         qu2.async {
             for _ in 1..<10000 {
                 print("qu2 add")
-                queue.add(SplitInternalEvent.sdkReadyTimeoutReached)
+                queue.add(.sdkReadyTimeoutReached)
                 Thread.sleep(forTimeInterval: 0.5)
             }
         }
@@ -168,7 +168,7 @@ class BlockingQueueTest: XCTestCase {
         qu3.async {
             for _ in 1..<10000 {
                 print("qu3 add")
-                queue.add(SplitInternalEvent.splitsUpdated)
+                queue.add(.splitsUpdated)
                 Thread.sleep(forTimeInterval: 0.8)
             }
         }
@@ -176,7 +176,7 @@ class BlockingQueueTest: XCTestCase {
         qu4.async {
             for _ in 1..<10000 {
                 print("qu4 add")
-                queue.add(SplitInternalEvent.mySegmentsUpdated)
+                queue.add(.mySegmentsUpdated)
                 sleep(1)
             }
         }
diff --git a/SplitTests/Fake/InternalSplitClientStub.swift b/SplitTests/Fake/InternalSplitClientStub.swift
index 5b792fb3e..17e6bf2d5 100644
--- a/SplitTests/Fake/InternalSplitClientStub.swift
+++ b/SplitTests/Fake/InternalSplitClientStub.swift
@@ -23,6 +23,7 @@ class InternalSplitClientStub: InternalSplitClient {
         self.myLargeSegmentsStorage = myLargeSegmentsStorage
     }
 
+    // MARK: Treatments
     func getTreatment(_ split: String, attributes: [String : Any]?) -> String {
         return SplitConstants.control
     }
@@ -43,6 +44,7 @@ class InternalSplitClientStub: InternalSplitClient {
         return createControlTreatmentsDictionary(splits: splits)
     }
 
+    // With config
     func getTreatmentWithConfig(_ split: String) -> SplitResult {
         return SplitResult(treatment: SplitConstants.control)
     }
@@ -63,6 +65,7 @@ class InternalSplitClientStub: InternalSplitClient {
         return createControlTreatmentsDictionary(splits: splits)
     }
 
+    // By FlagSets
     func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String : Any]?) -> [String : String] {
         return ["": SplitConstants.control]
     }
@@ -95,15 +98,14 @@ class InternalSplitClientStub: InternalSplitClient {
         return ["": SplitResult(treatment: SplitConstants.control)]
     }
 
-    func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) {
-    }
-
-    func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) {
-    }
-
-    func on(event: SplitEvent, execute action: @escaping SplitAction) {
-    }
+    // MARK: Events Listeners
+    func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) {}
+    func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) {}
+    func on(event: SplitEvent, execute action: @escaping SplitAction) {}
+    func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) {}
+    func on(event: SplitEvent, executeTask: SplitEventTask) {}
 
+    // MARK: Tracking
     func track(trafficType: String, eventType: String) -> Bool {
         return true
     }
@@ -160,18 +162,10 @@ class InternalSplitClientStub: InternalSplitClient {
         return true
     }
     
-    func flush() {
-    }
-
-    func destroy() {
-    }
-
-    func destroy(completion: (() -> Void)?) {
-    }
-
-    func on(event: SplitEvent, executeTask: SplitEventTask) {
-
-    }
+    // MARK: Lifecycle
+    func flush() {}
+    func destroy() {}
+    func destroy(completion: (() -> Void)?) {}
 
     private func createControlTreatmentsDictionary<T>(splits: [String]) -> [String: T] where T: Any {
         var result = [String: T]()
diff --git a/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift b/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift
index 94cbb8991..7fd72f601 100644
--- a/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift
+++ b/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift
@@ -22,9 +22,11 @@ class SplitEventsManagerCoordinatorStub: SplitEventsManagerCoordinator {
         managers[key] = nil
     }
 
-    func register(event: SplitEvent, task: SplitEventTask) {
-
-    }
+    func register(event: SplitEvent, task: SplitEventActionTask) {}
+    
+    func register(event: SplitEventWithMetadata, task: SplitEventActionTask) {}
+    
+    func notifyInternalEvent(_ event: SplitInternalEvent, metadata: SplitMetadata?) {}
 
     var notifiedEvents = Set<String>()
     func notifyInternalEvent(_ event: SplitInternalEvent) {
diff --git a/SplitTests/Fake/SplitClientStub.swift b/SplitTests/Fake/SplitClientStub.swift
index a19acdcb7..862009b90 100644
--- a/SplitTests/Fake/SplitClientStub.swift
+++ b/SplitTests/Fake/SplitClientStub.swift
@@ -10,7 +10,8 @@ import Foundation
 @testable import Split
 
 class SplitClientStub: SplitClient {
-
+    
+    // MARK: Treatments
     func getTreatment(_ split: String, attributes: [String : Any]?) -> String {
         return SplitConstants.control
     }
@@ -31,6 +32,7 @@ class SplitClientStub: SplitClient {
         return ["feature": SplitConstants.control]
     }
 
+    // With Config
     func getTreatmentWithConfig(_ split: String) -> SplitResult {
         return SplitResult(treatment: SplitConstants.control)
     }
@@ -51,6 +53,7 @@ class SplitClientStub: SplitClient {
         return ["feature": SplitResult(treatment: SplitConstants.control)]
     }
 
+    // By FlagSets
     func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String : Any]?) -> [String : String] {
         return ["feature": SplitConstants.control]
     }
@@ -83,18 +86,14 @@ class SplitClientStub: SplitClient {
         return ["feature": SplitResult(treatment: SplitConstants.control)]
     }
 
-    func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) {
-    }
-
-    func on(event: SplitEvent, execute action: @escaping SplitAction) {
-    }
-
-    func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) {
-    }
-
-    func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitAction) {
-    }
+    // MARK: Events Listeners
+    func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) {}
+    func on(event: SplitEvent, execute action: @escaping SplitAction) {}
+    func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) {}
+    func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitAction) {}
+    func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) {}
 
+    // MARK: Tracking
     func track(trafficType: String, eventType: String) -> Bool {
         return true
     }
@@ -127,6 +126,7 @@ class SplitClientStub: SplitClient {
         return true
     }
 
+    // MARK: Attributes
     func setAttribute(name: String, value: Any) -> Bool {
         return true
     }
@@ -151,12 +151,8 @@ class SplitClientStub: SplitClient {
         return true
     }
 
-    func flush() {
-    }
-
-    func destroy() {
-    }
-
-    func destroy(completion: (() -> Void)?) {
-    }
+    // MARK: Lifecycle
+    func flush() {}
+    func destroy() {}
+    func destroy(completion: (() -> Void)?) {}
 }
diff --git a/SplitTests/Fake/SplitEventsManagerMock.swift b/SplitTests/Fake/SplitEventsManagerMock.swift
index 1132148df..d1bdd234e 100644
--- a/SplitTests/Fake/SplitEventsManagerMock.swift
+++ b/SplitTests/Fake/SplitEventsManagerMock.swift
@@ -21,48 +21,59 @@ class SplitEventsManagerMock: SplitEventsManager {
     var isSdkReadyFromCacheFired: Bool {
         return isSegmentsReadyFromCacheFired && isSplitsReadyFromCacheFired
     }
+    
     var isSegmentsReadyFired = false
     var isSplitsReadyFired = false
     var isSegmentsReadyFromCacheFired = false
     var isSplitsReadyFromCacheFired = false
     var isSdkTimeoutFired = false
-
+    var isSdkErrorFired = false
     var isSplitUpdatedTriggered = false
     var isSdkUpdatedFired = false
-
     var isSdkReadyChecked = false
+    
+    var metadata: SplitMetadata?
 
-    func notifyInternalEvent(_ event:SplitInternalEvent) {
+    // MARK: Notifiers
+    func notifyInternalEvent(_ event: SplitInternalEvent) {
         switch event {
-        case .mySegmentsUpdated:
-            isSegmentsReadyFired = true
-        case .splitsUpdated:
-            isSplitsReadyFired = true
-            isSplitUpdatedTriggered = true
-            if let exp = readyExp {
-                exp.fulfill()
+            case .mySegmentsUpdated:
+                isSegmentsReadyFired = true
+            case .splitsUpdated:
+                isSplitsReadyFired = true
+                isSplitUpdatedTriggered = true
+                if let exp = readyExp {
+                    exp.fulfill()
+                }
+            case .sdkReadyTimeoutReached:
+                isSdkTimeoutFired = true
+                if let exp = timeoutExp {
+                    exp.fulfill()
+                }
+            default:
+                print("\(event)")
             }
-        case .sdkReadyTimeoutReached:
-            isSdkTimeoutFired = true
-            if let exp = timeoutExp {
-                exp.fulfill()
-            }
-        default:
-            print("\(event)")
-        }
     }
-
-    var registeredEvents = [SplitEvent: SplitEventTask]()
-    func register(event: SplitEvent, task: SplitEventTask) {
-        registeredEvents[event] = task
+    
+    func notifyInternalEvent(_ event: SplitInternalEvent, metadata: SplitMetadata?) {
+        self.metadata = metadata
+        notifyInternalEvent(event)
     }
     
-    func start() {
+    // MARK: Registers
+    var registeredEvents = [SplitEvent: SplitEventActionTask]()
+    func register(event: SplitEvent, task: SplitEventActionTask) {
+        register(event: SplitEventWithMetadata(type: event), task: task)
     }
-
-    func stop() {
+    
+    func register(event: SplitEventWithMetadata, task: SplitEventActionTask) {
+        registeredEvents[event.type] = task
     }
     
+    func start() {}
+
+    func stop() {}
+    
     func eventAlreadyTriggered(event: SplitEvent) -> Bool {
         switch event {
         case.sdkReady:
@@ -74,6 +85,8 @@ class SplitEventsManagerMock: SplitEventsManager {
             return isSdkTimeoutFired
         case .sdkUpdated:
             return isSdkUpdatedFired
+        case .sdkError:
+            return isSdkErrorFired
 
         default:
             return true
diff --git a/SplitTests/Fake/SplitEventsManagerStub.swift b/SplitTests/Fake/SplitEventsManagerStub.swift
index 1d7685dc3..15fcdf023 100644
--- a/SplitTests/Fake/SplitEventsManagerStub.swift
+++ b/SplitTests/Fake/SplitEventsManagerStub.swift
@@ -15,32 +15,49 @@ class SplitEventsManagerStub: SplitEventsManager {
     var splitsKilledEventFiredCount = 0
     var splitsUpdatedEventFiredCount = 0
     var mySegmentsLoadedEventFiredCount = 0
+    
+    var metadata: SplitMetadata?
+    
     var mySegmentsLoadedEventExp: XCTestExpectation?
     var startCalled = false
     var stopCalled = false
 
     func notifyInternalEvent(_ event: SplitInternalEvent) {
+        notifyInternalEvent(event, metadata: nil)
+    }
+    
+    func notifyInternalEvent(_ event: SplitInternalEvent, metadata: SplitMetadata? = nil) {
+        
+        self.metadata = metadata
+        
         switch event {
-        case .mySegmentsLoadedFromCache:
-            mySegmentsLoadedEventFiredCount+=1
-            if let exp = mySegmentsLoadedEventExp {
-                exp.fulfill()
-            }
-        case .splitsLoadedFromCache:
-            splitsLoadedEventFiredCount+=1
-
-        case .splitKilledNotification:
-            splitsKilledEventFiredCount+=1
-
-        case .splitsUpdated:
-            splitsUpdatedEventFiredCount+=1
-        default:
-            print("internal event fired: \(event)")
+            case .mySegmentsLoadedFromCache:
+                mySegmentsLoadedEventFiredCount += 1
+                if let exp = mySegmentsLoadedEventExp {
+                    exp.fulfill()
+                }
+            case .splitsLoadedFromCache:
+                splitsLoadedEventFiredCount += 1
+
+            case .splitKilledNotification:
+                splitsKilledEventFiredCount += 1
+
+            case .splitsUpdated:
+                splitsUpdatedEventFiredCount += 1
+            
+            case .sdkReadyTimeoutReached:
+                splitsUpdatedEventFiredCount += 1
+            default:
+                print("internal event fired: \(event)")
         }
     }
 
-    var registeredEvents = [SplitEvent: SplitEventTask]()
-    func register(event: SplitEvent, task: SplitEventTask) {
+    var registeredEvents = [SplitEventWithMetadata: SplitEventActionTask]()
+    func register(event: SplitEvent, task: SplitEventActionTask) {
+        register(event: SplitEventWithMetadata(type: event), task: task)
+    }
+    
+    func register(event: SplitEventWithMetadata, task: SplitEventActionTask) {
         registeredEvents[event] = task
     }
 
diff --git a/SplitTests/Integration/Recorder/TelemetryTest.swift b/SplitTests/Integration/Recorder/TelemetryTest.swift
index a766a21f6..28ba9d8c8 100644
--- a/SplitTests/Integration/Recorder/TelemetryTest.swift
+++ b/SplitTests/Integration/Recorder/TelemetryTest.swift
@@ -167,6 +167,7 @@ class TelemetryTest: XCTestCase {
         XCTAssertEqual(0, sum(latenciesAfter.treatmentsWithConfig))
         XCTAssertEqual(0, sum(latenciesAfter.track))
 
+        
         let semaphore = DispatchSemaphore(value: 0)
         client.destroy(completion: {
             _ = semaphore.signal()
diff --git a/SplitTests/SplitEventsManagerTest.swift b/SplitTests/SplitEventsManagerTest.swift
index 9f66d2936..6daa666b0 100644
--- a/SplitTests/SplitEventsManagerTest.swift
+++ b/SplitTests/SplitEventsManagerTest.swift
@@ -25,17 +25,17 @@ class SplitEventsManagerTest: XCTestCase {
         eventManager.register(event: .sdkUpdated, task: updatedTask)
         eventManager.start()
         
-        eventManager.notifyInternalEvent(SplitInternalEvent.mySegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.myLargeSegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitsUpdated)
+        eventManager.notifyInternalEvent(.mySegmentsUpdated)
+        eventManager.notifyInternalEvent(.myLargeSegmentsUpdated)
+        eventManager.notifyInternalEvent(.splitsUpdated)
+        eventManager.notifyInternalEvent(.splitsUpdated)
         ThreadUtils.delay(seconds: 0.2)
         
         let expectation = XCTestExpectation(description: "SDK Readky triggered")
         queue.async {
             while !shouldStop {
                 sleep(UInt32(self.intervalExecutionTime))
-                if eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReady) {
+                if eventManager.eventAlreadyTriggered(event: .sdkReady) {
                     shouldStop = true;
                     expectation.fulfill()
                 }
@@ -43,9 +43,9 @@ class SplitEventsManagerTest: XCTestCase {
         }
         wait(for: [expectation], timeout: expectationTimeOut)
         
-        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReady), "SDK Ready should be triggered");
-        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkUpdated), "SDK Update shouldn't be triggered");
-        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyTimedOut), "SDK Time out shouldn't be triggered");
+        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: .sdkReady), "SDK Ready should be triggered");
+        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: .sdkUpdated), "SDK Update shouldn't be triggered");
+        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: .sdkReadyTimedOut), "SDK Time out shouldn't be triggered");
 
         eventManager.stop()
     }
@@ -56,20 +56,20 @@ class SplitEventsManagerTest: XCTestCase {
         let eventManager: SplitEventsManager = DefaultSplitEventsManager(config: config)
         eventManager.start()
 
-        eventManager.notifyInternalEvent(SplitInternalEvent.mySegmentsLoadedFromCache)
-        eventManager.notifyInternalEvent(SplitInternalEvent.myLargeSegmentsLoadedFromCache)
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitsLoadedFromCache)
-        eventManager.notifyInternalEvent(SplitInternalEvent.attributesLoadedFromCache)
+        eventManager.notifyInternalEvent(.mySegmentsLoadedFromCache)
+        eventManager.notifyInternalEvent(.myLargeSegmentsLoadedFromCache)
+        eventManager.notifyInternalEvent(.splitsLoadedFromCache)
+        eventManager.notifyInternalEvent(.attributesLoadedFromCache)
 
-        eventManager.notifyInternalEvent(SplitInternalEvent.mySegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.myLargeSegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitsUpdated)
+        eventManager.notifyInternalEvent(.mySegmentsUpdated)
+        eventManager.notifyInternalEvent(.myLargeSegmentsUpdated)
+        eventManager.notifyInternalEvent(.splitsUpdated)
         var shouldStop = false
         let expectation = XCTestExpectation(description: "SDK Readky from cache triggered")
         queue.async {
             while !shouldStop {
                 sleep(UInt32(self.intervalExecutionTime))
-                if eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReady) {
+                if eventManager.eventAlreadyTriggered(event: .sdkReady) {
                     shouldStop = true;
                     expectation.fulfill()
                 }
@@ -77,9 +77,9 @@ class SplitEventsManagerTest: XCTestCase {
         }
         wait(for: [expectation], timeout: expectationTimeOut)
 
-        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyFromCache), "SDK Ready should from cache be triggered");
-        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReady), "SDK Ready should be triggered");
-        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyTimedOut), "SDK Time out shouldn't be triggered");
+        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: .sdkReadyFromCache), "SDK Ready should from cache be triggered");
+        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: .sdkReady), "SDK Ready should be triggered");
+        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: .sdkReadyTimedOut), "SDK Time out shouldn't be triggered");
 
         eventManager.stop()
     }
@@ -96,17 +96,17 @@ class SplitEventsManagerTest: XCTestCase {
         let timeoutExp = XCTestExpectation()
         eventManager.register(event: .sdkReadyTimedOut, task: TestTask(exp: timeoutExp))
 
-        eventManager.notifyInternalEvent(SplitInternalEvent.mySegmentsLoadedFromCache)
-        eventManager.notifyInternalEvent(SplitInternalEvent.myLargeSegmentsLoadedFromCache)
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitsLoadedFromCache)
-        eventManager.notifyInternalEvent(SplitInternalEvent.attributesLoadedFromCache)
-        eventManager.notifyInternalEvent(SplitInternalEvent.sdkReadyTimeoutReached)
+        eventManager.notifyInternalEvent(.mySegmentsLoadedFromCache)
+        eventManager.notifyInternalEvent(.myLargeSegmentsLoadedFromCache)
+        eventManager.notifyInternalEvent(.splitsLoadedFromCache)
+        eventManager.notifyInternalEvent(.attributesLoadedFromCache)
+        eventManager.notifyInternalEvent(.sdkReadyTimeoutReached)
 
         wait(for: [cacheExp, timeoutExp], timeout: expectationTimeOut)
 
-        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyFromCache), "SDK Ready should from cache be triggered");
-        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReady), "SDK Ready should not be triggered");
-        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyTimedOut), "SDK Time out should be triggered");
+        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: .sdkReadyFromCache), "SDK Ready should from cache be triggered");
+        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: .sdkReady), "SDK Ready should not be triggered");
+        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: .sdkReadyTimedOut), "SDK Time out should be triggered");
 
         eventManager.stop()
     }
@@ -122,8 +122,8 @@ class SplitEventsManagerTest: XCTestCase {
         eventManager.register(event: .sdkReadyTimedOut, task: TestTask(exp: timeoutExp))
         wait(for: [timeoutExp], timeout: expectationTimeOut)
         
-        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyTimedOut), "SDK Time out should be triggered")
-        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReady), "SDK Ready shouldn't be triggered")
+        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: .sdkReadyTimedOut), "SDK Time out should be triggered")
+        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: .sdkReady), "SDK Ready shouldn't be triggered")
 
         eventManager.stop()
     }
@@ -141,18 +141,18 @@ class SplitEventsManagerTest: XCTestCase {
         wait(for: [expectationTimeout], timeout: expectationTimeOut)
         
         //At this line timeout has been reached
-        let timeoutTriggered  = eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyTimedOut)
+        let timeoutTriggered  = eventManager.eventAlreadyTriggered(event: .sdkReadyTimedOut)
 
         let readyExp = XCTestExpectation(description: "SDK Readky triggered")
         eventManager.register(event: .sdkReady, task: TestTask(exp: readyExp))
 
-        eventManager.notifyInternalEvent(SplitInternalEvent.mySegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.myLargeSegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitsUpdated)
+        eventManager.notifyInternalEvent(.mySegmentsUpdated)
+        eventManager.notifyInternalEvent(.myLargeSegmentsUpdated)
+        eventManager.notifyInternalEvent(.splitsUpdated)
 
         wait(for: [readyExp], timeout: expectationTimeOut)
         
-        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReady), "SDK Ready should be triggered");
+        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: .sdkReady), "SDK Ready should be triggered");
         XCTAssertTrue(timeoutTriggered)
 
         eventManager.stop()
@@ -170,17 +170,17 @@ class SplitEventsManagerTest: XCTestCase {
         eventManager.register(event: .sdkReady, task: TestTask(exp: readyExp))
         eventManager.register(event: .sdkUpdated, task: updatedTask)
 
-        eventManager.notifyInternalEvent(SplitInternalEvent.mySegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.myLargeSegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitsUpdated)
+        eventManager.notifyInternalEvent(.mySegmentsUpdated)
+        eventManager.notifyInternalEvent(.myLargeSegmentsUpdated)
+        eventManager.notifyInternalEvent(.splitsUpdated)
         ThreadUtils.delay(seconds: 0.5)
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitsUpdated)
+        eventManager.notifyInternalEvent(.splitsUpdated)
 
         wait(for: [readyExp, sdkUpdatedExp], timeout: expectationTimeOut)
 
-        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReady), "SDK Ready should be triggered");
+        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: .sdkReady), "SDK Ready should be triggered");
         XCTAssertTrue(updatedTask.taskTriggered, "SDK Update should be triggered");
-        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyTimedOut), "SDK Time out shouldn't be triggered");
+        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: .sdkReadyTimedOut), "SDK Time out shouldn't be triggered");
 
         eventManager.stop()
     }
@@ -197,15 +197,15 @@ class SplitEventsManagerTest: XCTestCase {
         eventManager.register(event: .sdkUpdated, task: updatedTask)
 
 
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.mySegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.myLargeSegmentsUpdated)
+        eventManager.notifyInternalEvent(.splitsUpdated)
+        eventManager.notifyInternalEvent(.mySegmentsUpdated)
+        eventManager.notifyInternalEvent(.myLargeSegmentsUpdated)
         ThreadUtils.delay(seconds: 0.5)
-        eventManager.notifyInternalEvent(SplitInternalEvent.mySegmentsUpdated)
+        eventManager.notifyInternalEvent(.mySegmentsUpdated)
 
         wait(for: [readyExp, sdkUpdatedExp], timeout: expectationTimeOut)
 
-        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReady), "SDK Ready should be triggered");
+        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: .sdkReady), "SDK Ready should be triggered");
         XCTAssertTrue(updatedTask.taskTriggered, "SDK Update should be triggered");
         XCTAssertFalse(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyTimedOut), "SDK Time out shouldn't be triggered");
 
@@ -223,17 +223,17 @@ class SplitEventsManagerTest: XCTestCase {
         eventManager.register(event: .sdkReady, task: TestTask(exp: readyExp))
         eventManager.register(event: .sdkUpdated, task: updatedTask)
 
-        eventManager.notifyInternalEvent(SplitInternalEvent.mySegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.myLargeSegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitsUpdated)
+        eventManager.notifyInternalEvent(.mySegmentsUpdated)
+        eventManager.notifyInternalEvent(.myLargeSegmentsUpdated)
+        eventManager.notifyInternalEvent(.splitsUpdated)
         ThreadUtils.delay(seconds: 0.5)
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitKilledNotification)
+        eventManager.notifyInternalEvent(.splitKilledNotification)
 
         wait(for: [readyExp, sdkUpdatedExp], timeout: expectationTimeOut)
 
-        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReady), "SDK Ready should be triggered");
+        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: .sdkReady), "SDK Ready should be triggered");
         XCTAssertTrue(updatedTask.taskTriggered, "SDK Update should be triggered");
-        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyTimedOut), "SDK Time out shouldn't be triggered");
+        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: .sdkReadyTimedOut), "SDK Time out shouldn't be triggered");
 
         eventManager.stop()
     }
@@ -250,19 +250,39 @@ class SplitEventsManagerTest: XCTestCase {
         eventManager.register(event: .sdkReadyTimedOut, task: timeOutTask)
         eventManager.start()
 
-        eventManager.notifyInternalEvent(SplitInternalEvent.mySegmentsUpdated)
-        eventManager.notifyInternalEvent(SplitInternalEvent.splitKilledNotification)
+        eventManager.notifyInternalEvent(.mySegmentsUpdated)
+        eventManager.notifyInternalEvent(.splitKilledNotification)
 
         wait(for: [sdkTiemoutExp], timeout: timeout)
 
         XCTAssertFalse(updatedTask.taskTriggered);
-        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReady));
+        XCTAssertFalse(eventManager.eventAlreadyTriggered(event: .sdkReady));
         XCTAssertTrue(timeOutTask.taskTriggered);
-        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyTimedOut));
+        XCTAssertTrue(eventManager.eventAlreadyTriggered(event: .sdkReadyTimedOut));
 
         eventManager.stop()
     }
     
+    func testSplitEventActionTaskMetadata() {
+        
+        // Dummy event with metadata
+        let metadataTypeToCheck: SplitMetadataType = .FEATURE_FLAGS_SYNC_ERROR
+        let metadataDataToCheck: String            = "Error syncing"
+        let dummyMetadata = SplitMetadata(type: metadataTypeToCheck, data: metadataDataToCheck)
+        
+        // This will be the task's "run()"
+        let action: SplitActionWithMetadata = { metadata in
+            XCTAssertEqual(metadataTypeToCheck, metadata!.type)
+            XCTAssertEqual(metadataDataToCheck,metadata!.data)
+        }
+        
+        // SUT
+        let SUT = TestTask(exp: nil, action: action, metadata: dummyMetadata)
+        
+        SUT.run(dummyMetadata)
+        XCTAssertTrue(SUT.taskTriggered)
+    }
+    
     // MARK: Helpers
     func currentTimestamp() -> Int {
         return Int(Date().unixTimestamp())
@@ -273,29 +293,24 @@ class SplitEventsManagerTest: XCTestCase {
     }
 }
 
-class TestTask: SplitEventTask {
-
-    var event: SplitEvent = .sdkReady
-
-    var runInBackground: Bool = false
-
-    var queue: DispatchQueue?
+class TestTask: SplitEventActionTask {
     
     var taskTriggered = false
+    
     let label: String
     var exp: XCTestExpectation?
-    init(exp: XCTestExpectation?, label: String = "") {
+    init(exp: XCTestExpectation?, label: String = "", action: SplitActionWithMetadata? = nil, metadata: SplitMetadata? = nil) {
         self.exp = exp
         self.label = label
+        super.init(action: action ?? { _ in }, event: .sdkReady, factory: SplitFactoryStub(apiKey: IntegrationHelper.dummyApiKey))
     }
 
-    func takeQueue() -> DispatchQueue? {
-        return nil
-    }
-
-    func run() {
+    override func run(_ metadata: SplitMetadata?) {
         print("run: \(self.label)")
         taskTriggered = true
+        
+        super.run(metadata)
+        
         if let exp = self.exp {
             exp.fulfill()
         }