diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c)2012, Lars Petersen
+Copyright (c)2012, Lars Petersen; (c)2014, Michael Ledger
 
 All rights reserved.
 
diff --git a/snaplet-recaptcha.cabal b/snaplet-recaptcha.cabal
--- a/snaplet-recaptcha.cabal
+++ b/snaplet-recaptcha.cabal
@@ -1,87 +1,37 @@
--- snaplet-recaptcha.cabal auto-generated by cabal init. For
--- additional options, see
--- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr.
--- The name of the package.
-Name:                snaplet-recaptcha
-
--- The package version. See the Haskell package versioning policy
--- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for
--- standards guiding when and how versions should be incremented.
-Version:             0.1
-
--- A short (one-line) description of the package.
-Synopsis:            A ReCAPTCHA verification snaplet with connection sharing.
-
--- A longer description of the package.
-Description:         This snaplet handles the interfacing with Google's ReCaptcha verification API.
-                     It uses `http-conduit` which manages a connection pool. On the one hand
-                     this avoid the overhead of reconnecting for every single captcha request and
-                     on the other hand limits the use of the server's resources.
-
--- The license under which the package is released.
-License:             BSD3
-
--- The file containing the license text.
-License-file:        LICENSE
-
--- The package author(s).
-Author:              Lars Petersen
-
--- An email address to which users can send suggestions, bug reports,
--- and patches.
-Maintainer:          info@lars-petersen.net
-
--- A copyright notice.
--- Copyright:           
-
-Category:            Web
-
-Build-type:          Simple
-
--- Extra files to be distributed with the package, such as examples or
--- a README.
--- Extra-source-files:  
-
--- Constraint on the version of Cabal needed to build this package.
-Cabal-version:       >=1.10
-
-Library
-  Hs-Source-Dirs:   src
-  Exposed-Modules:  Snap.Snaplet.ReCaptcha
-  Default-Language: Haskell2010
-  Build-Depends:    base >= 4 && < 5
-                  , data-lens
-                  , data-lens-template
-                  , bytestring
-                  , failure
-                  , conduit
-                  , http-conduit
-                  , http-types
-                  , snap-core
-                  , snap
+-- Initial snaplet-recaptcha.cabal generated by cabal init.  For further
+-- documentation, see http://haskell.org/cabal/users-guide/
 
-Executable          recaptcha-test
-  Main-Is:          recaptcha-test.hs
-  Hs-Source-Dirs:   src
-  Other-Modules:    Snap.Snaplet.ReCaptcha
-  Default-Language: Haskell2010
-  Build-Depends:    base >= 4 && < 5
-                  , data-lens
-                  , data-lens-template
-                  , bytestring
-                  , text
-                  , failure
-                  , snap-core
-                  , snap-server
-                  , snap
-                  , blaze-html
-                  , failure
-                  , conduit
-                  , http-conduit
-                  , http-types
+name:                snaplet-recaptcha
+version:             1.0
+synopsis:            A ReCAPTCHA verification snaplet with Heist integration and connection sharing.
+-- description:
+homepage:            http://github.com/mikeplus64/snaplet-recaptcha
+license:             BSD3
+license-file:        LICENSE
+author:              Mike Ledger, Lars Petersen
+maintainer:          Mike Ledger <eleventynine@gmail.com>
+-- copyright:
+category:            Web
+build-type:          Simple
+-- extra-source-files:
+cabal-version:       >=1.10
 
-Source-Repository   head
-  type:             git
-  location:         http://github.com/lpeterse/snaplet-recaptcha.git
+library
+  exposed-modules:     Snap.Snaplet.ReCaptcha
+                     , Snap.Snaplet.ReCaptcha.Example
 
+  build-depends:       base < 5
+                     , lens
+                     , heist >= 0.14 && <0.15
+                     , snap >= 0.13 && <0.14
+                     , blaze-builder
+                     , MonadCatchIO-transformers
+                     , mtl
+                     , aeson
+                     , bytestring
+                     , configurator
+                     , text
+                     , http-conduit >=2.1
 
+  hs-source-dirs:      src
+  default-language:    Haskell2010
diff --git a/src/Snap/Snaplet/ReCaptcha.hs b/src/Snap/Snaplet/ReCaptcha.hs
--- a/src/Snap/Snaplet/ReCaptcha.hs
+++ b/src/Snap/Snaplet/ReCaptcha.hs
@@ -1,138 +1,273 @@
-{-# LANGUAGE OverloadedStrings, FlexibleInstances, MultiParamTypeClasses, TemplateHaskell #-}
+{-# LANGUAGE DeriveDataTypeable        #-}
+{-# LANGUAGE FlexibleInstances         #-}
+{-# LANGUAGE MultiParamTypeClasses     #-}
+{-# LANGUAGE NoMonomorphismRestriction #-}
+{-# LANGUAGE OverloadedStrings         #-}
+{-# LANGUAGE TemplateHaskell           #-}
 -- |
 -- Module      : Snap.Snaplet.ReCaptcha
--- Copyright   : (c) Lars Petersen 2012
+-- Copyright   : (c) Mike Ledger 2014
+--               (c) Lars Petersen 2012
+--
 -- License     : BSD-style
 --
--- Maintainer  : info@lars-petersen.net
+-- Maintainer  : mike@quasimal.com, info@lars-petersen.net
 -- Stability   : experimental
 -- Portability : portable
--- 
--- This is a snaplet for google's ReCaptcha verification api. This library uses `http-conduit` and keeps connections alive (a maximum of 10 by now). This is an important point in order to avoid denial of service attacks.
--- 
--- Include it into your application like this:
 --
--- > import Snap.Snaplet.ReCaptcha
--- >
--- > data MyApplication = MyApplication { _recaptcha :: Snaplet ReCaptcha, ... }
--- >
--- > $(makeLenses [''MyApplication])
--- >
--- > instance HasReCaptcha MyApplicaiton where
--- >   recaptchaLens = subSnaplet recaptcha
--- >
--- > myApplication :: SnapletInit MyApplication
--- > myApplication
--- >   = makeSnaplet
--- >       "MyApplication"
--- >       ""
--- >        Nothing
--- >        $ do r <- embedSnaplet "recaptcha" recaptcha $ initReCaptcha "YOUR_PRIVATE_KEY"
--- >             return $ MyApplication { _recaptcha = r, ... }
+-- This is a snaplet for google's ReCaptcha verification api. This library uses
+-- `http-conduit` and keeps connections alive (a maximum of 10). This is an
+-- important point in order to avoid denial of service attacks.
 --
- 
-module Snap.Snaplet.ReCaptcha 
-       ( -- * Snaplet and Initialization
-         ReCaptcha ()
-       , HasReCaptcha (..)
-       , initReCaptcha
-         -- * Handlers
-       , verifyCaptcha
-         -- * Types
-       , PrivateKey
-       , ReCaptchaResult (..)
-       ) where
+-- See 'Snap.Snaplet.ReCaptcha.Example' and the docs provided here for example
+-- usage.
+--
 
-import Data.Lens.Common
-import Data.Lens.Template
+module Snap.Snaplet.ReCaptcha
+  ( -- * Snaplet and Initialization
+    ReCaptcha ()
+  , HasReCaptcha (..)
+  , initReCaptcha
+  , initReCaptcha'
+    -- * Handlers
+  , checkCaptcha
+  , withCaptcha
+  , getCaptcha
+    -- * Types
+  , Captcha(..)
+  , PrivateKey
+  , SiteKey
+    -- * Extra
+  , cstate
+  , recaptchaScript, recaptchaDiv
+  ) where
 
-import qualified Data.ByteString as BS
-import qualified Data.ByteString.Lazy as BSL
+import qualified Blaze.ByteString.Builder    as Blaze
+import           Control.Applicative
+import           Control.Lens
+import           Control.Monad.Reader        (runReaderT)
 
-import Data.Monoid
-import Control.Applicative
-import Control.Failure
-import Control.Exception (throw, try)
-import Control.Monad.Trans.Resource (runResourceT)
+import qualified Data.Aeson                  as JSON
+import qualified Data.Aeson.TH               as JSON
+import qualified Data.ByteString.Char8       as BS
+import qualified Data.Configurator           as Conf
+import           Data.Text                   (Text)
+import           Data.Text.Encoding          (encodeUtf8)
 
-import Network.HTTP.Types (renderSimpleQuery)
-import Network.HTTP.Conduit as HTTP
-import Snap
+import           Data.Foldable               (fold, for_, toList)
+import           Data.Monoid
+import           Data.Typeable
 
-data ReCaptcha
-  = ReCaptcha
-    { privateKey        :: PrivateKey
-    , connectionManager :: Manager 
-    }
+import           Heist
+import           Heist.Compiled
 
-class HasReCaptcha b where
-  recaptchaLens :: Lens (Snaplet b) (Snaplet ReCaptcha)
+import qualified Network.HTTP.Client.Conduit as HTTP
 
-type PrivateKey = BS.ByteString
+import           Snap
+import           Snap.Snaplet.Heist.Compiled
 
--- | The private key must be 40 characters long and encoded just like you get it from Google. 
-initReCaptcha :: PrivateKey -> SnapletInit b ReCaptcha
-initReCaptcha key
-  = makeSnaplet
-      "ReCaptcha"
-      ""
-      Nothing
-      $ do man <- liftIO (newManager def)
-           if BS.length key /= 40
-             then fail "ReCaptcha: private key must be exactly 40 chars long"
-             else return $ ReCaptcha key man
+type PrivateKey = BS.ByteString
+type SiteKey    = BS.ByteString
+type UserIP     = BS.ByteString
+type UserAnswer = BS.ByteString
 
-data ReCaptchaResult
+data Captcha
   = Success
-  -- Taken from the official ReCaptcha Api documentation
-  | Failure BSL.ByteString
-  | ConnectionError HttpException
-  -- Misc
-  | MissingArguments
+  | Failure
+  -- | Errors returned by the Captcha. See <https://developers.google.com/recaptcha/docs/verify>
+  -- for possible error codes. Note that 'Failure' is used for the case that the
+  -- only error code returned is "invalid-input-response".
+  | Errors [Text]
+  -- | The server didn't respond with the JSON object required as per
+  -- <https://developers.google.com/recaptcha/docs/verify>
+  | InvalidServerResponse
+  -- | There was no "recaptcha_response_field" parameter set in the user request.
+  | MissingResponseParam
+ deriving (Show, Typeable)
 
-instance Failure HttpException (Handler b ReCaptcha) where
-  failure = throw
+data ReCaptcha = ReCaptcha
+  { connectionManager :: !HTTP.Manager
+  , recaptchaQuery    :: !(UserIP -> UserAnswer -> HTTP.Request)
+  , _cstate           :: !Captcha
+  } deriving (Typeable)
 
--- | You may use captcha verification troughout your whole application like this:
+makeLenses ''ReCaptcha
+
+class HasReCaptcha b where
+  captchaLens :: SnapletLens (Snaplet b) ReCaptcha
+
+instance HasReCaptcha ReCaptcha where
+  captchaLens = id
+
+-- This is kinda lame - should we just parse it manually?
+data ReCaptchaResponse = ReCaptchaResponse
+  { success     :: !Bool
+  , error_codes :: !(Maybe [Text])
+  }
+
+JSON.deriveJSON JSON.defaultOptions ''Captcha
+
+JSON.deriveFromJSON JSON.defaultOptions
+  { JSON.fieldLabelModifier = map $ \c -> case c of
+      '_' -> '-'
+      _   -> c
+  } ''ReCaptchaResponse
+
+initialiser :: Maybe (Snaplet (Heist b)) -> (SiteKey, PrivateKey)
+            -> Initializer b v ReCaptcha
+initialiser mheist (site,key) = do
+  -- this has to parse for the snaplet to work at all
+  req <- liftIO (HTTP.parseUrl "https://www.google.com/recaptcha/api/siteverify")
+  man <- liftIO HTTP.newManager
+  for_ mheist (\heist -> addReCaptchaHeist heist site)
+  return ReCaptcha
+    { connectionManager = man
+    , recaptchaQuery    = \ip answer ->
+        HTTP.urlEncodedBody
+          [ ("secret"   , key)
+          , ("response" , answer)
+          , ("remoteip" , ip) ]
+          req
+    , _cstate = Failure
+    }
+
+-- | Initialise the 'ReCaptcha' snaplet. You are required to have "site_key" and
+-- "secret_key" set in the snaplet's configuration file. See 'initReCaptcha\''
+-- if you don't want to use Snap's snaplet configuration mechanism.
 --
--- > myHandler :: (HasReCaptcha b) => Handler b SomeSubSnaplet ()
--- > myHandler
--- >   = do c <- verifyCaptcha
--- >        case c of
--- >          Success   -> .. -- captcha successfully solved
--- >          _         -> .. -- something went wrong
+-- This provides optional Heist support, which is implemented using
+-- 'recaptchaScript' and 'recaptchaDiv'.
+initReCaptcha :: Maybe (Snaplet (Heist b)) -> SnapletInit b ReCaptcha
+initReCaptcha heist =
+  makeSnaplet "recaptcha" "ReCaptcha integration" Nothing $
+    initialiser heist =<< do
+      conf <- getSnapletUserConfig
+      (,) <$> require conf "site_key"
+          <*> require conf "secret_key"
+ where
+  require conf field = do
+    v <- liftIO (Conf.lookup conf field)
+    case v of
+      Just v' -> return v'
+      Nothing -> do
+        spath <- BS.pack `fmap` getSnapletFilePath
+        err   <- errorMsg (encodeUtf8 field <> " not in the config " <> spath
+                           <> "/devel.cfg")
+        fail (BS.unpack err)
+
+-- | Same as 'initReCaptcha', but passing the site key and private key
+-- explicitly - no configuration on the filesystem is required.
+initReCaptcha' :: Maybe (Snaplet (Heist b)) -> (SiteKey, PrivateKey)
+               -> SnapletInit b ReCaptcha
+initReCaptcha' heist keys =
+  makeSnaplet "recaptcha" "ReCaptcha integration" Nothing
+    (initialiser heist keys)
+
+addReCaptchaHeist :: Snaplet (Heist b) -> BS.ByteString -> Initializer b v ()
+addReCaptchaHeist heist site = addConfig heist $ mempty &~ do
+  scCompiledSplices .= do
+    "recaptcha-div"    ## pureSplice id (return (recaptchaDiv site))
+    "recaptcha-script" ## pureSplice id (return recaptchaScript)
+
+-- | @<script src='https://www.google.com/recaptcha/api.js' async defer></script> @
+recaptchaScript :: Blaze.Builder
+recaptchaScript = Blaze.fromByteString
+  "<script src='https://www.google.com/recaptcha/api.js' async defer></script>"
+
+-- | For use in a HTML form.
+recaptchaDiv :: BS.ByteString -> Blaze.Builder
+recaptchaDiv site = Blaze.fromByteString $!
+  "<div class='g-recaptcha' data-sitekey='" <> site <> "'></div>"
+
+-- | Get the ReCaptcha result by querying Google's API.
 --
--- You don't need to extract the challenge and response paramters from the request. This is all handled by the snaplet. Just make sure the parameters `recaptcha_challenge_field` and `recaptcha_response_field` are contained in the user's request. Otherwise the `verifyCaptcha` call will return `MissingArguments`.
+-- This requires a "g-recaptcha-response" (POST) parameter to be set in the
+-- current request.
 --
-verifyCaptcha :: (HasReCaptcha b) => Handler b c ReCaptchaResult 
-verifyCaptcha
-  = do mchallenge <- getParam "recaptcha_challenge_field"
-       mresponse  <- getParam "recaptcha_response_field"
-       case (mchallenge, mresponse) of
-         (Just challenge, Just response)
-           -> do man         <- withTop' recaptchaLens (gets connectionManager)
-                 privatekey  <- withTop' recaptchaLens (gets privateKey)
-                 rq          <- withTop' recaptchaLens (parseUrl "http://www.google.com/recaptcha/api/verify")
-                 remoteip    <- rqRemoteAddr <$> getRequest
-                 let request = rq
-                               { HTTP.method = "POST"
-                               , queryString = renderSimpleQuery False
-                                                 [ ("privatekey", privatekey)
-                                                 , ("remoteip",   remoteip)
-                                                 , ("challenge",  challenge)
-                                                 , ("response",   response)
-                                                 ]
-                               }
-                 result <- liftIO $ Control.Exception.try $ runResourceT $ httpLbs request man
-                 case result of
-                   Left e   -> do return (ConnectionError (e :: HttpException))
-                   Right r  -> do let ls = BSL.split 0x0a $ responseBody r
-                                  let ua = return (Failure $ responseBody r)
-                                  if length ls < 2
-                                    then ua
-                                    else case ls !! 0 of
-                                           "true"  -> return $ Success
-                                           "false" -> return $ Failure (ls !! 1) 
-                                           _       -> ua
-         _ -> return MissingArguments
+-- See 'ReCaptchaResult' for possible failure types.
+--
+-- @
+-- cstate <- getCaptcha
+-- case cstate of
+--   Success               -> writeText "Congratulations! You won."
+--   Failure               -> writeText "Incorrect cstate answer."
+--   MissingResponseParam  -> writeText "No g-recaptcha-response in POST"
+--   InvalidServerResponse -> writeText "Did Google change their API?"
+--   Errors errs           -> writeText ("Errors: " <> 'T.pack' ('show' errs))
+-- @
+--
+-- This may throw a 'HTTP.HttpException' if there is a connection-related error.
+getCaptcha :: HasReCaptcha b => Handler b c Captcha
+getCaptcha = do
+  mresponse <- getPostParam "g-recaptcha-response"
+  case mresponse of
+    Just answer -> withTop' captchaLens $ do
+      manager  <- gets connectionManager
+      getQuery <- gets recaptchaQuery
+      remoteip <- getsRequest rqRemoteAddr
+      response <- runReaderT (HTTP.httpLbs (getQuery remoteip answer)) manager
+      -- The reply is a JSON object looking like
+      -- {
+      --   "success": true|false,
+      --   "error-codes": [...]   // optional
+      -- }
+      -- see <https://developers.google.com/recaptcha/docs/verify>
+      -- we just use aeson and the derived FromJSON instance here
+      return $! case JSON.decode (HTTP.responseBody response) of
+        Just obj
+          | success      obj -> Success
+          | invalidInput obj -> Failure
+          | otherwise        -> Errors (fold (error_codes obj))
+        Nothing -> InvalidServerResponse
+    Nothing -> return MissingResponseParam
+ where
+  invalidInput obj = error_codes obj == Just ["invalid-input-response"]
 
+-- | Run one of two handlers on either failing or succeeding a captcha.
+--
+-- @
+-- 'withCaptcha' banForever $ do
+--   postId <- 'getParam' "id"
+--   thing  <- 'getPostParam' thing
+--   addCommentToDB postId thing
+-- @
+--
+-- See 'getCaptcha'
+withCaptcha
+  :: HasReCaptcha b
+  => Handler b c () -- ^ Ran on failure
+  -> Handler b c () -- ^ Ran on success
+  -> Handler b c ()
+withCaptcha onFail onSuccess = do
+  s <- getCaptcha
+  withTop' captchaLens (cstate .= s)
+  case s of
+    Success -> onSuccess
+    _       -> onFail
+
+-- | 'pass' if the cstate failed. Logs errors (not incorrect captchas) with
+-- 'logError'.
+--
+-- @ 'checkCaptcha' '<|>' 'writeText' "Captcha failed!" @
+--
+-- See 'getCaptcha'
+checkCaptcha :: HasReCaptcha b => Handler b c ()
+checkCaptcha = do
+  s <- getCaptcha
+  withTop' captchaLens (cstate .= s)
+  case s of
+    Success   -> return ()
+    Failure   -> pass
+    someError -> do
+      logError =<< errorMsg (BS.pack (show someError))
+      pass
+
+errorMsg :: (MonadSnaplet m, Monad (m b v)) => BS.ByteString
+         -> m b v BS.ByteString
+errorMsg err = do
+  ancestry <- getSnapletAncestry
+  name     <- getSnapletName
+  return $! showTextList (ancestry++toList name) <> " (ReCaptcha) : " <> err
+ where
+  showTextList :: [Text] -> BS.ByteString
+  showTextList = BS.intercalate "/" . map encodeUtf8
diff --git a/src/Snap/Snaplet/ReCaptcha/Example.hs b/src/Snap/Snaplet/ReCaptcha/Example.hs
new file mode 100644
--- /dev/null
+++ b/src/Snap/Snaplet/ReCaptcha/Example.hs
@@ -0,0 +1,150 @@
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TemplateHaskell       #-}
+module Snap.Snaplet.ReCaptcha.Example
+  ( -- * Main
+    main
+  , initSample
+    -- * Necessities
+  , sampleTemplate
+    -- * Implementation
+  , initBlog
+  ) where
+import qualified Blaze.ByteString.Builder    as Blaze
+import           Heist
+import           Heist.Compiled
+
+import           Snap
+import           Snap.Snaplet.Heist.Compiled
+import           Snap.Snaplet.ReCaptcha
+
+import           Control.Lens
+
+import qualified Data.ByteString.Char8       as BS
+import           Data.Monoid
+import           Data.Text                   (Text)
+
+-- | Simple sample snaplet, built using 'ReCaptcha' in order to demonstrate how
+-- one might use it in the scenario of adding comments to a blog.
+data Sample = Sample
+  { _recaptcha :: !(Snaplet ReCaptcha)
+  , _heist     :: !(Snaplet (Heist Sample))
+  , _blog      :: !(Snaplet Blog)
+  }
+
+-- | Not actually a blog
+data Blog = Blog
+  { _currentPost :: !(Maybe BS.ByteString)
+  }
+
+makeLenses ''Sample
+makeLenses ''Blog
+
+instance HasReCaptcha Sample where
+  captchaLens = subSnaplet recaptcha
+
+instance HasHeist Sample where
+  heistLens = subSnaplet heist
+
+-- | A "blog" snaplet which reads hypothetical "posts" by their 'id', routing
+-- GET on \/posts\/:id to display a post, and POST on \/posts\/:id to add a
+-- comment to them. For loose, useless definitions of "post" and "comment" -
+-- this snaplet is only for demonstration purposes.
+initBlog :: forall b. (HasReCaptcha b, HasHeist b) => Snaplet (Heist b)
+         -> SnapletInit b Blog
+initBlog heist = makeSnaplet "blog" "simple blog" Nothing $ do
+  me <- getLens
+
+  addRoutes
+    -- Hypothetical comments are just sent as POST to the respective post they
+    -- are replying to
+    [("/posts/:id", method GET displayPost <|> method POST commentOnPost)]
+
+  addConfig heist $ mempty &~ do
+    -- Just 'blog-post' to be whatever is in 'post' at the time
+    -- (hopefully set by 'displayPost' after being routed to /posts/:id)
+    scCompiledSplices .=
+      ("blog-post" ## pureSplice Blaze.fromByteString . lift $ do
+         post' <- withTop' me (use currentPost)
+         case post' of
+           Just post -> return post
+           Nothing   -> fail "Couldn't find that.")
+
+  return (Blog Nothing)
+ where
+  displayPost :: Handler b Blog ()
+  displayPost = do
+    Just postId <- getParam "id"
+    currentPost .= Just ("there is no post #" <> postId <> ". only me.")
+    render "recaptcha-example" <|> fail "Couldn't load recaptcha-example.tpl"
+
+  commentOnPost :: Handler b Blog ()
+  commentOnPost = do
+    Just postId  <- getParam "id"
+    Just captcha <- getPostParam "g-recaptcha-response"
+    checkCaptcha <|> fail "Bad captcha response."
+    -- if we reach here, the captcha was OK
+    Just name    <- getPostParam "name"
+    Just email   <- getPostParam "email"
+    Just content <- getPostParam "content"
+    writeBS $ BS.concat [postId, " < (", name,", ", email, ", ", content, ")"]
+
+-- | Heist template, written to $PWD\/snaplets\/heist\/recaptcha-example.tpl
+--
+-- >sampleTemplate ≈
+-- >  <html>
+-- >   <head>
+-- >     <recaptcha-script />
+-- >   </head>
+-- >   <body>
+-- >     <form method='POST'>
+-- >       <input type='text' name='name' placeholder='Name'>
+-- >       <input type='text' name='email' placeholder='Email'>
+-- >       <br>
+-- >       <textarea class='field' name='content' rows='20' placeholder='Content'></textarea>
+-- >       <br>
+-- >       <recaptcha-div />
+-- >       <input type='submit' value='Comment'>
+-- >     </form>
+-- >   </body>
+-- >  </html>
+--
+sampleTemplate :: Text
+sampleTemplate =
+ "<html>\
+ \  <head>\
+ \    <recaptcha-script />\
+ \  </head>\
+ \  <body>\
+ \    <form method='POST'>\
+ \      <input type='text' name='name' placeholder='Name'>\
+ \      <input type='text' name='email' placeholder='Email'>\
+ \      <br>\
+ \      <textarea class='field' name='content' rows='20' placeholder='Content'></textarea>\
+ \      <br>\
+ \      <recaptcha-div />\
+ \      <input type='submit' value='Comment'>\
+ \    </form>\
+ \  </body>\
+ \</html>"
+
+-- | Requires 'snaplets/heist/templates/sample.tpl' - a suggested version of which
+-- is available in this module as 'sampleTemplate'.
+--
+-- This simple asks for your site and private key through stdin.
+initSample :: SnapletInit Sample Sample
+initSample = makeSnaplet "sample" "" Nothing $ do
+  h <- nestSnaplet "heist"  heist     (heistInit "templates")
+  c <- nestSnaplet "submit" recaptcha (initReCaptcha (Just h))
+  t <- nestSnaplet "blog"   blog      (initBlog h)
+  return (Sample c h t)
+
+-- | @ 'main' = 'serveSnaplet' 'defaultConfig' 'initSample' @
+--
+-- You can load this into GHCi and run it, with full logging to stdout/stderr
+--
+-- >>> :main --verbose --access-log= --error-log=
+main :: IO ()
+main = serveSnaplet defaultConfig initSample
diff --git a/src/recaptcha-test.hs b/src/recaptcha-test.hs
deleted file mode 100644
--- a/src/recaptcha-test.hs
+++ /dev/null
@@ -1,68 +0,0 @@
-{-# LANGUAGE OverloadedStrings, FlexibleInstances, MultiParamTypeClasses, TemplateHaskell #-}
-module Main where
-
-import Data.Lens.Common
-import Data.Lens.Template
-
-import qualified Data.ByteString             as BS
-import qualified Data.ByteString.Lazy        as BSL
-import qualified Data.Text                   as T
-import qualified Data.Text.Encoding          as T
-import           Text.Blaze.Html5            as H
-import           Text.Blaze.Html5.Attributes as A
-import           Data.Monoid
-import           Control.Applicative
-import           Text.Blaze.Renderer.Utf8 (renderHtml)
-
-import Snap
-import Snap.Snaplet.ReCaptcha
-
-data ReCaptchaTest
-  = ReCaptchaTest
-    { _recaptcha :: Snaplet ReCaptcha
-    }
-
-$(makeLenses [''ReCaptchaTest])
-
-instance HasReCaptcha ReCaptchaTest where
-  recaptchaLens = subSnaplet recaptcha
-
-main :: IO ()
-main
-  = do putStrLn "Please enter your ReCaptcha public key:"
-       puk <- getLine
-       putStrLn "Please enter your ReCaptcha private key:"
-       prk <- T.encodeUtf8 <$> T.pack <$> getLine
-       putStrLn "Now point your browser to localhost:8000!"
-       serveSnaplet defaultConfig (snaplet puk prk)
-  where
-    snaplet puk prk 
-      = makeSnaplet
-                "ReCaptchaTest"
-                ""
-                Nothing
-                $ do r <- embedSnaplet "recaptcha" recaptcha $ initReCaptcha prk
-                     addRoutes [("",       writeLBS $ renderHtml $ template puk)
-                               ,("verify", do r <- verifyCaptcha
-                                              case r of
-                                                Success           -> writeBS "Success."
-                                                Failure x         -> writeBS "Failure: " >> writeLBS x
-                                                ConnectionError x -> writeBS "ConnectionError: "  >> writeText (T.pack $ show x)
-                                                MissingArguments  -> writeBS "MissingArguments"
-                                )
-                               ] 
-                     return (ReCaptchaTest r) 
-    template puk
-      = H.docTypeHtml
-         $ do H.head
-               $ do H.script ""
-                     ! A.type_ "text/javascript"
-                     ! A.src   "http://www.google.com/recaptcha/api/js/recaptcha_ajax.js"
-              H.body
-               ! A.onload ( "Recaptcha.create('" `mappend` toValue puk `mappend` "', 'recaptcha');" )
-               $ do H.form
-                     ! A.action "verify"
-                     ! A.method "POST"
-                     $ do H.div ""
-                           ! A.id "recaptcha"
-                          H.button "Test"
