diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -40,13 +40,17 @@
 
   provider:
     name: aws
-    runtime: nodejs6.10
+    runtime: nodejs8.10
 
   functions:
     myfunc:
       handler: mypackage.myfunc
       # Here, mypackage is the Haskell package name and myfunc is the executable
-      # name as defined in the Cabal file
+      # name as defined in the Cabal file. The handler field may be prefixed
+      # with a path of the form `dir1/.../dirn`, relative to `serverless.yml`,
+      # which points to the location where the Haskell package `mypackage` is
+      # defined. This prefix is not needed when the Stack project is defined at
+      # the same level as `serverless.yml`.
 
   plugins:
     - serverless-haskell
@@ -96,13 +100,18 @@
 
 ### Integration tests
 
-Integration tests are not run automatically due to the need for an AWS account.
-To run them manually:
+Integration test verifies that the project can build and deploy a complete
+function to AWS, and it runs with expected functionality.
 
+Integration test is only automatically run up to deployment due to the need for
+an AWS account. To run manually:
+
 * Ensure you have the required dependencies: `curl`, [jq], [NPM], `pwgen` and
   [Stack].
 * Get an AWS account and add the access credentials into your shell environment.
 * Run `./integration-test/run.sh`. The exit code indicates success.
+* To verify just the packaging, without deployment, run
+  `./integration-test/run.sh --dry-run`.
 
 ## Releasing
 
diff --git a/serverless-haskell.cabal b/serverless-haskell.cabal
--- a/serverless-haskell.cabal
+++ b/serverless-haskell.cabal
@@ -1,11 +1,11 @@
--- This file has been generated from package.yaml by hpack version 0.20.0.
+-- This file has been generated from package.yaml by hpack version 0.28.2.
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 298ad7fe6395331a112788dc36344fd07fa6c61411aa488fd4f0b128edec9710
+-- hash: 6aead2a5ac837e6007c05071a0b459bd55ce9a015ff3fc03b6d176d288dd453a
 
 name:           serverless-haskell
-version:        0.4.3
+version:        0.5.0
 synopsis:       Deploying Haskell code onto AWS Lambda using Serverless
 description:    Utilities to help process the events from AWS Lambda when deployed with the serverless-haskell plugin.
 category:       AWS, Cloud, Network
@@ -16,7 +16,6 @@
 license-file:   LICENSE
 build-type:     Simple
 cabal-version:  >= 1.10
-
 extra-source-files:
     README.md
 
@@ -25,6 +24,21 @@
   location: https://github.com/seek-oss/serverless-haskell
 
 library
+  exposed-modules:
+      AWSLambda
+      AWSLambda.Events
+      AWSLambda.Events.APIGateway
+      AWSLambda.Events.KinesisEvent
+      AWSLambda.Events.Records
+      AWSLambda.Events.S3Event
+      AWSLambda.Events.SNSEvent
+      AWSLambda.Handler
+      AWSLambda.Orphans
+      Data.Aeson.Alternative
+      Data.Aeson.Embedded
+      Data.Aeson.TextValue
+  other-modules:
+      Paths_serverless_haskell
   hs-source-dirs:
       src
   ghc-options: -Wall
@@ -38,31 +52,27 @@
     , base >=4.7 && <5
     , bytestring
     , case-insensitive
+    , http-types
+    , iproute
     , lens
     , text
     , time
     , unix
     , unordered-containers
-  exposed-modules:
-      AWSLambda
-      AWSLambda.Events
-      AWSLambda.Events.APIGateway
-      AWSLambda.Events.KinesisEvent
-      AWSLambda.Events.Records
-      AWSLambda.Events.S3Event
-      AWSLambda.Events.SNSEvent
-      AWSLambda.Handler
-      AWSLambda.Orphans
-      Data.Aeson.Alternative
-      Data.Aeson.Embedded
-      Data.Aeson.TextValue
-  other-modules:
-      Paths_serverless_haskell
   default-language: Haskell2010
 
 test-suite tests
   type: exitcode-stdio-1.0
   main-is: Spec.hs
+  other-modules:
+      AWSLambda.Events.APIGatewaySpec
+      AWSLambda.Events.KinesisEventSpec
+      AWSLambda.Events.S3EventSpec
+      AWSLambda.Events.SNSEventSpec
+      Data.Aeson.AlternativeSpec
+      Data.Aeson.EmbeddedSpec
+      Data.Aeson.TestUtil
+      Paths_serverless_haskell
   hs-source-dirs:
       test
   ghc-options: -Wall
@@ -78,6 +88,8 @@
     , case-insensitive
     , hspec
     , hspec-discover
+    , http-types
+    , iproute
     , lens
     , raw-strings-qq
     , serverless-haskell
@@ -85,13 +97,4 @@
     , time
     , unix
     , unordered-containers
-  other-modules:
-      AWSLambda.Events.APIGatewaySpec
-      AWSLambda.Events.KinesisEventSpec
-      AWSLambda.Events.S3EventSpec
-      AWSLambda.Events.SNSEventSpec
-      Data.Aeson.AlternativeSpec
-      Data.Aeson.EmbeddedSpec
-      Data.Aeson.TestUtil
-      Paths_serverless_haskell
   default-language: Haskell2010
diff --git a/src/AWSLambda.hs b/src/AWSLambda.hs
--- a/src/AWSLambda.hs
+++ b/src/AWSLambda.hs
@@ -21,7 +21,7 @@
   >
   > provider:
   >   name: aws
-  >   runtime: nodejs6.10
+  >   runtime: nodejs8.10
   >
   > functions:
   >   myfunc:
diff --git a/src/AWSLambda/Events/APIGateway.hs b/src/AWSLambda/Events/APIGateway.hs
--- a/src/AWSLambda/Events/APIGateway.hs
+++ b/src/AWSLambda/Events/APIGateway.hs
@@ -11,20 +11,27 @@
 -}
 module AWSLambda.Events.APIGateway where
 
-import           Control.Lens
+import           Control.Lens            hiding ((.=))
 import           Data.Aeson
 import           Data.Aeson.Casing       (aesonDrop, camelCase)
 import           Data.Aeson.TH           (deriveFromJSON)
 -- import           Data.CaseInsensitive (CI (..))
 import           Data.Aeson.Embedded
 import           Data.Aeson.TextValue
+import           Data.Aeson.Types        (Parser)
 import           Data.ByteString         (ByteString)
+import qualified Data.CaseInsensitive    as CI
 import           Data.HashMap.Strict     (HashMap)
 import qualified Data.HashMap.Strict     as HashMap
+import           Data.IP
 import           Data.Text               (Text)
+import qualified Data.Text               as Text
+import           Data.Text.Encoding      (decodeUtf8, encodeUtf8)
 import           GHC.Generics            (Generic)
 import           Network.AWS.Data.Base64
 import           Network.AWS.Data.Text
+import qualified Network.HTTP.Types      as HTTP
+import           Text.Read
 
 import           AWSLambda.Handler       (lambdaMain)
 
@@ -45,7 +52,7 @@
   , _riCognitoIdentityId             :: !(Maybe Text)
   , _riCaller                        :: !(Maybe Text)
   , _riApiKey                        :: !(Maybe Text)
-  , _riSourceIp                      :: !(Maybe Text)
+  , _riSourceIp                      :: !(Maybe IP)
   , _riCognitoAuthenticationType     :: !(Maybe Text)
   , _riCognitoAuthenticationProvider :: !(Maybe Text)
   , _riUserArn                       :: !(Maybe Text)
@@ -53,7 +60,25 @@
   , _riUser                          :: !(Maybe Text)
   } deriving (Eq, Show)
 
-$(deriveFromJSON (aesonDrop 3 camelCase) ''RequestIdentity)
+readParse :: Read a => String -> Text -> Parser a
+readParse msg str =
+  case readMaybe (Text.unpack str) of
+    Just result -> pure result
+    Nothing     -> fail $ "Failed to parse an " ++ msg
+
+instance FromJSON RequestIdentity where
+  parseJSON =
+    withObject "RequestIdentity" $ \o ->
+      RequestIdentity <$> o .:? "cognitoIdentityPoolId" <*> o .:? "accountId" <*>
+      o .:? "cognitoIdentityId" <*>
+      o .:? "caller" <*>
+      o .:? "apiKey" <*>
+      (o .:? "sourceIp" >>= traverse (readParse "IP address")) <*>
+      o .:? "cognitoAuthenticationType" <*>
+      o .:? "cognitoAuthenticationProvider" <*>
+      o .:? "userArn" <*>
+      o .:? "userAgent" <*>
+      o .:? "user"
 $(makeLenses ''RequestIdentity)
 
 data ProxyRequestContext = ProxyRequestContext
@@ -66,16 +91,17 @@
   , _prcResourcePath :: !Text
   , _prcHttpMethod   :: !Text
   , _prcApiId        :: !Text
+  , _prcProtocol     :: !Text
   } deriving (Eq, Show)
 $(deriveFromJSON (aesonDrop 4 camelCase) ''ProxyRequestContext)
 $(makeLenses ''ProxyRequestContext)
 
 data APIGatewayProxyRequest body = APIGatewayProxyRequest
   { _agprqResource              :: !Text
-  , _agprqPath                  :: !Text
-  , _agprqHttpMethod            :: !Method
-  , _agprqHeaders               :: !(HashMap HeaderName HeaderValue)
-  , _agprqQueryStringParameters :: !(HashMap QueryParamName QueryParamValue)
+  , _agprqPath                  :: !ByteString
+  , _agprqHttpMethod            :: !HTTP.Method
+  , _agprqHeaders               :: !HTTP.RequestHeaders
+  , _agprqQueryStringParameters :: !HTTP.Query
   , _agprqPathParameters        :: !(HashMap PathParamName PathParamValue)
   , _agprqStageVariables        :: !(HashMap StageVarName StageVarValue)
   , _agprqRequestContext        :: !ProxyRequestContext
@@ -86,15 +112,25 @@
   parseJSON = withObject "APIGatewayProxyRequest" $ \o ->
     APIGatewayProxyRequest
     <$> o .: "resource"
-    <*> o .: "path"
-    <*> o .: "httpMethod"
-    <*> o .:? "headers" .!= HashMap.empty
-    <*> o .:? "queryStringParameters" .!= HashMap.empty
+    <*> (encodeUtf8 <$> o .: "path")
+    <*> (encodeUtf8 <$> o .: "httpMethod")
+    <*> (fmap fromAWSHeaders <$> o .:? "headers") .!= mempty
+    <*> (fmap fromAWSQuery <$> o .:? "queryStringParameters") .!= mempty
     <*> o .:? "pathParameters" .!= HashMap.empty
     <*> o .:? "stageVariables" .!= HashMap.empty
     <*> o .: "requestContext"
     <*> o .:? "body"
-
+    where
+      -- Explicit type signatures so that we don't accidentally tell Aeson
+      -- to try to parse the wrong sort of structure
+      fromAWSHeaders :: HashMap HeaderName HeaderValue -> HTTP.RequestHeaders
+      fromAWSHeaders = fmap toHeader . HashMap.toList
+        where
+          toHeader = bimap (CI.mk . encodeUtf8) encodeUtf8
+      fromAWSQuery :: HashMap QueryParamName QueryParamValue -> HTTP.Query
+      fromAWSQuery = fmap toQueryItem . HashMap.toList
+        where
+          toQueryItem = bimap encodeUtf8 (\x -> if Text.null x then Nothing else Just . encodeUtf8 $ x)
 
 $(makeLenses ''APIGatewayProxyRequest)
 
@@ -112,20 +148,39 @@
 
 data APIGatewayProxyResponse body = APIGatewayProxyResponse
   { _agprsStatusCode :: !Int
-  , _agprsHeaders    :: !(HashMap HeaderName HeaderValue)
+  , _agprsHeaders    :: !HTTP.ResponseHeaders
   , _agprsBody       :: !(Maybe (TextValue body))
   } deriving (Eq, Show, Generic)
 
 instance ToText body => ToJSON (APIGatewayProxyResponse body) where
-  toJSON = genericToJSON $ aesonDrop 6 camelCase
+  toJSON APIGatewayProxyResponse {..} =
+    object
+      [ "statusCode" .= _agprsStatusCode
+      , "headers" .= toAWSHeaders _agprsHeaders
+      , "body" .= _agprsBody
+      ]
+    where
+      toAWSHeaders :: HTTP.ResponseHeaders -> HashMap HeaderName HeaderValue
+      toAWSHeaders = HashMap.fromList . fmap (bimap (decodeUtf8 . CI.original) decodeUtf8)
 
 instance FromText body => FromJSON (APIGatewayProxyResponse body) where
-  parseJSON = genericParseJSON $ aesonDrop 6 camelCase
+  parseJSON =
+    withObject "APIGatewayProxyResponse" $ \o ->
+      APIGatewayProxyResponse <$> o .: "statusCode" <*>
+      (fromAWSHeaders <$> o .: "headers") <*>
+      o .:? "body"
+      -- Explicit type signatures so that we don't accidentally tell Aeson
+      -- to try to parse the wrong sort of structure
+    where
+      fromAWSHeaders :: HashMap HeaderName HeaderValue -> HTTP.RequestHeaders
+      fromAWSHeaders = fmap toHeader . HashMap.toList
+        where
+          toHeader = bimap (CI.mk . encodeUtf8) encodeUtf8
 
 $(makeLenses ''APIGatewayProxyResponse)
 
 response :: Int -> APIGatewayProxyResponse body
-response statusCode = APIGatewayProxyResponse statusCode HashMap.empty Nothing
+response statusCode = APIGatewayProxyResponse statusCode mempty Nothing
 
 responseOK :: APIGatewayProxyResponse body
 responseOK = response 200
diff --git a/test/AWSLambda/Events/APIGatewaySpec.hs b/test/AWSLambda/Events/APIGatewaySpec.hs
--- a/test/AWSLambda/Events/APIGatewaySpec.hs
+++ b/test/AWSLambda/Events/APIGatewaySpec.hs
@@ -9,6 +9,7 @@
 import           Data.Aeson
 import           Data.ByteString.Lazy        (ByteString)
 import qualified Data.HashMap.Strict         as HashMap
+import           Data.IP
 import           Data.Text                   (Text)
 
 import           Text.RawString.QQ
@@ -19,10 +20,10 @@
 spec =  do
   describe "APIGatewayProxyRequest" $
     it "parses sample GET request" $
-      decode sampleGetRequestJSON `shouldBe` Just sampleGetRequest
+      eitherDecode sampleGetRequestJSON `shouldBe` Right sampleGetRequest
   describe "APIGatewayProxyResponse" $
     it "parses sample text event" $
-      decode sampleOKResponseJSON `shouldBe` Just sampleOKResponse
+      eitherDecode sampleOKResponseJSON `shouldBe` Right sampleOKResponse
 
 sampleGetRequestJSON :: ByteString
 sampleGetRequestJSON = [r|
@@ -55,6 +56,7 @@
     "resourceId": "us4z18",
     "stage": "test",
     "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9",
+    "protocol": "HTTP/1.1",
     "identity": {
       "cognitoIdentityPoolId": "",
       "accountId": "",
@@ -90,7 +92,6 @@
   , _agprqPath = "/test/hello"
   , _agprqHttpMethod = "GET"
   , _agprqHeaders =
-    HashMap.fromList
       [ ("X-Forwarded-Proto", "https")
       , ("CloudFront-Is-Desktop-Viewer", "true")
       , ( "Accept"
@@ -113,7 +114,7 @@
         , "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)")
       , ("X-Forwarded-For", "192.168.100.1, 192.168.1.1")
       ]
-  , _agprqQueryStringParameters = HashMap.fromList [("name", "me")]
+  , _agprqQueryStringParameters = [("name", Just "me")]
   , _agprqPathParameters = HashMap.fromList [("proxy", "hello")]
   , _agprqStageVariables = HashMap.fromList [("stageVarName", "stageVarValue")]
   , _agprqRequestContext =
@@ -130,7 +131,7 @@
       , _riCognitoIdentityId = Just ""
       , _riCaller = Just ""
       , _riApiKey = Just ""
-      , _riSourceIp = Just "192.168.100.1"
+      , _riSourceIp = Just $ IPv4 $ toIPv4 [192, 168, 100, 1]
       , _riCognitoAuthenticationType = Just ""
       , _riCognitoAuthenticationProvider = Just ""
       , _riUserArn = Just ""
@@ -141,6 +142,7 @@
     , _prcResourcePath = "/{proxy+}"
     , _prcHttpMethod = "GET"
     , _prcApiId = "wt6mne2s9k"
+    , _prcProtocol = "HTTP/1.1"
     }
   , _agprqBody = Nothing
   }
@@ -176,7 +178,7 @@
 sampleOKResponse =
   responseOK
   & responseBody ?~ "Hello World"
-  & agprsHeaders .~ HashMap.fromList
+  & agprsHeaders .~
     [ ("X-Forwarded-Proto", "https")
     , ("CloudFront-Is-Desktop-Viewer", "true")
     , ( "Accept"
