diff --git a/CryptoExchange.Net.UnitTests/ConverterTests.cs b/CryptoExchange.Net.UnitTests/ConverterTests.cs
index 1d4705f..a4f43f8 100644
--- a/CryptoExchange.Net.UnitTests/ConverterTests.cs
+++ b/CryptoExchange.Net.UnitTests/ConverterTests.cs
@@ -140,10 +140,10 @@ namespace CryptoExchange.Net.UnitTests
         [TestCase("four", TestEnum.Four)]
         [TestCase("Four1", null)]
         [TestCase(null, null)]
-        public void TestEnumConverterNullableDeserializeTests(string? value, TestEnum? expected)
+        public void TestEnumConverterNullableDeserializeTests(string value, TestEnum? expected)
         {
             var val = value == null ? "null" : $"\"{value}\"";
-            var output = JsonConvert.DeserializeObject<EnumObject?>($"{{ \"Value\": {val} }}");
+            var output = JsonConvert.DeserializeObject<EnumObject>($"{{ \"Value\": {val} }}");
             Assert.AreEqual(output.Value, expected);
         }
     }
diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs
index adb591e..dc580f1 100644
--- a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs
+++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Net.Http;
 using CryptoExchange.Net.Authentication;
 using CryptoExchange.Net.Objects;
@@ -33,14 +34,11 @@ namespace CryptoExchange.Net.UnitTests
         {
         }
 
-        public override Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed, HttpMethodParameterPosition postParameters, ArrayParametersSerialization arraySerialization)
+        public override void AuthenticateRequest(RestApiClient apiClient, Uri uri, HttpMethod method, Dictionary<string, object> providedParameters, bool auth, ArrayParametersSerialization arraySerialization, HttpMethodParameterPosition parameterPosition, out SortedDictionary<string, object> uriParameters, out SortedDictionary<string, object> bodyParameters, out Dictionary<string, string> headers)
         {
-            return base.AddAuthenticationToHeaders(uri, method, parameters, signed, postParameters, arraySerialization);
-        }
-
-        public override Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed, HttpMethodParameterPosition postParameters, ArrayParametersSerialization arraySerialization)
-        {
-            return base.AddAuthenticationToParameters(uri, method, parameters, signed, postParameters, arraySerialization);
+            bodyParameters = new SortedDictionary<string, object>();
+            uriParameters = new SortedDictionary<string, object>();
+            headers = new Dictionary<string, string>();
         }
 
         public override string Sign(string toSign)
diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs
index 535702a..e425830 100644
--- a/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs
+++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs
@@ -56,10 +56,10 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
             request.Setup(c => c.GetHeaders()).Returns(() => headers);
 
             var factory = Mock.Get(RequestFactory);
-            factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>()))
-                .Callback<HttpMethod, string, int>((method, uri, id) => 
+            factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
+                .Callback<HttpMethod, Uri, int>((method, uri, id) => 
                 { 
-                    request.Setup(a => a.Uri).Returns(new Uri(uri));
+                    request.Setup(a => a.Uri).Returns(uri);
                     request.Setup(a => a.Method).Returns(method); 
                 })
                 .Returns(request.Object);
@@ -76,7 +76,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
             request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we);
 
             var factory = Mock.Get(RequestFactory);
-            factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>()))
+            factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
                 .Returns(request.Object);
         }
 
@@ -99,8 +99,8 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
             request.Setup(c => c.GetHeaders()).Returns(headers);
 
             var factory = Mock.Get(RequestFactory);
-            factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>()))
-                .Callback<HttpMethod, string, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(new Uri(uri)))
+            factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
+                .Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
                 .Returns(request.Object);
         }
 
@@ -122,8 +122,23 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
 
         }
 
+        public override TimeSpan GetTimeOffset()
+        {
+            throw new NotImplementedException();
+        }
+
         protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)
             => new TestAuthProvider(credentials);
+
+        protected override Task<WebCallResult<DateTime>> GetServerTimestampAsync()
+        {
+            throw new NotImplementedException();
+        }
+
+        protected override TimeSyncInfo GetTimeSyncInfo()
+        {
+            throw new NotImplementedException();
+        }
     }
 
     public class TestRestApi2Client : RestApiClient
@@ -133,8 +148,23 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
 
         }
 
+        public override TimeSpan GetTimeOffset()
+        {
+            throw new NotImplementedException();
+        }
+
         protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)
             => new TestAuthProvider(credentials);
+
+        protected override Task<WebCallResult<DateTime>> GetServerTimestampAsync()
+        {
+            throw new NotImplementedException();
+        }
+
+        protected override TimeSyncInfo GetTimeSyncInfo()
+        {
+            throw new NotImplementedException();
+        }
     }
 
     public class TestAuthProvider : AuthenticationProvider
@@ -142,6 +172,13 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
         public TestAuthProvider(ApiCredentials credentials) : base(credentials)
         {
         }
+
+        public override void AuthenticateRequest(RestApiClient apiClient, Uri uri, HttpMethod method, Dictionary<string, object> providedParameters, bool auth, ArrayParametersSerialization arraySerialization, HttpMethodParameterPosition parameterPosition, out SortedDictionary<string, object> uriParameters, out SortedDictionary<string, object> bodyParameters, out Dictionary<string, string> headers)
+        {
+            uriParameters = parameterPosition == HttpMethodParameterPosition.InUri ? new SortedDictionary<string, object>(providedParameters) : new SortedDictionary<string, object>();
+            bodyParameters = parameterPosition == HttpMethodParameterPosition.InBody ? new SortedDictionary<string, object>(providedParameters) : new SortedDictionary<string, object>();
+            headers = new Dictionary<string, string>();
+        }
     }
 
     public class ParseErrorTestRestClient: TestRestClient
diff --git a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs
index eaf4d4a..2942d9e 100644
--- a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs
+++ b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs
@@ -1,6 +1,8 @@
-using CryptoExchange.Net.Objects;
+using CryptoExchange.Net.Converters;
+using CryptoExchange.Net.Objects;
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Net.Http;
 using System.Security.Cryptography;
 using System.Text;
@@ -35,43 +37,41 @@ namespace CryptoExchange.Net.Authentication
         }
 
         /// <summary>
-        /// Authenticate a request where the parameters need to be in the Uri
+        /// Authenticate a request. Output parameters should include the providedParameters input
         /// </summary>
         /// <param name="apiClient">The Api client sending the request</param>
         /// <param name="uri">The uri for the request</param>
         /// <param name="method">The method of the request</param>
-        /// <param name="parameters">The request parameters</param>
-        /// <param name="headers">The request headers</param>
+        /// <param name="providedParameters">The request parameters</param>
         /// <param name="auth">If the requests should be authenticated</param>
         /// <param name="arraySerialization">Array serialization type</param>
-        /// <returns></returns>
-        public abstract void AuthenticateUriRequest(
+        /// <param name="parameterPosition">The position where the providedParameters should go</param>
+        /// <param name="uriParameters">Parameters that need to be in the Uri of the request. Should include the provided parameters if they should go in the uri</param>
+        /// <param name="bodyParameters">Parameters that need to be in the body of the request. Should include the provided parameters if they should go in the body</param>
+        /// <param name="headers">The headers that should be send with the request</param>
+        public abstract void AuthenticateRequest(
             RestApiClient apiClient,
             Uri uri,
             HttpMethod method,
-            SortedDictionary<string, object> parameters,
-            Dictionary<string, string> headers,
+            Dictionary<string, object> providedParameters,
             bool auth,
-            ArrayParametersSerialization arraySerialization);
+            ArrayParametersSerialization arraySerialization,
+            HttpMethodParameterPosition parameterPosition,
+            out SortedDictionary<string, object> uriParameters,
+            out SortedDictionary<string, object> bodyParameters,
+            out Dictionary<string, string> headers
+            );
 
         /// <summary>
-        /// Authenticate a request where the parameters need to be in the request body
+        /// SHA256 sign the data and return the bytes
         /// </summary>
-        /// <param name="apiClient">The Api client sending the request</param>
-        /// <param name="uri">The uri for the request</param>
-        /// <param name="method">The method of the request</param>
-        /// <param name="parameters">The request parameters</param>
-        /// <param name="headers">The request headers</param>
-        /// <param name="auth">If the requests should be authenticated</param>
-        /// <param name="arraySerialization">Array serialization type</param>
-        public abstract void AuthenticateBodyRequest(
-            RestApiClient apiClient,
-            Uri uri,
-            HttpMethod method,
-            SortedDictionary<string, object> parameters,
-            Dictionary<string, string> headers,
-            bool auth,
-            ArrayParametersSerialization arraySerialization);
+        /// <param name="data"></param>
+        /// <returns></returns>
+        protected static byte[] SignSHA256Bytes(string data)
+        {
+            using var encryptor = SHA256.Create();
+            return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
+        }
 
         /// <summary>
         /// SHA256 sign the data and return the hash
@@ -158,9 +158,18 @@ namespace CryptoExchange.Net.Authentication
         /// <param name="outputType">String type</param>
         /// <returns></returns>
         protected string SignHMACSHA512(string data, SignOutputType? outputType = null)
+            => SignHMACSHA512(Encoding.UTF8.GetBytes(data), outputType);
+
+        /// <summary>
+        /// HMACSHA512 sign the data and return the hash
+        /// </summary>
+        /// <param name="data">Data to sign</param>
+        /// <param name="outputType">String type</param>
+        /// <returns></returns>
+        protected string SignHMACSHA512(byte[] data, SignOutputType? outputType = null)
         {
             using var encryptor = new HMACSHA512(_sBytes);
-            var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
+            var resultBytes = encryptor.ComputeHash(data);
             return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
         }
 
@@ -206,5 +215,25 @@ namespace CryptoExchange.Net.Authentication
         {
             return Convert.ToBase64String(buff);
         }
+
+        /// <summary>
+        /// Get current timestamp including the time sync offset from the api client
+        /// </summary>
+        /// <param name="apiClient"></param>
+        /// <returns></returns>
+        protected static DateTime GetTimestamp(RestApiClient apiClient)
+        {
+            return DateTime.UtcNow.Add(apiClient?.GetTimeOffset() ?? TimeSpan.Zero)!;
+        }
+
+        /// <summary>
+        /// Get millisecond timestamp as a string including the time sync offset from the api client
+        /// </summary>
+        /// <param name="apiClient"></param>
+        /// <returns></returns>
+        protected static string GetMillisecondTimestamp(RestApiClient apiClient)
+        {
+            return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture);
+        }
     }
 }
diff --git a/CryptoExchange.Net/Clients/BaseRestClient.cs b/CryptoExchange.Net/Clients/BaseRestClient.cs
index f7c6efd..1d21c3a 100644
--- a/CryptoExchange.Net/Clients/BaseRestClient.cs
+++ b/CryptoExchange.Net/Clients/BaseRestClient.cs
@@ -119,10 +119,13 @@ namespace CryptoExchange.Net
         {
             var requestId = NextId();
 
-            var syncTimeResult = await apiClient.SyncTimeAsync().ConfigureAwait(false);
-            if (!syncTimeResult)
-                return syncTimeResult.As<T>(default);
-            
+            if (signed)
+            {
+                var syncTimeResult = await apiClient.SyncTimeAsync().ConfigureAwait(false);
+                if (!syncTimeResult)
+                    return syncTimeResult.As<T>(default);
+            }
+
             log.Write(LogLevel.Debug, $"[{requestId}] Creating request for " + uri);
             if (signed && apiClient.AuthenticationProvider == null)
             {
@@ -285,33 +288,40 @@ namespace CryptoExchange.Net
             int requestId,
             Dictionary<string, string>? additionalHeaders)
         {
-            SortedDictionary<string, object> sortedParameters = new SortedDictionary<string, object>(GetParameterComparer());
-            if (parameters != null)
-                sortedParameters = new SortedDictionary<string, object>(parameters, GetParameterComparer());
-
+            parameters ??= new Dictionary<string, object>();
             if (parameterPosition == HttpMethodParameterPosition.InUri)
             {
-                foreach (var parameter in sortedParameters)
+                foreach (var parameter in parameters)
                     uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString());
             }
 
-            var length = sortedParameters.Count;
             var headers = new Dictionary<string, string>();
+            var uriParameters = parameterPosition == HttpMethodParameterPosition.InUri ? new SortedDictionary<string, object>(parameters) : new SortedDictionary<string, object>();
+            var bodyParameters = parameterPosition == HttpMethodParameterPosition.InBody ? new SortedDictionary<string, object>(parameters) : new SortedDictionary<string, object>();
             if (apiClient.AuthenticationProvider != null)
+                apiClient.AuthenticationProvider.AuthenticateRequest(
+                    apiClient, 
+                    uri, 
+                    method, 
+                    parameters, 
+                    signed, 
+                    arraySerialization,
+                    parameterPosition,
+                    out uriParameters, 
+                    out bodyParameters, 
+                    out headers);
+                 
+            // Sanity check
+            foreach(var param in parameters)
             {
-                if(parameterPosition == HttpMethodParameterPosition.InUri)
-                    apiClient.AuthenticationProvider.AuthenticateUriRequest(apiClient, uri, method, sortedParameters, headers, signed, arraySerialization);
-                else
-                    apiClient.AuthenticationProvider.AuthenticateBodyRequest(apiClient, uri, method, sortedParameters, headers, signed, arraySerialization);
-            }
-
-            if (parameterPosition == HttpMethodParameterPosition.InUri)
-            {
-                // Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters
-                if (sortedParameters.Count != length)
-                    uri = uri.SetParameters(sortedParameters);
+                if (!uriParameters.ContainsKey(param.Key) && !bodyParameters.ContainsKey(param.Key))
+                    throw new Exception($"Missing parameter {param.Key} after authentication processing. AuthenticationProvider implementation " +
+                        $"should return provided parameters in either the uri or body parameters output");
             }
 
+            // Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters            
+            uri = uri.SetParameters(uriParameters);
+        
             var request = RequestFactory.Create(method, uri, requestId);
             request.Accept = Constants.JsonContentHeader;
 
@@ -335,8 +345,8 @@ namespace CryptoExchange.Net
             if (parameterPosition == HttpMethodParameterPosition.InBody)
             {
                 var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
-                if (sortedParameters?.Any() == true)
-                    WriteParamBody(request, sortedParameters, contentType);
+                if (bodyParameters.Any())
+                    WriteParamBody(request, bodyParameters, contentType);
                 else
                     request.SetContent(requestBodyEmptyContent, contentType);
             }
@@ -386,8 +396,6 @@ namespace CryptoExchange.Net
             //return request;
         }
 
-        protected virtual IComparer<string> GetParameterComparer() => null;
-
         /// <summary>
         /// Writes the parameters of the request to the request object body
         /// </summary>
diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs
index 1247425..00ef774 100644
--- a/CryptoExchange.Net/Clients/RestApiClient.cs
+++ b/CryptoExchange.Net/Clients/RestApiClient.cs
@@ -14,8 +14,16 @@ namespace CryptoExchange.Net
     /// </summary>
     public abstract class RestApiClient: BaseApiClient
     {
-        protected abstract TimeSyncModel GetTimeSyncParameters();
-        protected abstract void UpdateTimeOffset(TimeSpan offset);
+        /// <summary>
+        /// Get time sync info for an API client
+        /// </summary>
+        /// <returns></returns>
+        protected abstract TimeSyncInfo GetTimeSyncInfo();
+        
+        /// <summary>
+        /// Get time offset for an API client
+        /// </summary>
+        /// <returns></returns>
         public abstract TimeSpan GetTimeOffset();
 
         /// <summary>
@@ -33,8 +41,6 @@ namespace CryptoExchange.Net
         /// </summary>
         internal IEnumerable<IRateLimiter> RateLimiters { get; }
 
-        private Log _log;
-
         /// <summary>
         /// ctor
         /// </summary>
@@ -58,12 +64,12 @@ namespace CryptoExchange.Net
 
         internal async Task<WebCallResult<bool>> SyncTimeAsync()
         {
-            var timeSyncParams = GetTimeSyncParameters();
-            if (await timeSyncParams.Semaphore.WaitAsync(0).ConfigureAwait(false))
+            var timeSyncParams = GetTimeSyncInfo();
+            if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false))
             {
-                if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.LastSyncTime < TimeSpan.FromHours(1)))
+                if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < TimeSpan.FromHours(1)))
                 {
-                    timeSyncParams.Semaphore.Release();
+                    timeSyncParams.TimeSyncState.Semaphore.Release();
                     return new WebCallResult<bool>(null, null, true, null);
                 }
 
@@ -71,7 +77,7 @@ namespace CryptoExchange.Net
                 var result = await GetServerTimestampAsync().ConfigureAwait(false);
                 if (!result)
                 {
-                    timeSyncParams.Semaphore.Release();
+                    timeSyncParams.TimeSyncState.Semaphore.Release();
                     return result.As(false);
                 }
 
@@ -82,7 +88,7 @@ namespace CryptoExchange.Net
                     result = await GetServerTimestampAsync().ConfigureAwait(false);
                     if (!result)
                     {
-                        timeSyncParams.Semaphore.Release();
+                        timeSyncParams.TimeSyncState.Semaphore.Release();
                         return result.As(false);
                     }
                 }
@@ -92,13 +98,13 @@ namespace CryptoExchange.Net
                 if (offset.TotalMilliseconds >= 0 && offset.TotalMilliseconds < 500)
                 {
                     // Small offset, probably mainly due to ping. Don't adjust time
-                    UpdateTimeOffset(offset);
-                    timeSyncParams.Semaphore.Release();
+                    timeSyncParams.UpdateTimeOffset(offset);
+                    timeSyncParams.TimeSyncState.Semaphore.Release();
                 }
                 else
                 {
-                    UpdateTimeOffset(offset);
-                    timeSyncParams.Semaphore.Release();
+                    timeSyncParams.UpdateTimeOffset(offset);
+                    timeSyncParams.TimeSyncState.Semaphore.Release();
                 }
             }
 
diff --git a/CryptoExchange.Net/CryptoExchange.Net.csproj b/CryptoExchange.Net/CryptoExchange.Net.csproj
index a587352..b5fca32 100644
--- a/CryptoExchange.Net/CryptoExchange.Net.csproj
+++ b/CryptoExchange.Net/CryptoExchange.Net.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
   </PropertyGroup>
@@ -41,7 +41,7 @@
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
-    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3">
+    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
diff --git a/CryptoExchange.Net/ExtensionMethods.cs b/CryptoExchange.Net/ExtensionMethods.cs
index 1cf4ab0..de4a859 100644
--- a/CryptoExchange.Net/ExtensionMethods.cs
+++ b/CryptoExchange.Net/ExtensionMethods.cs
@@ -151,7 +151,7 @@ namespace CryptoExchange.Net
         public static string ToFormData(this SortedDictionary<string, object> parameters)
         {
             var formData = HttpUtility.ParseQueryString(string.Empty);
-            foreach (var kvp in parameters.OrderBy(p => p.Key))
+            foreach (var kvp in parameters)
             {
                 if (kvp.Value.GetType().IsArray)
                 {
@@ -433,6 +433,25 @@ namespace CryptoExchange.Net
             return uriBuilder.Uri;
         }
 
+        /// <summary>
+        /// Create a new uri with the provided parameters as query
+        /// </summary>
+        /// <param name="parameters"></param>
+        /// <param name="baseUri"></param>
+        /// <returns></returns>
+        public static Uri SetParameters(this Uri baseUri, IOrderedEnumerable<KeyValuePair<string, object>> parameters)
+        {
+            var uriBuilder = new UriBuilder();
+            uriBuilder.Scheme = baseUri.Scheme;
+            uriBuilder.Host = baseUri.Host;
+            uriBuilder.Path = baseUri.AbsolutePath;
+            var httpValueCollection = HttpUtility.ParseQueryString(string.Empty);
+            foreach (var parameter in parameters)
+                httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
+            uriBuilder.Query = httpValueCollection.ToString();
+            return uriBuilder.Uri;
+        }
+
 
         /// <summary>
         /// Add parameter to URI
diff --git a/CryptoExchange.Net/Objects/TimeSyncModel.cs b/CryptoExchange.Net/Objects/TimeSyncModel.cs
deleted file mode 100644
index 6bc1c64..0000000
--- a/CryptoExchange.Net/Objects/TimeSyncModel.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading;
-
-namespace CryptoExchange.Net.Objects
-{
-    public class TimeSyncModel
-    {
-        public bool SyncTime { get; set; }
-        public SemaphoreSlim Semaphore { get; set; }
-        public DateTime LastSyncTime { get; set; }
-
-        public TimeSyncModel(bool syncTime, SemaphoreSlim semaphore, DateTime lastSyncTime)
-        {
-            SyncTime = syncTime;
-            Semaphore = semaphore;
-            LastSyncTime = lastSyncTime;
-        }
-    }
-}
diff --git a/CryptoExchange.Net/Objects/TimeSyncState.cs b/CryptoExchange.Net/Objects/TimeSyncState.cs
new file mode 100644
index 0000000..81f453b
--- /dev/null
+++ b/CryptoExchange.Net/Objects/TimeSyncState.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Threading;
+using CryptoExchange.Net.Logging;
+using Microsoft.Extensions.Logging;
+
+namespace CryptoExchange.Net.Objects
+{
+    /// <summary>
+    /// The time synchronization state of an API client
+    /// </summary>
+    public class TimeSyncState
+    {
+        /// <summary>
+        /// Semaphore to use for checking the time syncing. Should be shared instance among the API client
+        /// </summary>
+        public SemaphoreSlim Semaphore { get; }
+        /// <summary>
+        /// Last sync time for the API client
+        /// </summary>
+        public DateTime LastSyncTime { get; set; }
+        /// <summary>
+        /// Time offset for the API client
+        /// </summary>
+        public TimeSpan TimeOffset { get; set; }
+
+        /// <summary>
+        /// ctor
+        /// </summary>
+        public TimeSyncState()
+        {
+            Semaphore = new SemaphoreSlim(1, 1);
+        }
+    }
+
+    /// <summary>
+    /// Time synchronization info
+    /// </summary>
+    public class TimeSyncInfo
+    {
+        /// <summary>
+        /// Logger
+        /// </summary>
+        public Log Log { get; }
+        /// <summary>
+        /// Should synchronize time
+        /// </summary>
+        public bool SyncTime { get; }
+        /// <summary>
+        /// Time sync state for the API client
+        /// </summary>
+        public TimeSyncState TimeSyncState { get; }
+       
+        /// <summary>
+        /// ctor
+        /// </summary>
+        /// <param name="log"></param>
+        /// <param name="syncTime"></param>
+        /// <param name="syncState"></param>
+        public TimeSyncInfo(Log log, bool syncTime, TimeSyncState syncState)
+        {
+            Log = log;
+            SyncTime = syncTime;
+            TimeSyncState = syncState;
+        }
+
+        /// <summary>
+        /// Set the time offset
+        /// </summary>
+        /// <param name="offset"></param>
+        public void UpdateTimeOffset(TimeSpan offset)
+        {
+            TimeSyncState.LastSyncTime = DateTime.UtcNow;
+            if (offset.TotalMilliseconds > 0 && offset.TotalMilliseconds < 500)
+                return;
+
+            Log.Write(LogLevel.Information, $"Time offset set to {Math.Round(offset.TotalMilliseconds)}ms");
+            TimeSyncState.TimeOffset = offset;
+        }
+    }
+}