diff --git a/agkyra/__init__.py b/agkyra/__init__.py
index 29e2b7ef88d0d36658d654a2ff2d0b85d1fa8b51..89ad4d917d53f4ff351e28e361ea1871461a9a5b 100644
--- a/agkyra/__init__.py
+++ b/agkyra/__init__.py
@@ -3,72 +3,105 @@ from wsgiref.simple_server import make_server
 from protocol import WebSocketProtocol
 from ws4py.server.wsgirefserver import WSGIServer, WebSocketWSGIRequestHandler
 from ws4py.server.wsgiutils import WebSocketWSGIApplication
+from ws4py.client import WebSocketBaseClient
 from tempfile import NamedTemporaryFile
 import subprocess
 import json
 from os.path import abspath
 from threading import Thread
-from hashlib import sha256
-from os import urandom
+from hashlib import sha1
+import os
+import logging
 
-from ws4py.client import WebSocketBaseClient
+
+LOG = logging.getLogger(__name__)
 
 
-class GUILauncher(WebSocketBaseClient):
+class GUI(WebSocketBaseClient):
     """Launch the GUI when the helper server is ready"""
 
-    def __init__(self, addr, gui_exec_path, token):
+    def __init__(self, addr, gui_exec_path, gui_id):
         """Initialize the GUI Launcher"""
-        super(GUILauncher, self).__init__(addr)
+        super(GUI, self).__init__(addr)
         self.addr = addr
         self.gui_exec_path = gui_exec_path
-        self.token = token
+        self.gui_id = gui_id
+        self.start = self.connect
+
+    def run_gui(self):
+        """Launch the GUI and keep it running, clean up afterwards.
+        If the GUI is terminated for some reason, the WebSocket is closed and
+        the temporary file with GUI settings is deleted.
+        In windows, the file must be closed before the GUI is launched.
+        """
+        # NamedTemporaryFile creates a file accessible only to current user
+        LOG.debug('Create temporary file')
+        with NamedTemporaryFile(delete=False) as fp:
+            json.dump(dict(gui_id=self.gui_id, address=self.addr), fp)
+        # subprocess.call blocks the execution
+        LOG.debug('RUN: %s %s' % (self.gui_exec_path, fp.name))
+        subprocess.call([
+            '/home/saxtouri/node-webkit-v0.11.6-linux-x64/nw',
+            # self.gui_exec_path,
+            abspath('gui/gui.nw'),
+            fp.name])
+        LOG.debug('GUI process closed, remove temp file')
+        os.remove(fp.name)
 
     def handshake_ok(self):
-        """If handshake is OK, the helper is UP, so the GUI can be launched
-        If the GUI is terminated for some reason, the WebSocket is closed"""
-        with NamedTemporaryFile(mode='a+') as fp:
-            json.dump(dict(token=self.token, address=self.addr), fp)
-            fp.flush()
-            # subprocess.call blocks the execution
-            subprocess.call([
-                '/home/saxtouri/node-webkit-v0.11.6-linux-x64/nw',
-                abspath('gui/gui.nw'),
-                fp.name])
+        """If handshake is OK, the helper is UP, so the GUI can be launched"""
+        self.run_gui()
+        LOG.debug('Close GUI wrapper connection')
         self.close()
 
 
-def setup_server(token, port=0):
-    """Setup and return the helper server"""
-    WebSocketProtocol.token = token
-    server = make_server(
-        '', port,
-        server_class=WSGIServer,
-        handler_class=WebSocketWSGIRequestHandler,
-        app=WebSocketWSGIApplication(handler_cls=WebSocketProtocol))
-    server.initialize_websockets_manager()
-    # self.port = server.server_port
-    return server
+class HelperServer(object):
+    """Agkyra Helper Server sets a WebSocket server with the Helper protocol
+    It also provided methods for running and killing the Helper server
+    :param gui_id: Only the GUI with this ID is allowed to chat with the Helper
+    """
+
+    def __init__(self, port=0):
+        """Setup the helper server"""
+        self.gui_id = sha1(os.urandom(128)).hexdigest()
+        WebSocketProtocol.gui_id = self.gui_id
+        server = make_server(
+            '', port,
+            server_class=WSGIServer,
+            handler_class=WebSocketWSGIRequestHandler,
+            app=WebSocketWSGIApplication(handler_cls=WebSocketProtocol))
+        server.initialize_websockets_manager()
+        self.server, self.port = server, server.server_port
 
+    def start(self):
+        """Start the helper server in a thread"""
+        Thread(target=self.server.serve_forever).start()
 
-def random_token():
-    return 'random token'
+    def shutdown(self):
+        """Shutdown the server (needs another thread) and join threads"""
+        t = Thread(target=self.server.shutdown)
+        t.start()
+        t.join()
 
 
 def run(gui_exec_path):
     """Prepare helper and GUI and run them in the proper order"""
-    token = sha256(urandom(256)).hexdigest()
-    server = setup_server(token)
-    addr = 'ws://localhost:%s' % server.server_port
+    server = HelperServer()
+    addr = 'ws://localhost:%s' % server.port
+    gui = GUI(addr, gui_exec_path, server.gui_id)
 
-    gui = GUILauncher(addr, gui_exec_path, token)
-    Thread(target=gui.connect).start()
+    LOG.info('Start helper server')
+    server.start()
 
     try:
-        server.serve_forever()
+        LOG.info('Start GUI')
+        gui.start()
     except KeyboardInterrupt:
-        print 'Shutdown GUI'
+        LOG.info('Shutdown GUI')
         gui.close()
+    LOG.info('Shutdown helper server')
+    server.shutdown()
 
 if __name__ == '__main__':
-    run('/home/saxtouri/node-webkit-v0.11.6-linux-x64/nw gui.nw')
+    logging.basicConfig(filename='agkyra.log', level=logging.DEBUG)
+    run(abspath('gui/app'))
diff --git a/agkyra/gui/protocol.js b/agkyra/gui/protocol.js
index c6a56a361dfa05ef9e4dae4c27fe30efbc88d9d8..16ece231621107c916f7a347d86473125d871e5c 100644
--- a/agkyra/gui/protocol.js
+++ b/agkyra/gui/protocol.js
@@ -3,20 +3,96 @@ var gui = require('nw.gui');
 // Read config file
 var fs = require('fs');
 var cnf = JSON.parse(fs.readFileSync(gui.App.argv[0], encoding='utf-8'));
+fs.writeFile(gui.App.argv[0], 'consumed');
+
+function send_json(socket, msg) {
+  socket.send(JSON.stringify(msg))
+}
+
+var requests = []
+var globals = {
+  'settings': {
+    'token': null,
+    'url': null,
+    'container': null,
+    'directory': null,
+    'exclude': null
+  },
+  'status': {"progress": null, "paused": null}
+}
+
+// Protocol: requests ::: responses
+function post_gui_id(socket) {
+  requests.push('post gui_id');
+  send_json(socket, {"method": "post", "gui_id": cnf['gui_id']})
+} // expected response: {"ACCEPTED": 202}
+
+function post_shutdown(socket) {
+  send_json(socket, {'method': 'post', 'path': 'shutdown'});
+} // expected response: nothing
+
+function get_settings(socket) {
+  requests.push('get settings');
+  send_json(socket, {'method': 'get', 'path': 'settings'});
+} // expected response: {settings JSON}
+
+function put_settings(socket, new_settings) {
+  requests.push('put settings');
+  new_settings['method'] = 'put';
+  new_settings['path'] = 'settings';
+  send_json(socket, new_settings);
+} // expected response: {"CREATED": 201}
+
+function get_status(socket) {
+  requests.push('get status');
+  send_json(socket, {'method': 'get', 'path': 'status'});
+} // expected response {"progress": ..., "paused": ...}
+
 
 // Connect to helper
 var socket = new WebSocket(cnf['address']);
 socket.onopen = function() {
-  console.log('Connecting to helper');
-  this.send(cnf['token']);
+  console.log('Send GUI ID to helper');
+  post_gui_id(this);
 }
 socket.onmessage = function(e) {
-  console.log('message', e.data);
+  var r = JSON.parse(e.data)
+  switch(requests.shift()) {
+    case 'post gui_id':
+      if (r['ACCEPTED'] == 202) {
+        get_settings(this);
+        get_status(this);
+      } else {
+        console.log('Helper: ' + JSON.stringify(r));
+        closeWindows();
+      }
+    break;
+    case 'get settings':
+      console.log(r);
+      globals['settings'] = r;
+    break;
+    case 'put settings':
+      if (r['CREATED'] == 201) {
+        get_settings(socket);
+      } else {
+        console.log('Helper: ' + JSON.stringify(r));
+      }
+    break;
+    case 'get status': globals['status'] = r;
+    break;
+    default:
+      console.log('Incomprehensible response ' + r);
+  }
+
 };
-socket.onerror = function () {
-    console.log('GUI and helper cannot communicate, quiting');
+socket.onerror = function (e) {
+    console.log('GUI - helper error' + e.data);
     gui.Window.get().close();
 }
+socket.onclose = function() {
+    console.log('Connection to helper closed');
+    closeWindows();
+}
 
 // Setup GUI
 var windows = {
@@ -30,19 +106,37 @@ function closeWindows() {
 
 // GUI components
 var tray = new gui.Tray({
-  tooltip: 'Paused (0% synced)',
+  // tooltip: 'Paused (0% synced)',
   title: 'Agkyra syncs with Pithos+',
   icon: 'icons/tray.png'
 });
 
 var menu = new gui.Menu();
 
+
+progress_menu = new gui.MenuItem({
+  label: 'Calculating status',
+  type: 'normal',
+});
+menu.append(progress_menu);
+window.setInterval(function() {
+  var status = globals['status']
+  var msg = 'Syncing'
+  if (status['paused']) msg = 'Paused'
+  progress_menu.label = msg + ' (' + status['progress'] + '%)';
+  tray.menu = menu;
+  get_status(socket);
+}, 5000);
+
+
 // See contents
 menu.append(new gui.MenuItem({type: 'separator'}));
 menu.append(new gui.MenuItem({
   label: 'Open local folder',
   icon: 'icons/folder.png',
-  click: function () {gui.Shell.showItemInFolder('.');}
+  click: function () {
+    gui.Shell.showItemInFolder('.');
+  }
 }));
 
 menu.append(new gui.MenuItem({
@@ -86,11 +180,7 @@ menu.append(new gui.MenuItem({type: 'separator'}));
 menu.append(new gui.MenuItem({
   label: 'Quit Agkyra',
   icon: 'icons/exit.png',
-  click: function () {
-    console.log('Exiting client');
-    console.log('Exiting GUI');
-    closeWindows()
-  }
+  click: function() {post_shutdown(socket);}
 }));
 
 tray.menu = menu;
diff --git a/agkyra/protocol.py b/agkyra/protocol.py
index 5454b449f0ed8ee13ff0c92ae071c68260dc3a30..c9829fdcabd89cb214e9a8bea91a0bc82b4ac8aa 100644
--- a/agkyra/protocol.py
+++ b/agkyra/protocol.py
@@ -1,12 +1,20 @@
 from ws4py.websocket import WebSocket
+import json
+import logging
+
+
+LOG = logging.getLogger(__name__)
 
 
 class WebSocketProtocol(WebSocket):
     """Helper-side WebSocket protocol for communication with GUI:
 
     -- INTERRNAL HANDSAKE --
-    GUI: {"token": <token>}
-    HELPER: {"ACCEPTED": 202}" or "{"ERROR": 401, "MESSAGE": <message>}
+    GUI: {"method": "post", "gui_id": <GUI ID>}
+    HELPER: {"ACCEPTED": 202}" or "{"REJECTED": 401}
+
+    -- SHUT DOWN --
+    GUI: {"method": "post", "path": "shutdown"}
 
     -- GET SETTINGS --
     GUI: {"method": "get", "path": "settings"}
@@ -17,7 +25,7 @@ class WebSocketProtocol(WebSocket):
             "container": <container>,
             "directory": <local directory>,
             "exclude": <file path>
-        } or {"ERROR": <error code>, "MESSAGE": <message>}"
+        } or {<ERROR>: <ERROR CODE>}
 
     -- PUT SETTINGS --
     GUI: {
@@ -28,22 +36,94 @@ class WebSocketProtocol(WebSocket):
             "directory": <local directory>,
             "exclude": <file path>
         }
-    HELPER: {"CREATED": 201} or {"ERROR": <error code>, "MESSAGE": <message>}
+    HELPER: {"CREATED": 201} or {<ERROR>: <ERROR CODE>}
 
     -- GET STATUS --
     GUI: {"method": "get", "path": "status"}
-    HELPER: ""progres": <int>, "paused": <boolean>} or
-        {"ERROR": <error code>, "MESSAGE": <message>}
+    HELPER: ""progress": <int>, "paused": <boolean>} or {<ERROR>: <ERROR CODE>}
     """
 
-    token = None
+    gui_id = None
+    accepted = False
+
+    # Syncer-related methods
+    def get_status(self):
+        self.progress = getattr(self, 'progress', -1)
+        self.progress += 1
+        return dict(progress=self.progress, paused=False)
+
+    def get_settings(self):
+        return dict(
+            token='token',
+            url='http://www.google.com',
+            container='pithos',
+            directory='~/tmp',
+            exclude='agkyra.log')
 
+    # WebSocket connection methods
     def opened(self):
-        print 'A connection is open', self.token
+        LOG.debug('Helper: connection established')
 
     def closed(self, *args):
-        print 'A connection is closed', args
+        LOG.debug('Helper: connection closed')
+
+    def send_json(self, msg):
+        LOG.debug('send: %s' % msg)
+        self.send(json.dumps(msg))
+
+    # Protocol handling methods
+    def _post(self, r):
+        """Handle POST requests"""
+        if self.accepted:
+            if r['path'] == 'shutdown':
+                self.close()
+            raise KeyError()
+        elif r['gui_id'] == self.gui_id:
+            self.accepted = True
+            self.send_json({'ACCEPTED': 202})
+        else:
+            self.send_json({'REJECTED': 401})
+            self.terminate()
+
+    def _put(self, r):
+        """Handle PUT requests"""
+        if not self.accepted:
+            self.send_json({'UNAUTHORIZED': 401})
+            self.terminate()
+        else:
+            LOG.debug('put %s' % r)
+
+    def _get(self, r):
+        """Handle GET requests"""
+        if not self.accepted:
+            self.send_json({'UNAUTHORIZED': 401})
+            self.terminate()
+        else:
+            data = {
+                'settings': self.get_settings,
+                'status': self.get_status,
+            }[r.pop('path')]()
+            self.send_json(data)
 
     def received_message(self, message):
-        print 'Got message', message
-        self.send(message)
+        """Route requests to corresponding handling methods"""
+        LOG.debug('recv: %s' % message)
+        try:
+            r = json.loads('%s' % message)
+        except ValueError as ve:
+            self.send_json({'BAD REQUEST': 400})
+            LOG.error('JSON ERROR: %s' % ve)
+            return
+        try:
+            {
+                'post': self._post,
+                'put': self._put,
+                'get': self._get
+            }[r.pop('method')](r)
+        except KeyError as ke:
+            self.send_json({'BAD REQUEST': 400})
+            LOG.error('KEY ERROR: %s' % ke)
+        except Exception as e:
+            self.send_json({'INTERNAL ERROR': 500})
+            LOG.error('EXCEPTION: %s' % e)
+            self.terminate()