diff --git a/Runtime/ChromeDevtools/BrowserTab.cs b/Runtime/ChromeDevtools/BrowserTab.cs
index d0c439b5f93ae4e849c3e87ee2104e0a9ffee822..4f0bbe53ecbdafd81372449c6966619e8d160dae 100644
--- a/Runtime/ChromeDevtools/BrowserTab.cs
+++ b/Runtime/ChromeDevtools/BrowserTab.cs
@@ -254,10 +254,8 @@ namespace bessw.Unity.WebView.ChromeDevTools
         public void Close()
         {
             Debug.Log($"BrowserTab close called for: '{pageTarget.Url}'");
-            _ = devtools.SendCommandAsync(new closeTarget(pageTarget.Id), delegate
-              {
-                  devtools.Dispose();
-              });
+            _ = devtools.SendCommandAsync(new closeTarget(pageTarget.Id))
+                .ContinueWith(_ => devtools.Dispose());
         }
     }
 }
diff --git a/Runtime/ChromeDevtools/DevtoolsProtocolHandler.cs b/Runtime/ChromeDevtools/DevtoolsProtocolHandler.cs
index 75bf0814df5b32860f1cffedec5c271414edc860..2fa4159e4d15be6032bd9e591a1b8ef66e9f76f2 100644
--- a/Runtime/ChromeDevtools/DevtoolsProtocolHandler.cs
+++ b/Runtime/ChromeDevtools/DevtoolsProtocolHandler.cs
@@ -7,7 +7,9 @@ using Newtonsoft.Json.Linq;
 using System;
 using System.Collections;
 using System.Collections.Concurrent;
+using System.Collections.Generic;
 using System.Threading.Tasks;
+using UnityEditor.PackageManager;
 using UnityEngine;
 
 namespace bessw.Unity.WebView.ChromeDevTools
@@ -21,7 +23,7 @@ namespace bessw.Unity.WebView.ChromeDevTools
 
         private IDevtoolsConnection devtools;
 
-        private ConcurrentDictionary<long, ResponseTypeAndCallback> commandResponseDict = new ConcurrentDictionary<long, ResponseTypeAndCallback>();
+        private ConcurrentDictionary<long, ResponseTypeAndCallback<IDevtoolsResponse>> commandResponseDict = new();
 
         // devtools events
         public event Action<screencastFrameEvent> onScreencastFrame;
@@ -102,18 +104,23 @@ namespace bessw.Unity.WebView.ChromeDevTools
         {
 
             // get the callback and the type for this response
-            ResponseTypeAndCallback responseTypeAndCallback;
-            if (!commandResponseDict.TryRemove(response.Id, out responseTypeAndCallback))
+            if (!commandResponseDict.TryRemove(response.Id, out ResponseTypeAndCallback<IDevtoolsResponse> responseTypeAndCallback))
             {
                 Debug.Log($"There is no command waiting for the response '{response.Id}'");
                 return;
             }
 
-            // deserialize the result
-            IDevtoolsResponse commandResponse = (IDevtoolsResponse) response.Result.ToObject(responseTypeAndCallback.responseType, Browser.devtoolsSerializer);
+            if (response.IsError)
+            {
+                responseTypeAndCallback.Task.SetException(new DevtoolsCommandException(response.Error));
+            } else
+            {
+                // deserialize the result
+                IDevtoolsResponse commandResponse = (IDevtoolsResponse)response.Result.ToObject(responseTypeAndCallback.ResponseType, Browser.devtoolsSerializer);
 
-            // pass the response to the callback
-            responseTypeAndCallback.callback(commandResponse);
+                // pass the response to the callback
+                responseTypeAndCallback.Task.SetResult(commandResponse);
+            }
         }
 
         /// <summary>
@@ -192,12 +199,13 @@ namespace bessw.Unity.WebView.ChromeDevTools
         /// Coroutine wrapper for SendCommandAsync
         /// json serializes and sends a command over the devtools websocket
         /// </summary>
-        /// <typeparam name="T">IDevtoolsCommand</typeparam>
+        /// <typeparam name="C">IDevtoolsCommand</typeparam>
         /// <param name="command"></param>
         /// <returns></returns>
-        public IEnumerator SendCommand<T>(T command, Action<IDevtoolsResponse> callback) where T : IDevtoolsCommandWithResponse
+        public IEnumerator SendCommand<C>(C command, Action<IDevtoolsResponse> callback) where C : IDevtoolsCommandWithResponse
         {
-            var sendTask = SendCommandAsync(command, callback);
+            var sendTask = SendCommandAsync(command)
+                .ContinueWith(_ => callback);
             // wait until the command has been send
             yield return new WaitUntil(() => sendTask.IsCompleted);
         }
@@ -205,61 +213,29 @@ namespace bessw.Unity.WebView.ChromeDevTools
         /// <summary>
         /// json serializes and sends a command over the devtools websocket
         /// </summary>
-        /// <typeparam name="C">IDevtoolsCommandWithResponse</typeparam>
-        /// <typeparam name="R">IDevtoolsResponse</typeparam>
-        /// <param name="command"></param>
-        /// <returns>Task that resoves with the response</returns>
-        public Task<R> SendCommandAsync<C, R>(C command) where C : IDevtoolsCommandWithResponse where R : class, IDevtoolsResponse
-        {
-            // apply the message wrapper
-            var wrappedCommand = new DevtoolsCommandWrapper<C>(command)
-            {
-                Id = ++LAST_COMMAND_ID
-            };
-
-            TaskCompletionSource<R> tcs = new TaskCompletionSource<R>();
-            
-            // register the response callback for this command id
-            if (!commandResponseDict.TryAdd(wrappedCommand.Id, new ResponseTypeAndCallback(typeof(R), (res) => tcs.SetResult(res as R))))
-            {
-                throw new InvalidOperationException($"could not add response callback for command '{wrappedCommand.Id}' to commandResponseDict");
-            }
-
-            // json serialize the command and send it
-            var json = JsonConvert.SerializeObject(wrappedCommand, Browser.devtoolsSerializerSettings);
-            Debug.Log($"ws send: '{json}'");
-            devtools.SendCommandAsync(json);
-            return tcs.Task;
-        }
+        /// <typeparam name="C">IDevtoolsCommand</typeparam>
+        /// <returns>Task that resoves then the command is send</returns>
+        public Task<DevtoolsResponse> SendCommandAsync<C>(C command) where C : IDevtoolsCommand => SendCommandAsync<C, DevtoolsResponse>(command);
 
         /// <summary>
         /// json serializes and sends a command over the devtools websocket
         /// </summary>
-        /// <typeparam name="T">IDevtoolsCommand</typeparam>
+        /// <typeparam name="C">IDevtoolsCommandWithResponse</typeparam>
+        /// <typeparam name="R">IDevtoolsResponse</typeparam>
         /// <param name="command"></param>
-        /// <returns>Task that resoves then the command is send</returns>
-        public async Task SendCommandAsync<T>(T command, Action<IDevtoolsResponse> callback) where T : IDevtoolsCommandWithResponse
+        /// <returns>Task that resoves with the response</returns>
+        public Task<R> SendCommandAsync<C, R>(C command) where C : IDevtoolsCommand where R : class, IDevtoolsResponse
         {
             // apply the message wrapper
-            var wrappedCommand = new DevtoolsCommandWrapper<T>(command)
+            var wrappedCommand = new DevtoolsCommandWrapper<C>(command)
             {
                 Id = ++LAST_COMMAND_ID
             };
 
-            // get the response type from the commands attribute
-            CommandResponseAttribute cra = (CommandResponseAttribute)Attribute.GetCustomAttribute(
-                    command.GetType(),
-                    typeof(CommandResponseAttribute)
-                );
-            if( cra == null )
-            {
-                throw new NotImplementedException("command has no respnse attribute");
-            }
-            // TODO: handle command without response
-            Type responseType = cra.responseType;
+            TaskCompletionSource<IDevtoolsResponse> response_tcs = new();
 
             // register the response callback for this command id
-            if (!commandResponseDict.TryAdd(wrappedCommand.Id, new ResponseTypeAndCallback(responseType, callback)))
+            if (!commandResponseDict.TryAdd(wrappedCommand.Id, new ResponseTypeAndCallback<IDevtoolsResponse>(typeof(R), response_tcs)))
             {
                 throw new InvalidOperationException($"could not add response callback for command '{wrappedCommand.Id}' to commandResponseDict");
             }
@@ -267,26 +243,9 @@ namespace bessw.Unity.WebView.ChromeDevTools
             // json serialize the command and send it
             var json = JsonConvert.SerializeObject(wrappedCommand, Browser.devtoolsSerializerSettings);
             Debug.Log($"ws send: '{json}'");
-            await devtools.SendCommandAsync(json);
-        }
-
-        /// <summary>
-        /// json serializes and sends a command over the devtools websocket
-        /// </summary>
-        /// <typeparam name="T">IDevtoolsCommand</typeparam>
-        /// <returns>Task that resoves then the command is send</returns>
-        public async Task SendCommandAsync<T>(T command) where T : IDevtoolsCommand
-        {
-            // apply the message wrapper
-            var wrappedCommand = new DevtoolsCommandWrapper<T>(command)
-            {
-                Id = ++LAST_COMMAND_ID
-            };
+            devtools.SendCommandAsync(json);
 
-            // json serialize the command and send it
-            var json = JsonConvert.SerializeObject(wrappedCommand, Browser.devtoolsSerializerSettings);
-            Debug.Log($"ws send: '{json}'");
-            await devtools.SendCommandAsync(json);
+            return  response_tcs.Task.ContinueWith(task => (R) task.Result);
         }
 
         public void Dispose()
@@ -298,25 +257,34 @@ namespace bessw.Unity.WebView.ChromeDevTools
     /////////////////////////////
     // Utility classes:
 
-
     /// <summary>
     /// Container type to store a response type together with its callback.
     /// This is used in DevtoolsProtocolHandler to parse the response to the correct C# data type and call the callback with it as parameter.
     /// </summary>
-    record ResponseTypeAndCallback
+    record ResponseTypeAndCallback<RESPONSE_TYPE> where RESPONSE_TYPE : class, IDevtoolsResponse
     {
-        public ResponseTypeAndCallback(Type responseType, Action<IDevtoolsResponse> callback)
+        public ResponseTypeAndCallback(Type responseType, TaskCompletionSource<RESPONSE_TYPE> task)
         {
-            this.responseType = responseType;
-            this.callback = callback;
+            this.ResponseType = responseType;
+            this.Task = task;
         }
 
-        public Type responseType { get; }
-        public Action<IDevtoolsResponse> callback { get; }
+        public Type ResponseType { get; }
+        public TaskCompletionSource<RESPONSE_TYPE> Task { get; }
     }
+
     public class UnexpectedMessageException : DevtoolsConnectionException
     {
         public UnexpectedMessageException(string message) : base($"Unexpected message {message}") { }
     }
 
+    public class DevtoolsCommandException : DevtoolsConnectionException
+    {
+        public readonly long Code;
+        public DevtoolsCommandException(DevtoolsErrorResponse error) : base(error.Message)
+        {
+            Code = error.Code;
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs b/Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs
index 5c254125b7855e978862f296ddb21b9667c7a529..b53fdc206b1ce4a301a25eb9c0ccbd6e903a83f6 100644
--- a/Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs
+++ b/Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs
@@ -7,13 +7,6 @@ namespace bessw.Unity.WebView.ChromeDevTools.Protocol
         public string Method { get; set; }
         public JObject Params { get; set; }
     }
-    ///
-    /// Every devtools command response has the same id and a method as the corresponding command
-    ///
-    public class DevtoolsEventWrapper<T> : DevtoolsEventWrapper where T : IDevtoolsEvent
-    {
-        public new T Params { get; set; }
-    }
 
     public interface IDevtoolsEvent { }
 }
diff --git a/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs b/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs
index def8bdbba961ec86d3e70b2c05e31347f4755622..767e01d889d4c659f9a69a72b8eb4a4dcda60ce4 100644
--- a/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs
+++ b/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs
@@ -1,19 +1,25 @@
+using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
 
+#nullable enable annotations
 namespace bessw.Unity.WebView.ChromeDevTools.Protocol
 {
     public class DevtoolsResponseWrapper
     {
         public long Id { get; set; }
-        public JObject Result { get; set; }
+        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+        public JObject? Result { get; set; }
+        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+        public DevtoolsErrorResponse? Error { get; set; }
+        [JsonIgnore]
+        public bool IsError => Error is not null;
     }
-    ///
-    /// Every devtools command response has the same id and a method as the corresponding command
-    ///
-    public class DevtoolsResponseWrapper<T> : DevtoolsResponseWrapper where T : IDevtoolsResponse
+    public class DevtoolsErrorResponse
     {
-        public new T Result { get; set; }
+        public long Code { get; set; }
+        public string Message { get; set; }
     }
 
     public interface IDevtoolsResponse { }
+    public class DevtoolsResponse : IDevtoolsResponse { }
 }