diff --git a/Event/AuthenticationFailureEvent.php b/Event/AuthenticationFailureEvent.php
index 4e0cc9a0..88bcf8ae 100644
--- a/Event/AuthenticationFailureEvent.php
+++ b/Event/AuthenticationFailureEvent.php
@@ -11,6 +11,7 @@
  * AuthenticationFailureEvent
  *
  * @author Emmanuel Vella <vella.emmanuel@gmail.com>
+ * @author Robin Chalas   <robin.chalas@gmail.com>
  */
 class AuthenticationFailureEvent extends Event
 {
@@ -36,9 +37,9 @@ class AuthenticationFailureEvent extends Event
      */
     public function __construct(Request $request, AuthenticationException $exception, Response $response)
     {
-        $this->request = $request;
+        $this->request   = $request;
         $this->exception = $exception;
-        $this->response = $response;
+        $this->response  = $response;
     }
 
     /**
@@ -64,4 +65,12 @@ public function getResponse()
     {
         return $this->response;
     }
+
+    /**
+     * @param Response $response
+     */
+    public function setResponse(Response $response)
+    {
+        $this->response = $response;
+    }
 }
diff --git a/Event/JWTAuthenticatedEvent.php b/Event/JWTAuthenticatedEvent.php
index a93a7121..6f782edc 100644
--- a/Event/JWTAuthenticatedEvent.php
+++ b/Event/JWTAuthenticatedEvent.php
@@ -8,7 +8,7 @@
 use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
 
 /**
- * JWTAuthenticatedEvent
+ * JWTCreatedEvent
  */
 class JWTAuthenticatedEvent extends Event
 {
diff --git a/Event/JWTInvalidEvent.php b/Event/JWTInvalidEvent.php
new file mode 100644
index 00000000..fc9f6651
--- /dev/null
+++ b/Event/JWTInvalidEvent.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
+
+/**
+ * JWTInvalidEvent
+ *
+ * @author Robin Chalas <robin.chalas@gmail.com>
+ */
+class JWTInvalidEvent extends AuthenticationFailureEvent
+{
+}
diff --git a/Events.php b/Events.php
index 641c4e0c..a10f4209 100644
--- a/Events.php
+++ b/Events.php
@@ -16,7 +16,8 @@ final class Events
     const AUTHENTICATION_SUCCESS = 'lexik_jwt_authentication.on_authentication_success';
 
     /**
-     * Dispatched after an authentication failure
+     * Dispatched after an authentication failure.
+     * Hook into this event to add a custom error message in the response body.
      */
     const AUTHENTICATION_FAILURE = 'lexik_jwt_authentication.on_authentication_failure';
 
@@ -43,4 +44,10 @@ final class Events
      * Hook into this event to perform additional modification to the authenticated token using the payload.
      */
     const JWT_AUTHENTICATED = 'lexik_jwt_authentication.on_jwt_authenticated';
+
+    /**
+     * Dispatched after the token has been invalidated by the provider.
+     * Hook into this event to add a custom error message in the response body.
+     */
+    const JWT_INVALID = 'lexik_jwt_authentication.on_jwt_invalid';
 }
diff --git a/Resources/config/services.xml b/Resources/config/services.xml
index e703a0c5..28d3233d 100644
--- a/Resources/config/services.xml
+++ b/Resources/config/services.xml
@@ -62,6 +62,9 @@
             <argument /> <!-- security.token_storage or security.context for Symfony <2.6 -->
             <argument type="service" id="security.authentication.manager" />
             <argument /> <!-- Options -->
+            <call method="setDispatcher">
+                <argument type="service" id="event_dispatcher"/>
+            </call>
         </service>
         <!-- Authorization Header Token Extractor -->
         <service id="lexik_jwt_authentication.extractor.authorization_header_extractor" class="%lexik_jwt_authentication.extractor.authorization_header_extractor.class%" public="false">
diff --git a/Resources/doc/2-data-customization.md b/Resources/doc/2-data-customization.md
index 9eb457a5..2daf6f96 100644
--- a/Resources/doc/2-data-customization.md
+++ b/Resources/doc/2-data-customization.md
@@ -178,6 +178,7 @@ public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $even
     $event->setData($data);
 }
 ```
+
 #### Events::JWT_ENCODED - get JWT string
 
 You may need to get JWT after its creation.
@@ -194,4 +195,72 @@ public function onJwtEncoded(JWTEncodedEvent $event)
 {
     $token = $event->getJWTString();
 }
-```
\ No newline at end of file
+```
+
+#### Events::AUTHENTICATION_FAILURE - customize the failure response
+
+By default, the response in case of failed authentication is just a json containing a "Bad credentials" message and a 401 status code, but you can set a custom response.
+
+``` yaml
+# services.yml
+services:
+    acme_api.event.authentication_failure_listener:
+        class: Acme\Bundle\ApiBundle\EventListener\AuthenticationFailureListener
+        tags:
+            - { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_failure, method: onAuthenticationFailureResponse }
+```
+
+Example 7: set a custom response on authentication failure
+
+``` php
+// Acme\Bundle\ApiBundle\EventListener\AuthenticationFailureListener.php
+/**
+ * @param AuthenticationFailureEvent $event
+ */
+public function onAuthenticationFailureResponse(AuthenticationFailureEvent $event)
+{
+    $data = [
+        'status'  => '401 Unauthorized',
+        'message' => 'Bad credentials, please verify that your username/password are correctly set',
+    ];
+
+    $response = new JsonResponse($data, 401);
+
+    $event->setResponse($response);
+}
+```
+
+#### Events::JWT_INVALID - customize the invalid token response
+
+By default, if the token is invalid or not set, the response is just a json containing the corresponding error message and a 401 status code, but you can set a custom response.
+
+``` yaml
+# services.yml
+services:
+    acme_api.event.jwt_invalid_listener:
+        class: Acme\Bundle\ApiBundle\EventListener\JWTInvalidListener
+        tags:
+            - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_invalid, method: onJWTInvalid }
+```
+
+Example 8: set a custom response message on invalid token
+
+``` php
+// Acme\Bundle\ApiBundle\EventListener\JWTInvalidListener.php
+/**
+ * @param JWTInvalidEvent $event
+ */
+public function onJWTInvalid(JWTInvalidEvent $event)
+{
+    $data = [
+        'status'  => '403 Forbidden',
+        'message' => 'Your token is invalid, please login again to get a new one',
+    ];
+
+    $response = new JsonResponse($data, 403);
+
+    $event->setResponse($response);
+}
+```
+
+__Note:__ This feature is not available if the `throw_exceptions` firewall option is set to `true`.
diff --git a/Security/Firewall/JWTListener.php b/Security/Firewall/JWTListener.php
index 621f0088..fad1656f 100644
--- a/Security/Firewall/JWTListener.php
+++ b/Security/Firewall/JWTListener.php
@@ -4,20 +4,24 @@
 
 use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface;
 use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
+use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent;
+use Lexik\Bundle\JWTAuthenticationBundle\Events;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
 use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
 use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
 use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
 use Symfony\Component\Security\Core\SecurityContextInterface;
 use Symfony\Component\Security\Http\Firewall\ListenerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * JWTListener
  *
  * @author Nicolas Cabot <n.cabot@lexik.fr>
- * @author Robin Chalas <robin.chalas@gmail.com>
+ * @author Robin Chalas  <robin.chalas@gmail.com>
  */
 class JWTListener implements ListenerInterface
 {
@@ -31,6 +35,11 @@ class JWTListener implements ListenerInterface
      */
     protected $authenticationManager;
 
+    /**
+     * @var EventDispatcherInterface
+     */
+    protected $dispatcher;
+
     /**
      * @var array
      */
@@ -46,8 +55,7 @@ class JWTListener implements ListenerInterface
      * @param AuthenticationManagerInterface                 $authenticationManager
      * @param array                                          $config
      */
-    public function __construct($tokenStorage, AuthenticationManagerInterface $authenticationManager, array $config = []
-    )
+    public function __construct($tokenStorage, AuthenticationManagerInterface $authenticationManager, array $config = [])
     {
         if (!$tokenStorage instanceof TokenStorageInterface && !$tokenStorage instanceof SecurityContextInterface) {
             throw new \InvalidArgumentException('Argument 1 should be an instance of Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface or Symfony\Component\Security\Core\SecurityContextInterface');
@@ -64,15 +72,15 @@ public function __construct($tokenStorage, AuthenticationManagerInterface $authe
      */
     public function handle(GetResponseEvent $event)
     {
-        if (!($requestToken = $this->getRequestToken($event->getRequest()))) {
-            return;
-        }
-
-        $token = new JWTUserToken();
-        $token->setRawToken($requestToken);
+        $request = $event->getRequest();
 
         try {
 
+            $requestToken = $this->getRequestToken($request);
+
+            $token = new JWTUserToken();
+            $token->setRawToken($requestToken);
+
             $authToken = $this->authenticationManager->authenticate($token);
             $this->tokenStorage->setToken($authToken);
 
@@ -80,21 +88,22 @@ public function handle(GetResponseEvent $event)
 
         } catch (AuthenticationException $failed) {
 
-            $statusCode = 401;
-
             if ($this->config['throw_exceptions']) {
                 throw $failed;
             }
 
             $data = [
-                'code'    => $statusCode,
+                'code'    => 401,
                 'message' => $failed->getMessage(),
             ];
 
-            $response = new JsonResponse($data, $statusCode);
+            $response = new JsonResponse($data, $data['code']);
             $response->headers->set('WWW-Authenticate', 'Bearer');
 
-            $event->setResponse($response);
+            $jwtInvalidEvent = new JWTInvalidEvent($request, $failed, $response);
+            $this->dispatcher->dispatch(Events::JWT_INVALID, $jwtInvalidEvent);
+
+            $event->setResponse($jwtInvalidEvent->getResponse());
         }
     }
 
@@ -106,6 +115,14 @@ public function addTokenExtractor(TokenExtractorInterface $extractor)
         $this->tokenExtractors[] = $extractor;
     }
 
+    /**
+     * @param EventDispatcherInterface $dispatcher
+     */
+    public function setDispatcher(EventDispatcherInterface $dispatcher)
+    {
+        $this->dispatcher = $dispatcher;
+    }
+
     /**
      * @param Request $request
      *
@@ -120,6 +137,6 @@ protected function getRequestToken(Request $request)
             }
         }
 
-        return false;
+        throw new AuthenticationCredentialsNotFoundException('No JWT token found');
     }
 }
diff --git a/Tests/Security/Authentication/Firewall/JWTListenerTest.php b/Tests/Security/Authentication/Firewall/JWTListenerTest.php
index 9a92e0cb..2fc4d674 100644
--- a/Tests/Security/Authentication/Firewall/JWTListenerTest.php
+++ b/Tests/Security/Authentication/Firewall/JWTListenerTest.php
@@ -20,11 +20,13 @@ public function testHandle()
         // no token extractor : should return void
 
         $listener = new JWTListener($this->getTokenStorageMock(), $this->getAuthenticationManagerMock());
+        $listener->setDispatcher($this->getEventDispatcherMock());
         $this->assertNull($listener->handle($this->getEvent()));
 
         // one token extractor with no result : should return void
 
         $listener = new JWTListener($this->getTokenStorageMock(), $this->getAuthenticationManagerMock());
+        $listener->setDispatcher($this->getEventDispatcherMock());
         $listener->addTokenExtractor($this->getAuthorizationHeaderTokenExtractorMock(false));
         $this->assertNull($listener->handle($this->getEvent()));
 
@@ -34,6 +36,7 @@ public function testHandle()
         $authenticationManager->expects($this->once())->method('authenticate');
 
         $listener = new JWTListener($this->getTokenStorageMock(), $authenticationManager);
+        $listener->setDispatcher($this->getEventDispatcherMock());
         $listener->addTokenExtractor($this->getAuthorizationHeaderTokenExtractorMock('token'));
         $listener->handle($this->getEvent());
 
@@ -50,6 +53,7 @@ public function testHandle()
             ->will($this->throwException($invalidTokenException));
 
         $listener = new JWTListener($this->getTokenStorageMock(), $authenticationManager);
+        $listener->setDispatcher($this->getEventDispatcherMock());
         $listener->addTokenExtractor($this->getAuthorizationHeaderTokenExtractorMock('token'));
 
         $event = $this->getEvent();
@@ -134,4 +138,14 @@ protected function getEvent()
 
         return $event;
     }
+
+    /**
+     * @return \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected function getEventDispatcherMock()
+    {
+        return $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcher')
+            ->disableOriginalConstructor()
+            ->getMock();
+    }
 }