diff --git a/src/Web/TwitchAPI/Helix/Users.hs b/src/Web/TwitchAPI/Helix/Users.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/TwitchAPI/Helix/Users.hs
@@ -0,0 +1,305 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE RecordWildCards       #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+
+module Web.TwitchAPI.Helix.Users where
+
+import Prelude
+
+import Data.Functor  ( (<&>) )
+
+import qualified Data.ByteString.Char8 as BS
+import qualified Data.Time             as Time
+import qualified Data.Time.RFC3339     as Time ( parseTimeRFC3339 )
+import qualified Network.HTTP.Client   as HTTP
+
+import Data.Aeson        ( FromJSON(..), (.:), withObject )
+import Data.Aeson.KeyMap ( toAscList )
+
+import qualified Web.TwitchAPI.Helix.Request as Req
+
+-- BUG: Not yet implemented:
+--   - Update User
+--   - Block/Unblock User
+--   - Update User Extensions
+
+data User = User { lookupID :: Maybe String
+                 , username :: Maybe String
+                 } deriving ( Show )
+
+instance Req.HelixRequest User where
+    toRequest user =
+        let lookupID' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\i -> [("id", Just . BS.pack . show $ i)]) (lookupID user)
+            username' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\u -> [("login", Just . BS.pack $ u)]) (username user)
+            setQuery = HTTP.setQueryString $ lookupID' ++ username'
+        in setQuery $ HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users"
+    scope User{} = Just "user:read:email"
+
+data BroadcasterType = Partner | Affiliate | None deriving ( Eq, Show )
+
+instance Read BroadcasterType where
+    readsPrec _ "partner"   = [(Partner, "")]
+    readsPrec _ "affiliate" = [(Affiliate, "")]
+    readsPrec _ _           = [(None, "")]
+
+data UserType = Staff | Admin | GlobalMod | NormalUser deriving ( Eq, Show )
+
+instance Read UserType where
+    readsPrec _ "staff"      = [(Staff, "")]
+    readsPrec _ "admin"      = [(Admin, "")]
+    readsPrec _ "global_mod" = [(GlobalMod, "")]
+    readsPrec _ _            = [(NormalUser, "")]
+
+data UserEntry = UserEntry { broadcasterType :: BroadcasterType
+                           , description     :: String
+                           , displayName     :: String
+                           , userId          :: Integer
+                           , login           :: String
+                           , offlineImageURL :: String
+                           , profileImageURL :: String
+                           , userType        :: UserType
+                           , email           :: Maybe String
+                           , createdAt       :: Maybe Time.UTCTime
+                           } deriving ( Show, Eq )
+
+instance FromJSON UserEntry where
+    parseJSON = withObject "UserEntry" $ \o -> do
+        userId' :: String <- o .: "id"
+        created :: String <- o .: "created_at"
+        userType' :: String <- o .: "type"
+        broadcasterType' :: String <- o .: "broadcaster_type"
+        let userId = read userId' :: Integer
+            createdAt = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 created
+            userType = read userType' :: UserType
+            broadcasterType = read broadcasterType' :: BroadcasterType
+
+        description <- o .: "description"
+        displayName <- o .: "display_name"
+        login <- o .: "login"
+        offlineImageURL <- o .: "offline_image_url"
+        profileImageURL <- o .: "profile_image_url"
+        email <- o .: "email"
+        return UserEntry{..}
+
+data Users = Users { lookupIDs :: [Integer]
+                   , usernames :: [String]
+                   } deriving ( Show, Eq )
+
+instance Req.HelixRequest Users where
+    toRequest users =
+        let lookupID' :: [(BS.ByteString, Maybe BS.ByteString)] = (\i -> ("id", Just . BS.pack . show $ i)) <$> lookupIDs users
+            username' :: [(BS.ByteString, Maybe BS.ByteString)] = (\u -> ("login", Just . BS.pack $ u)) <$> usernames users
+            setQuery = HTTP.setQueryString $ lookupID' ++ username'
+        in setQuery $ HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users"
+    scope Users{} = Nothing
+
+newtype UsersResponse = UsersResponse { users :: [UserEntry] } deriving ( Eq, Show )
+
+instance FromJSON UsersResponse where
+    parseJSON = withObject "UsersResponse" $ \o -> do
+        users <- o .: "data"
+        return UsersResponse{..}
+
+data Follows = Follows { after :: Maybe String
+                       , max :: Maybe Integer
+                       , from :: Maybe Integer
+                       , to :: Maybe Integer
+                       } deriving ( Show, Eq )
+
+instance Req.HelixRequest Follows where
+    toRequest user =
+        let after' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\a -> [("after", Just . BS.pack $ a)]) (after user)
+            max' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\u -> [("first", Just . BS.pack . show $ u)]) (from user)
+            fromID' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\u -> [("after", Just . BS.pack . show $ u)]) (from user)
+            toID' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\u -> [("after", Just . BS.pack . show $ u)]) (to user)
+            setQuery = HTTP.setQueryString $ concat [after', max', fromID', toID']
+        in setQuery $ HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users/follows"
+    scope Follows{} = Nothing
+
+data FollowEntry = FollowEntry { fromID :: Integer
+                               , fromLogin :: String
+                               , fromName :: String
+                               , toID :: Integer
+                               , toName :: String
+                               , followedAt :: Maybe Time.UTCTime
+                               } deriving ( Show, Eq )
+
+instance FromJSON FollowEntry where
+    parseJSON = withObject "FollowEntry" $ \o -> do
+        fromID' :: String <- o .: "from_id"
+        toID' :: String <- o .: "to_id"
+        followedAt' :: String <- o .: "followed_at"
+        let fromID = read fromID' :: Integer
+            toID = read toID' :: Integer
+            followedAt = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 followedAt'
+        fromLogin <- o .: "from_login"
+        fromName <- o .: "from_name"
+        toName <- o .: "to_name"
+        return FollowEntry{..}
+
+data FollowsResponse = FollowsResponse { total :: Integer
+                                       , users :: [FollowEntry]
+                                       , paginationCursor :: String
+                                       } deriving ( Show, Eq )
+
+instance FromJSON FollowsResponse where
+    parseJSON = withObject "FollowsResponse" $ \o -> do
+        total <- o .: "total"
+        paginationCursor <- (o .: "pagination") >>= (.: "cursor")
+        users <- o .: "data"
+        return FollowsResponse{..}
+
+newtype BlockList = BlockList { broadcasterID :: Integer } deriving ( Show, Eq )
+
+instance Req.HelixRequest BlockList where
+    toRequest user =
+        HTTP.setQueryString [("login", Just . BS.pack . show . broadcasterID $ user)] $
+            HTTP.parseRequest_ "https://api.twitch.tv/helix/users/blocks"
+    scope BlockList{} = Just "user:read:blocked_users"
+
+data BlockListEntry = BlockListEntry { userId :: Integer
+                                     , userLogin :: String
+                                     , displayName :: String
+                                     } deriving ( Show, Eq )
+
+instance FromJSON BlockListEntry where
+    parseJSON = withObject "BlockListEntry" $ \o -> do
+        userId' :: String <- o .: "user_id"
+        let userId = read userId' :: Integer
+        userLogin <- o .: "user_login"
+        displayName <- o .: "display_name"
+        return BlockListEntry{..}
+
+newtype BlockListResponse = BlockListResponse { blocks :: [BlockListEntry] } deriving ( Show, Eq )
+
+instance FromJSON BlockListResponse where
+    parseJSON = withObject "BlockListResponse" $ \o -> do
+        blocks <- o .: "data"
+        return BlockListResponse{..}
+
+data Extensions = Extensions
+
+instance Req.HelixRequest Extensions where
+    toRequest _ = HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users/extensions/list"
+    scope Extensions = Just "user:read:broadcast"
+
+data ExtensionType = Component | Mobile | Panel | Overlay deriving ( Show, Eq )
+
+instance Read ExtensionType where
+    readsPrec _ "component" = [(Component, "")]
+    readsPrec _ "mobile"    = [(Mobile, "")]
+    readsPrec _ "panel"     = [(Panel, "")]
+    readsPrec _ "overlay"   = [(Overlay, "")]
+    readsPrec _ _           = mempty
+
+data ExtensionsEntry = ExtensionsEntry { canActivate :: Bool
+                                       , extensionId :: String
+                                       , name :: String
+                                       , extensionTypes :: [ExtensionType]
+                                       , version :: String
+                                       } deriving ( Show, Eq )
+
+instance FromJSON ExtensionsEntry where
+    parseJSON = withObject "ExtensionsEntry" $ \o -> do
+        extensionTypes' :: [String] <- o .: "type"
+        let extensionTypes :: [ExtensionType] = read <$> extensionTypes'
+        canActivate <- o .: "can_activate"
+        extensionId <- o .: "id"
+        name <- o .: "name"
+        version <- o .: "version"
+        return ExtensionsEntry{..}
+
+newtype ExtensionsResponse = ExtensionsResponse { extensions :: [ExtensionsEntry] } deriving ( Show, Eq )
+
+instance FromJSON ExtensionsResponse where
+    parseJSON = withObject "ExtensionsResponse" $ \o -> do
+        extensions <- o .: "data"
+        return ExtensionsResponse{..}
+
+newtype ActiveExtensions = ActiveExtensions { userID :: Maybe Integer } deriving ( Show, Eq )
+
+instance Req.HelixRequest ActiveExtensions where
+    toRequest user =
+        let userID' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\i -> [("user_id", Just . BS.pack . show $ i)]) (userID user)
+            setQuery = HTTP.setQueryString userID'
+        in setQuery $ HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users/extensions"
+    scope ActiveExtensions{} = Nothing
+
+data ActiveComponentExtensionEntry' = ActiveComponentExtensionEntry' { active :: Bool
+                                                                     , extensionId :: String
+                                                                     , version :: String
+                                                                     , name :: String
+                                                                     , x :: Integer
+                                                                     , y :: Integer
+                                                                     }
+                                    | InactiveComponentExtension deriving ( Show, Eq )
+
+instance FromJSON ActiveComponentExtensionEntry' where
+    parseJSON = withObject "ActiveExtensionEntry" $ \o -> do
+        active <- o .: "active"
+        if active then do
+            extensionId <- o .: "id"
+            version <- o .: "version"
+            name <- o .: "name"
+            x <- o .: "x"
+            y <- o .: "y"
+            return ActiveComponentExtensionEntry'{..}
+        else return InactiveComponentExtension
+
+data ActiveComponentExtensionEntry = ActiveComponentExtensionEntry { active :: Bool
+                                                                   , extensionId :: String
+                                                                   , version :: String
+                                                                   , name :: String
+                                                                   , x :: Integer
+                                                                   , y :: Integer
+                                                                   } deriving ( Show, Eq )
+
+filterActiveComponentExtensions :: [ActiveComponentExtensionEntry'] -> [ActiveComponentExtensionEntry]
+filterActiveComponentExtensions = reverse . foldl filterActiveComponentExtensions' []
+
+filterActiveComponentExtensions' :: [ActiveComponentExtensionEntry] -> ActiveComponentExtensionEntry' -> [ActiveComponentExtensionEntry]
+filterActiveComponentExtensions' as InactiveComponentExtension = as
+filterActiveComponentExtensions' as (ActiveComponentExtensionEntry' _ i v n x y) = ActiveComponentExtensionEntry True i v n x y : as
+
+data ActiveExtensionEntry' = ActiveExtensionEntry' { active :: Bool
+                                                   , extensionId :: String
+                                                   , version :: String
+                                                   , name :: String
+                                                   } 
+                          | InactiveExtension deriving ( Show, Eq )
+
+instance FromJSON ActiveExtensionEntry' where
+    parseJSON = withObject "ActiveExtensionEntry" $ \o -> do
+        active <- o .: "active"
+        if active then do
+            extensionId <- o .: "id"
+            version <- o .: "version"
+            name <- o .: "name"
+            return ActiveExtensionEntry'{..}
+        else return InactiveExtension
+
+data ActiveExtensionEntry = ActiveExtensionEntry { active :: Bool
+                                                 , extensionId :: String
+                                                 , version :: String
+                                                 , name :: String
+                                                 } deriving ( Show, Eq )
+
+filterActiveExtensions :: [ActiveExtensionEntry'] -> [ActiveExtensionEntry]
+filterActiveExtensions = reverse . foldl filterActiveExtensions' []
+
+filterActiveExtensions' :: [ActiveExtensionEntry] -> ActiveExtensionEntry' -> [ActiveExtensionEntry]
+filterActiveExtensions' as InactiveExtension = as
+filterActiveExtensions' as (ActiveExtensionEntry' _ i v n) = ActiveExtensionEntry True i v n : as
+
+data ActiveExtensionsResponse = ActiveExtensionsResponse { components :: [ActiveComponentExtensionEntry]
+                                                         , overlays :: [ActiveExtensionEntry]
+                                                         , panels :: [ActiveExtensionEntry]
+                                                         } deriving ( Show, Eq )
+
+instance FromJSON ActiveExtensionsResponse where
+    parseJSON = withObject "ActiveExtensionsResponse" $ \o -> do
+        components <- (((o .: "data") >>= (.: "component")) <&> ((snd <$>) . toAscList)) <&> filterActiveComponentExtensions
+        overlays <- (((o .: "data") >>= (.: "overlay")) <&> ((snd <$>) . toAscList)) <&> filterActiveExtensions
+        panels <- (((o .: "data") >>= (.: "panel")) <&> ((snd <$>) . toAscList)) <&> filterActiveExtensions
+        return ActiveExtensionsResponse{..}
diff --git a/test/Helix.hs b/test/Helix.hs
new file mode 100644
--- /dev/null
+++ b/test/Helix.hs
@@ -0,0 +1,11 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Main ( main ) where
+
+import qualified Helix.Users
+
+import Test.QuickCheck
+import Test.Hspec
+
+main :: IO ()
+main = Helix.Users.main
diff --git a/test/Helix/Users.hs b/test/Helix/Users.hs
new file mode 100644
--- /dev/null
+++ b/test/Helix/Users.hs
@@ -0,0 +1,203 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Helix.Users ( main ) where
+
+import qualified Data.Aeson            as JSON
+import qualified Data.ByteString.Lazy  as BS
+import qualified Data.List             as List
+import qualified Data.Text             as T
+import qualified Data.Text.Encoding    as T
+import qualified Data.Time             as Time
+import qualified Data.Time.RFC3339     as Time ( parseTimeRFC3339 )
+import qualified Data.Time.Clock.POSIX as Time ( POSIXTime, posixSecondsToUTCTime )
+
+import qualified Web.TwitchAPI.Helix.Users as Users
+
+import Data.Char   ( isDigit, isLetter )
+import Text.Printf ( printf )
+
+import Test.QuickCheck
+import Test.Hspec
+
+main :: IO ()
+main = do
+    hspec $ describe "Users Interactions (Twitch Fixtures)" $ do
+        it "Parses Twitch's Single-User JSON Fixture" $ property prop_userJSONFixture
+        it "Parses Twitch's Multi-User JSON Fixture" $ property prop_usersJSONFixture
+        it "Parses Twitch's Follow JSON Fixture" $ property prop_followsJSONFixture
+        it "Parses Twitch's Block List JSON Fixture" $ property prop_blocksJSONFixture
+        it "Parses Twitch's Extensions List JSON Fixture" $ property prop_extensionsJSONFixture
+        it "Parses Twitch's Active Extensions List JSON Fixture" $ property prop_activeExtensionsJSONFixture
+
+prop_userJSONFixture :: Bool
+prop_userJSONFixture =
+    let broadcasterType = Users.Partner
+        description = "Supporting third-party developers building Twitch integrations from chatbots to game integrations."
+        displayName = "TwitchDev"
+        userId = 141981764
+        login = "twitchdev"
+        offlineImageURL = "https://static-cdn.jtvnw.net/jtv_user_pictures/3f13ab61-ec78-4fe6-8481-8682cb3b0ac2-channel_offline_image-1920x1080.png"
+        profileImageURL = "https://static-cdn.jtvnw.net/jtv_user_pictures/8a6381c7-d0c0-4576-b179-38bd5ce1d6af-profile_image-300x300.png"
+        userType = Users.NormalUser
+        email = Just "not-real@email.com"
+        createdAt = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 "2016-12-14T20:32:28Z"
+        record = Users.UserEntry broadcasterType description displayName userId login offlineImageURL profileImageURL userType email createdAt
+        response = Right $ Users.UsersResponse [record]
+        parsed = parseJSON singleUserJSON :: Either String Users.UsersResponse
+    in response == parsed
+
+prop_usersJSONFixture :: Bool
+prop_usersJSONFixture =
+    let broadcasterType = Users.Partner
+        description = "Supporting third-party developers building Twitch integrations from chatbots to game integrations."
+        displayName = "TwitchDev"
+        userId = 141981764
+        login = "twitchdev"
+        offlineImageURL = "https://static-cdn.jtvnw.net/jtv_user_pictures/3f13ab61-ec78-4fe6-8481-8682cb3b0ac2-channel_offline_image-1920x1080.png"
+        profileImageURL = "https://static-cdn.jtvnw.net/jtv_user_pictures/8a6381c7-d0c0-4576-b179-38bd5ce1d6af-profile_image-300x300.png"
+        userType = Users.NormalUser
+        email = Just "not-real@email.com"
+        createdAt = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 "2016-12-14T20:32:28Z"
+        record = Users.UserEntry broadcasterType description displayName userId login offlineImageURL profileImageURL userType email createdAt
+        response = Right $ Users.UsersResponse [record, record]
+        parsed = parseJSON multiUserJSON :: Either String Users.UsersResponse
+    in response == parsed
+
+prop_followsJSONFixture :: Bool
+prop_followsJSONFixture =
+    let a_from_id = 171003792
+        a_from_login = "iiisutha067iii"
+        a_from_name = "IIIsutha067III"
+        a_to_id = 23161357
+        a_to_name = "LIRIK"
+        a_followed = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 "2017-08-22T22:55:24Z"
+
+        b_from_id = 113627897
+        b_from_login = "birdman616"
+        b_from_name = "Birdman616"
+        b_to_id = 23161357
+        b_to_name = "LIRIK"
+        b_followed = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 "2017-08-22T22:55:04Z"
+
+        a = Users.FollowEntry a_from_id a_from_login a_from_name a_to_id a_to_name a_followed
+        b = Users.FollowEntry b_from_id b_from_login b_from_name b_to_id b_to_name b_followed
+        total = 12345
+        cursor = "eyJiIjpudWxsLCJhIjoiMTUwMzQ0MTc3NjQyNDQyMjAwMCJ9"
+        response = Right $ Users.FollowsResponse total [a, b] cursor
+        parsed = parseJSON followsJSON :: Either String Users.FollowsResponse
+    in response == parsed
+
+prop_blocksJSONFixture :: Bool
+prop_blocksJSONFixture =
+    let a_user_id = 135093069
+        a_user_login = "bluelava"
+        a_display_name = "BlueLava"
+
+        b_user_id = 27419011
+        b_user_login = "travistyoj"
+        b_display_name = "TravistyOJ"
+
+        a = Users.BlockListEntry a_user_id a_user_login a_display_name
+        b = Users.BlockListEntry b_user_id b_user_login b_display_name
+        response = Right $ Users.BlockListResponse [a, b]
+        parsed = parseJSON blocksJSON :: Either String Users.BlockListResponse
+    in response == parsed
+
+prop_extensionsJSONFixture :: Bool
+prop_extensionsJSONFixture =
+    let a_i = "wi08ebtatdc7oj83wtl9uxwz807l8b"
+        a_v = "1.1.8"
+        a_n = "Streamlabs Leaderboard"
+        a_c = True
+        a_t = [Users.Panel]
+
+        b_i = "d4uvtfdr04uq6raoenvj7m86gdk16v"
+        b_v = "2.0.2"
+        b_n = "Prime Subscription and Loot Reminder"
+        b_c = True
+        b_t = [Users.Overlay]
+
+
+        c_i = "rh6jq1q334hqc2rr1qlzqbvwlfl3x0"
+        c_v = "1.1.0"
+        c_n = "TopClip"
+        c_c = True
+        c_t = [Users.Mobile, Users.Panel]
+
+        d_i = "zfh2irvx2jb4s60f02jq0ajm8vwgka"
+        d_v = "1.0.19"
+        d_n = "Streamlabs"
+        d_c = True
+        d_t = [Users.Mobile, Users.Overlay]
+
+        e_i = "lqnf3zxk0rv0g7gq92mtmnirjz2cjj"
+        e_v = "0.0.1"
+        e_n = "Dev Experience Test"
+        e_c = True
+        e_t = [Users.Component, Users.Mobile, Users.Panel, Users.Overlay]
+
+        a = Users.ExtensionsEntry a_c a_i a_n a_t a_v
+        b = Users.ExtensionsEntry b_c b_i b_n b_t b_v
+        c = Users.ExtensionsEntry c_c c_i c_n c_t c_v
+        d = Users.ExtensionsEntry d_c d_i d_n d_t d_v
+        e = Users.ExtensionsEntry e_c e_i e_n e_t e_v
+        response = Right $ Users.ExtensionsResponse [a, b, c, d, e]
+        parsed = parseJSON extensionsJSON :: Either String Users.ExtensionsResponse
+    in response == parsed
+
+prop_activeExtensionsJSONFixture :: Bool
+prop_activeExtensionsJSONFixture =
+    let p_1_i = "rh6jq1q334hqc2rr1qlzqbvwlfl3x0"
+        p_1_v = "1.1.0"
+        p_1_n = "TopClip"
+
+        p_2_i = "wi08ebtatdc7oj83wtl9uxwz807l8b"
+        p_2_v = "1.1.8"
+        p_2_n = "Streamlabs Leaderboard"
+
+        p_3_i = "naty2zwfp7vecaivuve8ef1hohh6bo"
+        p_3_v = "1.0.9"
+        p_3_n = "Streamlabs Stream Schedule & Countdown"
+
+        o_i = "zfh2irvx2jb4s60f02jq0ajm8vwgka"
+        o_v = "1.0.19"
+        o_n = "Streamlabs"
+
+        c_i = "lqnf3zxk0rv0g7gq92mtmnirjz2cjj"
+        c_v = "0.0.1"
+        c_n = "Dev Experience Test"
+        c_x = 0
+        c_y = 0
+
+        p = [ Users.ActiveExtensionEntry True p_1_i p_1_v p_1_n
+            , Users.ActiveExtensionEntry True p_2_i p_2_v p_2_n
+            , Users.ActiveExtensionEntry True p_3_i p_3_v p_3_n
+            ]
+        o = [ Users.ActiveExtensionEntry True o_i o_v o_n ]
+        c = [ Users.ActiveComponentExtensionEntry True c_i c_v c_n c_x c_y ]
+        response = Right $ Users.ActiveExtensionsResponse c o p
+        parsed = parseJSON activeExtensionsJSON :: Either String Users.ActiveExtensionsResponse
+    in response == parsed
+
+parseJSON :: JSON.FromJSON a => String -> Either String a
+parseJSON x = JSON.eitherDecode $ (BS.fromStrict . T.encodeUtf8 . T.pack) x
+
+-- Server Responses
+
+singleUserJSON :: String
+singleUserJSON = "{\"data\": [{\"id\": \"141981764\",\"login\": \"twitchdev\",\"display_name\": \"TwitchDev\",\"type\": \"\",\"broadcaster_type\": \"partner\",\"description\": \"Supporting third-party developers building Twitch integrations from chatbots to game integrations.\",\"profile_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/8a6381c7-d0c0-4576-b179-38bd5ce1d6af-profile_image-300x300.png\",\"offline_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/3f13ab61-ec78-4fe6-8481-8682cb3b0ac2-channel_offline_image-1920x1080.png\",\"view_count\": 5980557,\"email\": \"not-real@email.com\",\"created_at\": \"2016-12-14T20:32:28Z\"}]}"
+
+multiUserJSON :: String
+multiUserJSON = "{\"data\": [{\"id\": \"141981764\",\"login\": \"twitchdev\",\"display_name\": \"TwitchDev\",\"type\": \"\",\"broadcaster_type\": \"partner\",\"description\": \"Supporting third-party developers building Twitch integrations from chatbots to game integrations.\",\"profile_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/8a6381c7-d0c0-4576-b179-38bd5ce1d6af-profile_image-300x300.png\",\"offline_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/3f13ab61-ec78-4fe6-8481-8682cb3b0ac2-channel_offline_image-1920x1080.png\",\"view_count\": 5980557,\"email\": \"not-real@email.com\",\"created_at\": \"2016-12-14T20:32:28Z\"}, {\"id\": \"141981764\",\"login\": \"twitchdev\",\"display_name\": \"TwitchDev\",\"type\": \"\",\"broadcaster_type\": \"partner\",\"description\": \"Supporting third-party developers building Twitch integrations from chatbots to game integrations.\",\"profile_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/8a6381c7-d0c0-4576-b179-38bd5ce1d6af-profile_image-300x300.png\",\"offline_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/3f13ab61-ec78-4fe6-8481-8682cb3b0ac2-channel_offline_image-1920x1080.png\",\"view_count\": 5980557,\"email\": \"not-real@email.com\",\"created_at\": \"2016-12-14T20:32:28Z\"}]}"
+
+followsJSON :: String
+followsJSON = "{\"total\": 12345,\"data\":[{\"from_id\": \"171003792\",\"from_login\": \"iiisutha067iii\",\"from_name\": \"IIIsutha067III\",\"to_id\": \"23161357\",\"to_name\": \"LIRIK\",\"followed_at\": \"2017-08-22T22:55:24Z\"},{\"from_id\": \"113627897\",\"from_login\": \"birdman616\",\"from_name\": \"Birdman616\",\"to_id\": \"23161357\",\"to_name\": \"LIRIK\",\"followed_at\": \"2017-08-22T22:55:04Z\"}],\"pagination\":{\"cursor\": \"eyJiIjpudWxsLCJhIjoiMTUwMzQ0MTc3NjQyNDQyMjAwMCJ9\"}}"
+
+blocksJSON :: String
+blocksJSON = "{\"data\": [{\"user_id\": \"135093069\",\"user_login\": \"bluelava\",\"display_name\": \"BlueLava\"},{\"user_id\": \"27419011\",\"user_login\": \"travistyoj\",\"display_name\": \"TravistyOJ\"}]}"
+
+extensionsJSON :: String
+extensionsJSON = "{\"data\": [{\"id\": \"wi08ebtatdc7oj83wtl9uxwz807l8b\",\"version\": \"1.1.8\",\"name\": \"Streamlabs Leaderboard\",\"can_activate\": true,\"type\": [\"panel\"]},{\"id\": \"d4uvtfdr04uq6raoenvj7m86gdk16v\",\"version\": \"2.0.2\",\"name\": \"Prime Subscription and Loot Reminder\",\"can_activate\": true,\"type\": [\"overlay\"]},{\"id\": \"rh6jq1q334hqc2rr1qlzqbvwlfl3x0\",\"version\": \"1.1.0\",\"name\": \"TopClip\",\"can_activate\": true,\"type\": [\"mobile\",\"panel\"]},{\"id\": \"zfh2irvx2jb4s60f02jq0ajm8vwgka\",\"version\": \"1.0.19\",\"name\": \"Streamlabs\",\"can_activate\": true,\"type\": [\"mobile\",\"overlay\"]},{\"id\": \"lqnf3zxk0rv0g7gq92mtmnirjz2cjj\",\"version\": \"0.0.1\",\"name\": \"Dev Experience Test\",\"can_activate\": true,\"type\": [\"component\",\"mobile\",\"panel\",\"overlay\"]}]}"
+
+activeExtensionsJSON :: String
+activeExtensionsJSON = "{\"data\": {\"panel\": {\"1\": {\"active\": true,\"id\": \"rh6jq1q334hqc2rr1qlzqbvwlfl3x0\",\"version\": \"1.1.0\",\"name\": \"TopClip\"},\"2\": {\"active\": true,\"id\": \"wi08ebtatdc7oj83wtl9uxwz807l8b\",\"version\": \"1.1.8\",\"name\": \"Streamlabs Leaderboard\"},\"3\": {\"active\": true,\"id\": \"naty2zwfp7vecaivuve8ef1hohh6bo\",\"version\": \"1.0.9\",\"name\": \"Streamlabs Stream Schedule & Countdown\"}},\"overlay\": {\"1\": {\"active\": true,\"id\": \"zfh2irvx2jb4s60f02jq0ajm8vwgka\",\"version\": \"1.0.19\",\"name\": \"Streamlabs\"}},\"component\": {\"1\": {\"active\": true,\"id\": \"lqnf3zxk0rv0g7gq92mtmnirjz2cjj\",\"version\": \"0.0.1\",\"name\": \"Dev Experience Test\",\"x\": 0,\"y\": 0},\"2\": {\"active\": false}}}}"
diff --git a/twitchapi.cabal b/twitchapi.cabal
--- a/twitchapi.cabal
+++ b/twitchapi.cabal
@@ -1,5 +1,5 @@
 name:                twitchapi
-version:             0.0.1
+version:             0.0.2
 synopsis:            Client access to Twitch.tv API endpoints
 description:         Twitch.tv API client supporting Helix and PubSub
 homepage:            https://github.com/wuest/haskell-twitchapi
@@ -21,17 +21,20 @@
 library
   ghc-options:      -Wall -fwarn-implicit-prelude -fwarn-monomorphism-restriction -O2 -static
   default-language: Haskell2010
+  default-extensions:   LambdaCase
+                      , OverloadedStrings
   hs-source-dirs:   src
 
   exposed-modules:    Web.TwitchAPI.Helix.Bits
                     , Web.TwitchAPI.Helix.ChannelPoints
                     , Web.TwitchAPI.Helix.Request
+                    , Web.TwitchAPI.Helix.Users
                     , Web.TwitchAPI.PubSub
 
   build-depends:      base            >= 4.11 && < 4.18
                     , aeson           >= 2.1  && < 2.2
                     , bytestring      >= 0.10 && < 0.12
-                    , hoauth2         >= 1.16 && < 2.6
+                    , hoauth2         >= 1.16 && < 2.7
                     , http-client     >= 0.5  && < 0.8
                     , text            >= 1.2  && < 2.1
                     , time            >= 1.6  && < 1.14
@@ -54,3 +57,23 @@
                     , text            >= 1.2  && < 2.1
                     , time            >= 1.6  && < 1.14
                     , timerep         >= 2.0  && < 2.2
+
+test-suite helix
+  ghc-options:      -threaded -rtsopts -with-rtsopts=-N -O2
+  default-language: Haskell2010
+  type:             exitcode-stdio-1.0
+  hs-source-dirs:     test
+                    , src
+  main-is:          Helix.hs
+  other-modules:      Web.TwitchAPI.Helix.Request
+                    , Web.TwitchAPI.Helix.Users
+                    , Helix.Users
+  build-depends:      base            >= 4.11 && < 4.18
+                    , QuickCheck      >= 2.8
+                    , hspec           >= 2.7
+                    , aeson           >= 2.1  && < 2.2
+                    , bytestring      >= 0.10 && < 0.12
+                    , text            >= 1.2  && < 2.1
+                    , time            >= 1.6  && < 1.14
+                    , timerep         >= 2.0  && < 2.2
+                    , http-client     >= 0.5  && < 0.8
