diff --git a/setup.py b/setup.py
index 8e93b9b4dca5ebd36ba1a52e3e6f4d6cf0c1e72a..9ba1d2db39cc6bc9251acb1ec37130ded2ea8093 100644
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@ for f in sorted(glob.glob(os.path.join('slapos', 'README.*.rst'))):
 long_description += open("CHANGES.txt").read() + "\n"
 
 prediction_require = ['statsmodels', 'scipy', 'pandas']
-test_require = ['mock', 'cryptography',] + prediction_require
+test_require = ['mock', 'cryptography', 'websockets; python_version>="3"',] + prediction_require
 
 setup(name=name,
       version=version,
@@ -54,6 +54,7 @@ setup(name=name,
         'six',
         'cryptography',
         'click',
+        'websocket-client; python_version>="3"',
         'ipaddress; python_version<"3"',
       ),
       extras_require = {
diff --git a/slapos/promise/plugin/check_websocket_available.py b/slapos/promise/plugin/check_websocket_available.py
new file mode 100644
index 0000000000000000000000000000000000000000..96525f946669b19962595808ebfe1d5fc6ea3d96
--- /dev/null
+++ b/slapos/promise/plugin/check_websocket_available.py
@@ -0,0 +1,71 @@
+"""
+Some notable parameters:
+
+  url:
+    The URL of the websocket to test
+  promise-timeout:
+    Optional timeout (in seconds) for promise.
+  timeout:
+    Optional timeout (in seconds) for websocket request.
+  frequency:
+    Optional frequency (in minutes) for running this promise.
+  binary:
+    Boolean to say if the frames sent to websocket are binary (default) or text, only useful when content* options are set
+  content-to-send:
+    Optional bytes array or string (depending on binary) to send to the websocket
+  content-to-receive:
+    Optional bytes array or string (depending on binary) to compare the first message sent by websocket with (must be used with content to send)
+"""
+
+from zope.interface import implementer
+from slapos.grid.promise import interface
+from slapos.grid.promise.generic import GenericPromise
+
+import websocket
+
+@implementer(interface.IPromise)
+class RunPromise(GenericPromise):
+  def __init__(self, config):
+    super(RunPromise, self).__init__(config)
+    # SR can set custom periodicity
+    self.setPeriodicity(float(self.getConfig('frequency', 2)))
+
+  def sense(self):
+    """
+      Check if websocket URL is available.
+    """
+
+    url = self.getConfig('url')
+    # make default time a max of 5 seconds, a bit smaller than promise-timeout
+    # and in the same time at least 1 second
+    default_timeout = max(
+      1, min(5, int(self.getConfig('promise-timeout', 20)) - 1))
+    binary = self.getConfig('binary', True)
+    content_to_send = self.getConfig('content-to-send')
+    content_to_receive = self.getConfig('content-to-receive')
+
+    try:
+      ws = websocket.create_connection(url, timeout=int(self.getConfig('timeout', default_timeout)))
+    except websocket._exceptions.WebSocketBadStatusException:
+      self.logger.error(
+        "ERROR connection not possible while accessing %r", url)
+    except Exception as e:
+      self.logger.error("ERROR: %s", e)
+    else:
+      if content_to_send and content_to_receive:
+        if binary:
+          ws.send_binary(content_to_send)
+        else:
+          ws.send(content_to_send)
+
+        response = ws.recv()
+        if response != content_to_receive:
+          self.logger.error("ERROR received %r instead of %r", response, content_to_receive)
+        else:
+          self.logger.info("Correctly received %r from %r", content_to_receive, url)
+      else:
+        self.logger.info("Correctly connected to %r", url)
+      ws.close()
+
+  def anomaly(self):
+    return self._test(result_count=3, failure_amount=3)
diff --git a/slapos/test/promise/plugin/check_websocket_available_py3_test.py b/slapos/test/promise/plugin/check_websocket_available_py3_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..dfd272f50007dcd29ef3ead070cf8a5765a8480e
--- /dev/null
+++ b/slapos/test/promise/plugin/check_websocket_available_py3_test.py
@@ -0,0 +1,107 @@
+##############################################################################
+#
+# Copyright (c) 2019 Vifib SARL and Contributors. All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsibility of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# guarantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+#聽This module contains python3 syntax that can't be parsed by python2
+# that's why it is in a separated module
+
+from slapos.grid.promise import PromiseError
+from . import TestPromisePluginMixin
+from slapos.util import str2bytes
+
+import asyncio
+import contextlib
+import os
+import time
+import websocket
+from websockets import serve
+import multiprocessing
+
+
+class CheckWebsocketAvailableMixin(TestPromisePluginMixin):
+
+
+  @classmethod
+  def setUpClass(cls):
+    SLAPOS_TEST_IPV4 = os.environ.get('SLAPOS_TEST_IPV4', '127.0.0.1')
+    SLAPOS_TEST_IPV4_PORT = 57965
+    cls.WS_ENDPOINT = "ws://%s:%s/" % (SLAPOS_TEST_IPV4, SLAPOS_TEST_IPV4_PORT)
+
+    async def echo(websocket):
+      path = websocket.path.split('/')[-1]
+
+      if '_' in path:
+        response, timeout = path.split('_')
+        response = response
+        timeout = int(timeout)
+      else:
+        timeout = 0
+        response = path
+
+      time.sleep(timeout)
+      async for message in websocket:
+        if response == "OK":
+          await websocket.send(message)
+        else:
+          await websocket.send("bad")
+
+    async def server():
+      async with serve(echo, SLAPOS_TEST_IPV4, SLAPOS_TEST_IPV4_PORT):
+        await asyncio.Future()  # run forever
+
+    def main():
+      asyncio.run(server())
+
+    cls.server_process = multiprocessing.Process(target=main)
+    cls.server_process.start()
+    for _ in range(20):
+      try:
+        with contextlib.closing(websocket.create_connection((SLAPOS_TEST_IPV4, SLAPOS_TEST_IPV4_PORT))):
+          break
+      except Exception:
+        time.sleep(.1)
+
+  @classmethod
+  def tearDownClass(cls):
+    cls.server_process.terminate()
+    cls.server_process.join()
+
+  def setUp(self):
+    TestPromisePluginMixin.setUp(self)
+    self.promise_name = "check-websocket-available.py"
+
+  def make_content(self, option_dict):
+    content = """from slapos.promise.plugin.check_websocket_available import RunPromise
+
+extra_config_dict = {
+"""
+    for option in option_dict.items():
+      content += "\n  '%s': %r," % option
+
+    return content + "\n}"
+
+  def tearDown(self):
+    TestPromisePluginMixin.tearDown(self)
diff --git a/slapos/test/promise/plugin/test_check_websocket_available.py b/slapos/test/promise/plugin/test_check_websocket_available.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e99ce4a87620428c24aebe8bd2238833d6566f6
--- /dev/null
+++ b/slapos/test/promise/plugin/test_check_websocket_available.py
@@ -0,0 +1,164 @@
+##############################################################################
+#
+# Copyright (c) 2019 Vifib SARL and Contributors. All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsibility of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# guarantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+
+
+from slapos.grid.promise import PromiseError
+from slapos.util import str2bytes
+
+import unittest
+import sys
+import contextlib
+import os
+import random
+import string
+import time
+import multiprocessing
+
+if sys.version_info[0] >= 3:
+  import asyncio
+  import websocket
+  from websockets import serve
+  from .check_websocket_available_py3_test import CheckWebsocketAvailableMixin
+else:
+  class CheckWebsocketAvailableMixin():
+    pass
+
+@unittest.skipIf(sys.version_info[0] < 3, "not supported in this library version")
+class TestCheckWebsocketAvailable(CheckWebsocketAvailableMixin):
+
+  def test_check_url_bad(self):
+    content = self.make_content({
+      'url': 'ws://',
+      'timeout': 10,
+    })
+    self.writePromise(self.promise_name, content)
+    self.configureLauncher()
+    with self.assertRaises(PromiseError):
+      self.launcher.run()
+    result = self.getPromiseResult(self.promise_name)
+    self.assertEqual(result['result']['failed'], True)
+    self.assertEqual(
+      result['result']['message'],
+      "ERROR: hostname is invalid"
+    )
+
+  def test_check_simple_connect(self):
+    url = self.WS_ENDPOINT + 'OK'
+    content = self.make_content({
+      'url': url,
+      'timeout': 10,
+    })
+    self.writePromise(self.promise_name, content)
+    self.configureLauncher()
+    self.launcher.run()
+    result = self.getPromiseResult(self.promise_name)
+    self.assertEqual(result['result']['failed'], False)
+    self.assertEqual(
+      result['result']['message'],
+      ("Correctly connected to %r" % url)
+    )
+
+  def test_check_read_text(self):
+    text = ''.join(random.choice(string.ascii_letters) for i in range(10))
+    url = self.WS_ENDPOINT + 'OK'
+    content = self.make_content({
+      'url': url,
+      'binary': False,
+      'content-to-send': text,
+      'content-to-receive' : text
+    })
+    self.writePromise(self.promise_name, content)
+    self.configureLauncher()
+    self.launcher.run()
+    result = self.getPromiseResult(self.promise_name)
+    self.assertEqual(result['result']['failed'], False)
+    self.assertEqual(
+      result['result']['message'],
+      ("Correctly received %r from %r" % (text, url))
+    )
+
+  def test_check_read_binary(self):
+    text = os.urandom(100)
+    url = self.WS_ENDPOINT + 'OK'
+    content = self.make_content({
+      'url': url,
+      'content-to-send': text,
+      'content-to-receive' : text
+    })
+    self.writePromise(self.promise_name, content)
+    self.configureLauncher()
+    self.launcher.run()
+    result = self.getPromiseResult(self.promise_name)
+    self.assertEqual(result['result']['failed'], False)
+    self.assertEqual(
+      result['result']['message'],
+      ("Correctly received %r from %r" % (text, url))
+    )
+
+  def test_check_bad_read(self):
+    text = ''.join(random.choice(string.ascii_letters) for i in range(10))
+    url = self.WS_ENDPOINT + 'NOK'
+    content = self.make_content({
+      'url': url,
+      'content-to-send': text,
+      'content-to-receive' : text
+    })
+    self.writePromise(self.promise_name, content)
+    self.configureLauncher()
+    with self.assertRaises(PromiseError):
+      self.launcher.run()
+    result = self.getPromiseResult(self.promise_name)
+    self.assertEqual(result['result']['failed'], True)
+    self.assertEqual(
+      result['result']['message'],
+      ("ERROR received 'bad' instead of %r" % text)
+    )
+
+  def test_check_timeout(self):
+    url = self.WS_ENDPOINT + 'OK_5'
+    content = self.make_content({
+      'url': url,
+      'timeout': 1,
+      # use content to send/receceive so that promise will try to read from websocket
+      # otherwise, we can't test the timeout
+      'content-to-send': "a",
+      'content-to-receive' : "a",
+    })
+    self.writePromise(self.promise_name, content)
+    self.configureLauncher()
+    with self.assertRaises(PromiseError):
+      self.launcher.run()
+    result = self.getPromiseResult(self.promise_name)
+    self.assertEqual(result['result']['failed'], True)
+    self.assertEqual(
+      result['result']['message'],
+      "Error: Promise timed out after 0.5 seconds",
+    )
+
+if __name__ == '__main__':
+  unittest.main()