diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Tomas Carnecky
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/github-webhook-handler.cabal b/github-webhook-handler.cabal
new file mode 100644
--- /dev/null
+++ b/github-webhook-handler.cabal
@@ -0,0 +1,37 @@
+name:                github-webhook-handler
+version:             0.0.1
+
+synopsis:            GitHub WebHook Handler
+description:
+  Generic definition of a GitHub Webhook Handler. Specialized version for Snap
+  can be found in the 'github-webhook-handler-snap' package.
+
+license:             MIT
+license-file:        LICENSE
+
+author:              Tomas Carnecky
+maintainer:          tomas.carnecky@gmail.com
+
+category:            GitHub
+
+build-type:          Simple
+cabal-version:       >=1.10
+
+
+library
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+
+  build-depends:
+     base >=4.8 && <4.9
+   , bytestring
+   , github-types
+   , transformers
+   , aeson
+   , text
+   , vector
+   , cryptohash
+
+  exposed-modules:
+     GitHub.WebHook.Handler
diff --git a/src/GitHub/WebHook/Handler.hs b/src/GitHub/WebHook/Handler.hs
new file mode 100644
--- /dev/null
+++ b/src/GitHub/WebHook/Handler.hs
@@ -0,0 +1,90 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module GitHub.WebHook.Handler
+  ( Handler(..)
+  , Error(..)
+  , runHandler
+  ) where
+
+
+import           Crypto.Hash
+
+import           Data.Aeson (eitherDecodeStrict')
+import           Data.Aeson.Types (parseEither)
+
+import           Data.Monoid
+
+import           Data.Text
+import qualified Data.Text as T
+import           Data.Text.Encoding
+
+import           Data.ByteString
+import qualified Data.ByteString.Char8 as BC8
+
+import           GitHub.Types
+
+
+
+data Handler m = Handler
+    { hSecretKey :: Maybe String
+      -- ^ Optional key which is used to authenticate the incoming request.
+
+    , hBody :: m ByteString
+      -- ^ Action which is used to read the request body.
+
+    , hHeader :: ByteString -> m (Maybe ByteString)
+      -- ^ Action which is used to retrieve a particular header from the
+      -- request.
+
+    , hAction :: Either Error Event -> m ()
+      -- ^ Action which is executed once we've processed all the information
+      -- in the request.
+    }
+
+
+data Error
+    = InvalidRequest
+    | ParseError !Text
+    | UnsignedRequest
+    | InvalidSignature
+
+toParseError :: String -> Either Error Event
+toParseError = Left . ParseError . T.pack
+
+runHandler :: Monad m => Handler m -> m ()
+runHandler h = do
+    res <- do
+        rawBody     <- hBody h
+        mbSignature <- hHeader h "X-Hub-Signature"
+
+        authenticatedBody <- return $ case (hSecretKey h, mbSignature) of
+
+            -- No secret key and no signature. Pass along the body unverified.
+            (Nothing, Nothing) -> Right rawBody
+
+            -- Signature is available but no secret key to verify it. This is
+            -- not a fatal error, we can still process the event.
+            (Nothing, Just _) -> Right rawBody
+
+            -- Secret token is available but the request is not signed. Reject
+            -- the request.
+            (Just _, Nothing) -> Left UnsignedRequest
+
+            -- Both the signature and secret token are available. Verify the
+            -- signature and reject the request if that fails.
+            (Just sc, Just sig) -> do
+                let mac = hmac (BC8.pack sc) rawBody :: HMAC SHA1
+                if sig == ("sha1=" <> digestToHexByteString (hmacGetDigest mac))
+                    then Right rawBody
+                    else Left InvalidSignature
+
+        mbEventName <- hHeader h "X-GitHub-Event"
+        return $ do
+            eventName <- maybe (Left InvalidRequest) Right mbEventName
+            body      <- authenticatedBody
+            case eitherDecodeStrict' body of
+                Left e -> toParseError e
+                Right value -> either toParseError Right $
+                    parseEither (eventParser $ decodeUtf8 eventName) value
+
+    hAction h res
