diff --git a/slixmpp/plugins/xep_0050/adhoc.py b/slixmpp/plugins/xep_0050/adhoc.py
index 5f3bc81c..072ec5aa 100644
--- a/slixmpp/plugins/xep_0050/adhoc.py
+++ b/slixmpp/plugins/xep_0050/adhoc.py
@@ -3,6 +3,7 @@
 # Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 # This file is part of Slixmpp.
 # See the file LICENSE for copying permission.
+import asyncio
 import logging
 import time
 
@@ -164,25 +165,25 @@ class XEP_0050(BasePlugin):
         self.xmpp.event('command', iq)
         self.xmpp.event('command_%s' % iq['command']['action'], iq)
 
-    def _handle_command_all(self, iq: Iq) -> None:
+    async def _handle_command_all(self, iq: Iq) -> None:
         action = iq['command']['action']
         sessionid = iq['command']['sessionid']
         session = self.sessions.get(sessionid)
 
         if session is None:
-            return self._handle_command_start(iq)
+            return await self._handle_command_start(iq)
 
         if action in ('next', 'execute'):
-            return self._handle_command_next(iq)
+            return await self._handle_command_next(iq)
         if action == 'prev':
-            return self._handle_command_prev(iq)
+            return await self._handle_command_prev(iq)
         if action == 'complete':
-            return self._handle_command_complete(iq)
+            return await self._handle_command_complete(iq)
         if action == 'cancel':
-            return self._handle_command_cancel(iq)
+            return await self._handle_command_cancel(iq)
         return None
 
-    def _handle_command_start(self, iq):
+    async def _handle_command_start(self, iq):
         """
         Process an initial request to execute a command.
 
@@ -222,11 +223,11 @@ class XEP_0050(BasePlugin):
                            'prev': None,
                            'cancel': None}
 
-        session = handler(iq, initial_session)
+        session = await _await_if_needed(handler, iq, initial_session)
 
         self._process_command_response(iq, session)
 
-    def _handle_command_next(self, iq):
+    async def _handle_command_next(self, iq):
         """
         Process a request for the next step in the workflow
         for a command with multiple steps.
@@ -246,13 +247,13 @@ class XEP_0050(BasePlugin):
             if len(results) == 1:
                 results = results[0]
 
-            session = handler(results, session)
+            session = await _await_if_needed(handler, results, session)
 
             self._process_command_response(iq, session)
         else:
             raise XMPPError('item-not-found')
 
-    def _handle_command_prev(self, iq):
+    async def _handle_command_prev(self, iq):
         """
         Process a request for the prev step in the workflow
         for a command with multiple steps.
@@ -272,7 +273,7 @@ class XEP_0050(BasePlugin):
             if len(results) == 1:
                 results = results[0]
 
-            session = handler(results, session)
+            session = await _await_if_needed(handler, results, session)
 
             self._process_command_response(iq, session)
         else:
@@ -334,7 +335,7 @@ class XEP_0050(BasePlugin):
 
         iq.send()
 
-    def _handle_command_cancel(self, iq):
+    async def _handle_command_cancel(self, iq):
         """
         Process a request to cancel a command's execution.
 
@@ -348,7 +349,7 @@ class XEP_0050(BasePlugin):
         if session:
             handler = session['cancel']
             if handler:
-                handler(iq, session)
+                await _await_if_needed(handler, iq, session)
             del self.sessions[sessionid]
             iq = iq.reply()
             iq['command']['node'] = node
@@ -360,7 +361,7 @@ class XEP_0050(BasePlugin):
             raise XMPPError('item-not-found')
 
 
-    def _handle_command_complete(self, iq):
+    async def _handle_command_complete(self, iq):
         """
         Process a request to finish the execution of command
         and terminate the workflow.
@@ -385,7 +386,7 @@ class XEP_0050(BasePlugin):
                 results = results[0]
 
             if handler:
-                handler(results, session)
+                await _await_if_needed(handler, results, session)
 
             del self.sessions[sessionid]
 
@@ -616,3 +617,12 @@ class XEP_0050(BasePlugin):
 
         if iq['command']['status'] == 'completed':
             self.terminate_command(session)
+
+
+async def _await_if_needed(handler, *args):
+    if asyncio.iscoroutinefunction(handler):
+        log.debug(f"%s is async", handler)
+        return await handler(*args)
+    else:
+        log.debug(f"%s is sync", handler)
+        return handler(*args)
diff --git a/tests/test_stream_xep_0050.py b/tests/test_stream_xep_0050.py
index 37cd233d..519c860e 100644
--- a/tests/test_stream_xep_0050.py
+++ b/tests/test_stream_xep_0050.py
@@ -47,7 +47,6 @@ class TestAdHocCommands(SlixTest):
             session['has_next'] = False
 
             return session
-
         self.xmpp['xep_0050'].add_command('tester@localhost', 'foo',
                                           'Do Foo', handle_command)
 
@@ -418,8 +417,6 @@ class TestAdHocCommands(SlixTest):
           </iq>
         """)
 
-
-
     def testMultiPayloads(self):
         """Test using commands with multiple payloads."""
         results = []
@@ -519,6 +516,451 @@ class TestAdHocCommands(SlixTest):
         self.assertEqual(results, [['form_1'], ['form_2']],
                 "Command handler was not executed: %s" % results)
 
+    def testZeroStepCommandAsync(self):
+        """Test running a command with no steps."""
+
+        async def handle_command(iq, session):
+            form = self.xmpp['xep_0004'].make_form(ftype='result')
+            form.addField(var='foo', ftype='text-single',
+                          label='Foo', value='bar')
+
+            session['payload'] = form
+            session['next'] = None
+            session['has_next'] = False
+
+            return session
+
+        self.xmpp['xep_0050'].add_command('tester@localhost', 'foo',
+                                          'Do Foo', handle_command)
+
+        self.recv("""
+          <iq id="11" type="set" to="tester@localhost" from="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     action="execute" />
+          </iq>
+        """)
+
+        self.send("""
+          <iq id="11" type="result" to="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     status="completed"
+                     sessionid="_sessionid_">
+              <x xmlns="jabber:x:data" type="result">
+                <field var="foo" label="Foo" type="text-single">
+                  <value>bar</value>
+                </field>
+              </x>
+            </command>
+          </iq>
+        """)
+
+    def testOneStepCommandAsync(self):
+        """Test running a single step command."""
+        results = []
+
+        async def handle_command(iq, session):
+
+            async def handle_form(form, session):
+                results.append(form.get_values()['foo'])
+                session['payload'] = None
+
+            form = self.xmpp['xep_0004'].make_form('form')
+            form.addField(var='foo', ftype='text-single', label='Foo')
+
+            session['payload'] = form
+            session['next'] = handle_form
+            session['has_next'] = False
+
+            return session
+
+        self.xmpp['xep_0050'].add_command('tester@localhost', 'foo',
+                                          'Do Foo', handle_command)
+
+        self.recv("""
+          <iq id="11" type="set" to="tester@localhost" from="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     action="execute" />
+          </iq>
+        """)
+
+        self.send("""
+          <iq id="11" type="result" to="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     status="executing"
+                     sessionid="_sessionid_">
+              <actions>
+                <complete />
+              </actions>
+              <x xmlns="jabber:x:data" type="form">
+                <field var="foo" label="Foo" type="text-single" />
+              </x>
+            </command>
+          </iq>
+        """)
+
+        self.recv("""
+          <iq id="12" type="set" to="tester@localhost" from="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     action="complete"
+                     sessionid="_sessionid_">
+              <x xmlns="jabber:x:data" type="submit">
+                <field var="foo" label="Foo" type="text-single">
+                  <value>blah</value>
+                </field>
+              </x>
+            </command>
+          </iq>
+        """)
+
+        self.send("""
+          <iq id="12" type="result" to="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     status="completed"
+                     sessionid="_sessionid_" />
+          </iq>
+        """)
+
+        self.assertEqual(results, ['blah'],
+                "Command handler was not executed: %s" % results)
+
+    def testTwoStepCommandAsync(self):
+        """Test using a two-stage command."""
+        results = []
+
+        async def handle_command(iq, session):
+
+            async def handle_step2(form, session):
+                results.append(form.get_values()['bar'])
+                session['payload'] = None
+
+            async def handle_step1(form, session):
+                results.append(form.get_values()['foo'])
+
+                form = self.xmpp['xep_0004'].make_form('form')
+                form.addField(var='bar', ftype='text-single', label='Bar')
+
+                session['payload'] = form
+                session['next'] = handle_step2
+                session['has_next'] = False
+
+                return session
+
+            form = self.xmpp['xep_0004'].make_form('form')
+            form.addField(var='foo', ftype='text-single', label='Foo')
+
+            session['payload'] = form
+            session['next'] = handle_step1
+            session['has_next'] = True
+
+            return session
+
+        self.xmpp['xep_0050'].add_command('tester@localhost', 'foo',
+                                          'Do Foo', handle_command)
+
+        self.recv("""
+          <iq id="11" type="set" to="tester@localhost" from="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     action="execute" />
+          </iq>
+        """)
+
+        self.send("""
+          <iq id="11" type="result" to="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     status="executing"
+                     sessionid="_sessionid_">
+              <actions>
+                <next />
+              </actions>
+              <x xmlns="jabber:x:data" type="form">
+                <field var="foo" label="Foo" type="text-single" />
+              </x>
+            </command>
+          </iq>
+        """)
+
+        self.recv("""
+          <iq id="12" type="set" to="tester@localhost" from="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     action="next"
+                     sessionid="_sessionid_">
+              <x xmlns="jabber:x:data" type="submit">
+                <field var="foo" label="Foo" type="text-single">
+                  <value>blah</value>
+                </field>
+              </x>
+            </command>
+          </iq>
+        """)
+
+        self.send("""
+          <iq id="12" type="result" to="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     status="executing"
+                     sessionid="_sessionid_">
+              <actions>
+                <complete />
+              </actions>
+              <x xmlns="jabber:x:data" type="form">
+                <field var="bar" label="Bar" type="text-single" />
+              </x>
+            </command>
+          </iq>
+        """)
+
+        self.recv("""
+          <iq id="13" type="set" to="tester@localhost" from="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     action="complete"
+                     sessionid="_sessionid_">
+              <x xmlns="jabber:x:data" type="submit">
+                <field var="bar" label="Bar" type="text-single">
+                  <value>meh</value>
+                </field>
+              </x>
+            </command>
+          </iq>
+        """)
+        self.send("""
+          <iq id="13" type="result" to="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     status="completed"
+                     sessionid="_sessionid_" />
+          </iq>
+        """)
+
+        self.assertEqual(results, ['blah', 'meh'],
+                "Command handler was not executed: %s" % results)
+
+    def testCancelCommandAsync(self):
+        """Test canceling command."""
+        results = []
+
+        async def handle_command(iq, session):
+
+            async def handle_form(form, session):
+                results.append(form['values']['foo'])
+
+            async def handle_cancel(iq, session):
+                results.append('canceled')
+
+            form = self.xmpp['xep_0004'].make_form('form')
+            form.addField(var='foo', ftype='text-single', label='Foo')
+
+            session['payload'] = form
+            session['next'] = handle_form
+            session['cancel'] = handle_cancel
+            session['has_next'] = False
+
+            return session
+
+        self.xmpp['xep_0050'].add_command('tester@localhost', 'foo',
+                                          'Do Foo', handle_command)
+
+        self.recv("""
+          <iq id="11" type="set" to="tester@localhost" from="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     action="execute" />
+          </iq>
+        """)
+
+        self.send("""
+          <iq id="11" type="result" to="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     status="executing"
+                     sessionid="_sessionid_">
+              <actions>
+                <complete />
+              </actions>
+              <x xmlns="jabber:x:data" type="form">
+                <field var="foo" label="Foo" type="text-single" />
+              </x>
+            </command>
+          </iq>
+        """)
+
+        self.recv("""
+          <iq id="12" type="set" to="tester@localhost" from="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     action="cancel"
+                     sessionid="_sessionid_">
+              <x xmlns="jabber:x:data" type="submit">
+                <field var="foo" label="Foo" type="text-single">
+                  <value>blah</value>
+                </field>
+              </x>
+            </command>
+          </iq>
+        """)
+
+        self.send("""
+          <iq id="12" type="result" to="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     status="canceled"
+                     sessionid="_sessionid_" />
+          </iq>
+        """)
+
+        self.assertEqual(results, ['canceled'],
+                "Cancelation handler not executed: %s" % results)
+
+    def testCommandNoteAsync(self):
+        """Test adding notes to commands."""
+
+        async def handle_command(iq, session):
+            form = self.xmpp['xep_0004'].make_form(ftype='result')
+            form.addField(var='foo', ftype='text-single',
+                          label='Foo', value='bar')
+
+            session['payload'] = form
+            session['next'] = None
+            session['has_next'] = False
+            session['notes'] = [('info', 'testing notes')]
+
+            return session
+
+        self.xmpp['xep_0050'].add_command('tester@localhost', 'foo',
+                                          'Do Foo', handle_command)
+
+        self.recv("""
+          <iq id="11" type="set" to="tester@localhost" from="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     action="execute" />
+          </iq>
+        """)
+
+        self.send("""
+          <iq id="11" type="result" to="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     status="completed"
+                     sessionid="_sessionid_">
+              <note type="info">testing notes</note>
+              <x xmlns="jabber:x:data" type="result">
+                <field var="foo" label="Foo" type="text-single">
+                  <value>bar</value>
+                </field>
+              </x>
+            </command>
+          </iq>
+        """)
+
+    def testMultiPayloadsAsync(self):
+        """Test using commands with multiple payloads."""
+        results = []
+
+        async def handle_command(iq, session):
+
+            async def handle_form(forms, session):
+                for form in forms:
+                    results.append(form.get_values()['FORM_TYPE'])
+                session['payload'] = None
+
+            form1 = self.xmpp['xep_0004'].make_form('form')
+            form1.addField(var='FORM_TYPE', ftype='hidden', value='form_1')
+            form1.addField(var='foo', ftype='text-single', label='Foo')
+
+            form2 = self.xmpp['xep_0004'].make_form('form')
+            form2.addField(var='FORM_TYPE', ftype='hidden', value='form_2')
+            form2.addField(var='foo', ftype='text-single', label='Foo')
+
+            session['payload'] = [form1, form2]
+            session['next'] = handle_form
+            session['has_next'] = False
+
+            return session
+
+        self.xmpp['xep_0050'].add_command('tester@localhost', 'foo',
+                                          'Do Foo', handle_command)
+
+        self.recv("""
+          <iq id="11" type="set" to="tester@localhost" from="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     action="execute" />
+          </iq>
+        """)
+
+        self.send("""
+          <iq id="11" type="result" to="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     status="executing"
+                     sessionid="_sessionid_">
+              <actions>
+                <complete />
+              </actions>
+              <x xmlns="jabber:x:data" type="form">
+                <field var="FORM_TYPE" type="hidden">
+                  <value>form_1</value>
+                </field>
+                <field var="foo" label="Foo" type="text-single" />
+              </x>
+              <x xmlns="jabber:x:data" type="form">
+                <field var="FORM_TYPE" type="hidden">
+                  <value>form_2</value>
+                </field>
+                <field var="foo" label="Foo" type="text-single" />
+              </x>
+            </command>
+          </iq>
+        """)
+
+        self.recv("""
+          <iq id="12" type="set" to="tester@localhost" from="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     action="complete"
+                     sessionid="_sessionid_">
+             <x xmlns="jabber:x:data" type="submit">
+                <field var="FORM_TYPE" type="hidden">
+                  <value>form_1</value>
+                </field>
+                <field var="foo" type="text-single">
+                  <value>bar</value>
+                </field>
+              </x>
+              <x xmlns="jabber:x:data" type="submit">
+                <field var="FORM_TYPE" type="hidden">
+                  <value>form_2</value>
+                </field>
+                <field var="foo" type="text-single">
+                  <value>bar</value>
+                </field>
+              </x>
+            </command>
+          </iq>
+        """)
+
+        self.send("""
+          <iq id="12" type="result" to="foo@bar">
+            <command xmlns="http://jabber.org/protocol/commands"
+                     node="foo"
+                     status="completed"
+                     sessionid="_sessionid_" />
+          </iq>
+        """)
+
+        self.assertEqual(results, [['form_1'], ['form_2']],
+                "Command handler was not executed: %s" % results)
+
     def testClientAPI(self):
         """Test using client-side API for commands."""
         results = []