diff --git a/postgres-websockets.cabal b/postgres-websockets.cabal
--- a/postgres-websockets.cabal
+++ b/postgres-websockets.cabal
@@ -1,6 +1,6 @@
 cabal-version:       3.0
 name:                postgres-websockets
-version:             0.11.2.5
+version:             0.12.0.0
 synopsis:            Middleware to map LISTEN/NOTIFY messages to Websockets
 description:         WAI middleware that adds websockets capabilites on top of PostgreSQL's asynchronous notifications using LISTEN and NOTIFY commands. Fully functioning server included.
 homepage:            https://github.com/diogob/postgres-websockets#readme
@@ -48,7 +48,7 @@
                      , hasql-notifications >= 0.2.4.0 && < 0.3
                      , hasql-pool ^>= 1.3
                      , http-types >= 0.12.3 && < 0.13
-                     , jose >= 0.11 && < 0.12
+                     , jose >= 0.11 && < 0.13
                      , lens >= 5.2.3 && < 5.4
                      , mtl >=2.3.1 && <2.4
                      , async >=2.2.5 && <2.3
diff --git a/src/PostgresWebsockets/Claims.hs b/src/PostgresWebsockets/Claims.hs
--- a/src/PostgresWebsockets/Claims.hs
+++ b/src/PostgresWebsockets/Claims.hs
@@ -35,7 +35,7 @@
   UTCTime ->
   IO (Either Text ConnectionInfo)
 validateClaims requestChannel secret jwtToken time = runExceptT $ do
-  cl <- liftIO $ jwtClaims time (parseJWK secret) jwtToken
+  cl <- liftIO $ jwtClaims time (parseSecret secret) jwtToken
   cl' <- case cl of
     JWTClaims c -> pure c
     JWTInvalid JWTExpired -> throwError "Token expired"
@@ -82,13 +82,13 @@
 -- |
 --  Receives the JWT secret (from config) and a JWT and returns a map
 --  of JWT claims.
-jwtClaims :: UTCTime -> JWK -> LByteString -> IO JWTAttempt
+jwtClaims :: UTCTime -> JWKSet -> LByteString -> IO JWTAttempt
 jwtClaims _ _ "" = return $ JWTClaims JSON.empty
-jwtClaims time jwk' payload = do
+jwtClaims time jwks payload = do
   let config = defaultJWTValidationSettings (const True)
   eJwt <- runExceptT $ do
     jwt <- decodeCompact payload
-    verifyClaimsAt config jwk' time jwt
+    verifyClaimsAt config jwks time (jwt :: SignedJWT)
   return $ case eJwt of
     Left e -> JWTInvalid e
     Right jwt -> JWTClaims . claims2map $ jwt
@@ -114,6 +114,8 @@
   where
     km = OctKeyMaterial (OctKeyParameters (JOSE.Types.Base64Octets key))
 
-parseJWK :: ByteString -> JWK
-parseJWK str =
-  fromMaybe (hs256jwk str) (JSON.decode (fromStrict str) :: Maybe JWK)
+parseSecret :: ByteString -> JWKSet
+parseSecret str =
+  case JSON.decode (fromStrict str) :: Maybe JWKSet of
+    Just jwks -> jwks
+    Nothing -> JWKSet [fromMaybe (hs256jwk str) (JSON.decode (fromStrict str) :: Maybe JWK)]
diff --git a/test/ClaimsSpec.hs b/test/ClaimsSpec.hs
--- a/test/ClaimsSpec.hs
+++ b/test/ClaimsSpec.hs
@@ -11,6 +11,14 @@
 secret :: ByteString
 secret = "reallyreallyreallyreallyverysafe"
 
+-- Same secret as a single JWK object
+jwkSecret :: ByteString
+jwkSecret = "{\"kty\":\"oct\",\"k\":\"cmVhbGx5cmVhbGx5cmVhbGx5cmVhbGx5dmVyeXNhZmU\",\"alg\":\"HS256\"}"
+
+-- Same secret wrapped in a JWKS envelope
+jwksSecret :: ByteString
+jwksSecret = "{\"keys\":[{\"kty\":\"oct\",\"k\":\"cmVhbGx5cmVhbGx5cmVhbGx5cmVhbGx5dmVyeXNhZmU\",\"alg\":\"HS256\"}]}"
+
 spec :: Spec
 spec =
   describe "validate claims" $ do
@@ -73,3 +81,34 @@
         "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjaGFubmVscyI6WyJ0ZXN0IiwidGVzdDIiXX0.akC1PEYk2DEZtLP2XjC6qXOGZJejmPx49qv-VeEtQYQ"
         time
         `shouldReturn` Left "Missing mode"
+
+    it "should validate a token using a JWK secret" $ do
+      time <- getCurrentTime
+      validateClaims
+        (Just "test")
+        jwkSecret
+        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtb2RlIjoiciIsImNoYW5uZWwiOiJ0ZXN0In0.1d4s-at2kWj8OSabHZHTbNh1dENF7NWy_r0ED3Rwf58"
+        time
+        `shouldReturn` Right (["test"], "r", JSON.fromList [("mode", String "r"), ("channel", String "test")])
+
+    it "should validate a token using a JWKS secret" $ do
+      time <- getCurrentTime
+      validateClaims
+        (Just "test")
+        jwksSecret
+        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtb2RlIjoiciIsImNoYW5uZWwiOiJ0ZXN0In0.1d4s-at2kWj8OSabHZHTbNh1dENF7NWy_r0ED3Rwf58"
+        time
+        `shouldReturn` Right (["test"], "r", JSON.fromList [("mode", String "r"), ("channel", String "test")])
+
+    it "should reject a token with wrong key in JWKS" $ do
+      time <- getCurrentTime
+      result <-
+        validateClaims
+          (Just "test")
+          "{\"keys\":[{\"kty\":\"oct\",\"k\":\"d3JvbmdrZXl3cm9uZ2tleXdyb25na2V5d3Jvbmdr\",\"alg\":\"HS256\"}]}"
+          "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtb2RlIjoiciIsImNoYW5uZWwiOiJ0ZXN0In0.1d4s-at2kWj8OSabHZHTbNh1dENF7NWy_r0ED3Rwf58"
+          time
+      result `shouldSatisfy` isLeft
+  where
+    isLeft (Left _) = True
+    isLeft _ = False
