diff --git a/aws-lambda-haskell-runtime.cabal b/aws-lambda-haskell-runtime.cabal
--- a/aws-lambda-haskell-runtime.cabal
+++ b/aws-lambda-haskell-runtime.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: eab5b338b49db7854613ecd43b17245f105c8c936c40230ecf9d08c5dfad9d6d
+-- hash: c21b3c988844f7c4bfb806aff4e307c5d5dc63a7973eff4a83eb329e7bccb1c9
 
 name:           aws-lambda-haskell-runtime
-version:        2.0.6
+version:        3.0.0
 synopsis:       Haskell runtime for AWS Lambda
 description:    Please see the README on GitHub at <https://github.com/theam/aws-lambda-haskell-runtime#readme>
 category:       AWS
@@ -46,6 +46,7 @@
       Aws.Lambda.Runtime.Environment
       Aws.Lambda.Runtime.Error
       Aws.Lambda.Runtime.Publish
+      Aws.Lambda.Utilities
       Paths_aws_lambda_haskell_runtime
   hs-source-dirs:
       src
diff --git a/src/Aws/Lambda/Configuration.hs b/src/Aws/Lambda/Configuration.hs
--- a/src/Aws/Lambda/Configuration.hs
+++ b/src/Aws/Lambda/Configuration.hs
@@ -3,7 +3,6 @@
   ( Main.LambdaOptions(..)
   , generateLambdaDispatcher
   , Dispatch.decodeObj
-  , Dispatch.encodeObj
   )
 where
 
diff --git a/src/Aws/Lambda/Meta/Dispatch.hs b/src/Aws/Lambda/Meta/Dispatch.hs
--- a/src/Aws/Lambda/Meta/Dispatch.hs
+++ b/src/Aws/Lambda/Meta/Dispatch.hs
@@ -1,50 +1,41 @@
 {-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE BangPatterns #-}
 
 {-| Dispatcher generation -}
 module Aws.Lambda.Meta.Dispatch
   ( generate
   , decodeObj
-  , encodeObj
   , Runtime.LambdaResult(..)
   ) where
 
+import qualified Data.Char as Char
 import Data.Function ((&))
 import Data.Text (Text)
 import qualified Data.Text as Text
-import qualified Data.Char as Char
 
 import Data.Aeson
 import qualified Data.ByteString.Lazy.Char8 as LazyByteString
 import qualified Language.Haskell.TH as Meta
 
 import Aws.Lambda.Meta.Common
+import qualified Aws.Lambda.Meta.Main as Main
+import qualified Aws.Lambda.Runtime.ApiGatewayInfo as ApiGatewayInfo
+import Aws.Lambda.Runtime.Common (toStandaloneLambdaResponse)
 import qualified Aws.Lambda.Runtime.Common as Runtime
 import qualified Aws.Lambda.Runtime.Error as Error
-import qualified Aws.Lambda.Runtime.ApiGatewayInfo as ApiGatewayInfo
 import qualified Control.Exception as Unchecked
-import qualified Aws.Lambda.Meta.Main as Main
-import Data.Typeable (Typeable, typeRep, Proxy (..))
+import Data.Typeable (Proxy (..), Typeable, typeRep)
 
 {-| Helper function that the dispatcher will use to
 decode the JSON that comes as an AWS Lambda event into the
 appropriate type expected by the handler.
 -}
-decodeObj :: forall a. (FromJSON a, Typeable a) => String -> Either Error.Parsing a
+decodeObj :: forall a. (FromJSON a, Typeable a) => LazyByteString.ByteString -> Either Error.Parsing a
 decodeObj x =
   let objName = show (typeRep (Proxy :: Proxy a)) in
-  case (eitherDecode $ LazyByteString.pack x) of
-    Left e  -> Left $ Error.Parsing e x objName
+  case (eitherDecode x) of
+    Left e  -> Left $ Error.Parsing e (LazyByteString.unpack x) objName
     Right v -> return v
 
-{-| Helper function that the dispatcher will use to
-decode the JSON that comes as an AWS Lambda event into the
-appropriate type expected by the handler.
--}
-encodeObj :: ToJSON a => a -> String
-encodeObj x = LazyByteString.unpack (encode x)
-
-
 {-| Generates the dispatcher out of a list of
 handler names in the form @src/Foo/Bar.handler@
 
@@ -70,20 +61,18 @@
   let pat = Meta.LitP (Meta.StringL $ Text.unpack lambdaHandler)
   body <- [e|do
     case decodeObj $(expressionName "eventObject") of
-      Right eventObject -> case decodeObj $(expressionName "contextObject") of
-        Right context -> (do
-          result <- $(expressionName (qualifiedHandlerName lambdaHandler)) eventObject context
-          either (pure . Left . Runtime.StandaloneLambdaError . encodeObj) (pure . Right . Runtime.StandaloneLambdaResult . encodeObj) result)
-          `Unchecked.catch` \(handlerError :: Unchecked.SomeException) -> pure . Left . Runtime.StandaloneLambdaError . encodeObj . show $ handlerError
-        Left err -> pure . Left . Runtime.StandaloneLambdaError . encodeObj $ err
-      Left err -> pure . Left . Runtime.StandaloneLambdaError . encodeObj $ err|]
+      Right eventObject -> (do
+          result <- $(expressionName (qualifiedHandlerName lambdaHandler)) eventObject contextObject
+          either (pure . Left . Runtime.StandaloneLambdaError . toStandaloneLambdaResponse) (pure . Right . Runtime.StandaloneLambdaResult . toStandaloneLambdaResponse) result)
+          `Unchecked.catch` \(handlerError :: Unchecked.SomeException) -> pure . Left . Runtime.StandaloneLambdaError . toStandaloneLambdaResponse . show $ handlerError
+      Left err -> pure . Left . Runtime.StandaloneLambdaError . toStandaloneLambdaResponse $ err|]
   pure $ Meta.Match pat (Meta.NormalB body) []
 
 standaloneLambdaUnmatchedCase :: Meta.MatchQ
 standaloneLambdaUnmatchedCase = do
   let pattern = Meta.WildP
   body <- [e|
-    pure . Left . Runtime.StandaloneLambdaError . encodeObj $ ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project")
+    pure . Left . Runtime.StandaloneLambdaError . toStandaloneLambdaResponse $ ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project" :: String)
     |]
   pure $ Meta.Match pattern (Meta.NormalB body) []
 
@@ -93,25 +82,23 @@
   body <- [e|do
     let returnErr statusCode = pure . Left . Runtime.ApiGatewayLambdaError . ApiGatewayInfo.mkApiGatewayResponse statusCode
     case decodeObj $(expressionName "eventObject") of
-      Right eventObject -> case decodeObj $(expressionName "contextObject") of
-        Right context -> do
-          resultE <- Unchecked.try $ $(expressionName (qualifiedHandlerName lambdaHandler)) eventObject context
-          case resultE of
-            Right result ->
-              either (pure . Left . Runtime.ApiGatewayLambdaError . fmap toJSON) (pure . Right . Runtime.ApiGatewayResult . fmap toJSON) result
-            Left (handlerError :: Unchecked.SomeException) ->
-              if (Runtime.propagateImpureExceptions . Runtime.apiGatewayDispatcherOptions $ options)
-              then returnErr 500 . toJSON . show $ handlerError
-              else returnErr 500 . toJSON $ "Something went wrong."
-        Left err -> returnErr 500 . toJSON $ err
-      Left err -> returnErr 400 . toJSON $ err|]
+      Right eventObject -> do
+        resultE <- Unchecked.try $ $(expressionName (qualifiedHandlerName lambdaHandler)) eventObject contextObject
+        case resultE of
+          Right result ->
+            either (pure . Left . Runtime.ApiGatewayLambdaError . fmap toApiGatewayResponseBody) (pure . Right . Runtime.ApiGatewayResult . fmap toApiGatewayResponseBody) result
+          Left (handlerError :: Unchecked.SomeException) ->
+            if (Runtime.propagateImpureExceptions . Runtime.apiGatewayDispatcherOptions $ options)
+            then returnErr 500 . toApiGatewayResponseBody . show $ handlerError
+            else returnErr 500 . toApiGatewayResponseBody . Text.pack $ "Something went wrong."
+      Left err -> returnErr 400 . toApiGatewayResponseBody . show $ err|]
   pure $ Meta.Match pat (Meta.NormalB body) []
 
 apiGatewayUnmatchedCase :: Meta.MatchQ
 apiGatewayUnmatchedCase = do
   let pattern = Meta.WildP
   body <- [e|
-    pure . Left . Runtime.ApiGatewayLambdaError . ApiGatewayInfo.mkApiGatewayResponse 500 . toJSON $ ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project")
+    pure . Left . Runtime.ApiGatewayLambdaError . ApiGatewayInfo.mkApiGatewayResponse 500 . toApiGatewayResponseBody $ ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project")
     |]
   pure $ Meta.Match pattern (Meta.NormalB body) []
 
diff --git a/src/Aws/Lambda/Meta/Main.hs b/src/Aws/Lambda/Meta/Main.hs
--- a/src/Aws/Lambda/Meta/Main.hs
+++ b/src/Aws/Lambda/Meta/Main.hs
@@ -21,5 +21,5 @@
  where
   directCallBody =
     [e|
-    runLambda run
+    runLambda initializeContext run
     |]
diff --git a/src/Aws/Lambda/Runtime.hs b/src/Aws/Lambda/Runtime.hs
--- a/src/Aws/Lambda/Runtime.hs
+++ b/src/Aws/Lambda/Runtime.hs
@@ -1,3 +1,6 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications    #-}
+
 module Aws.Lambda.Runtime
   ( runLambda
   , Runtime.LambdaResult(..)
@@ -15,7 +18,7 @@
 import qualified Network.HTTP.Client as Http
 
 import Data.Aeson
-import qualified Data.ByteString.Lazy.Char8 as LazyByteString
+import Data.IORef
 
 import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo
 import qualified Aws.Lambda.Runtime.Common as Runtime
@@ -27,13 +30,15 @@
 
 -- | Runs the user @haskell_lambda@ executable and posts back the
 -- results. This is called from the layer's @main@ function.
-runLambda :: Runtime.RunCallback -> IO ()
-runLambda callback = do
+runLambda :: forall context. IO context -> Runtime.RunCallback context -> IO ()
+runLambda initializeCustomContext callback = do
   manager <- Http.newManager httpManagerSettings
+  customContext <- initializeCustomContext
+  customContextRef <- newIORef customContext
   forever $ do
     lambdaApi <- Environment.apiEndpoint `catch` variableNotSet
     event     <- ApiInfo.fetchEvent manager lambdaApi `catch` errorParsing
-    context   <- Context.initialize event `catch` errorParsing `catch` variableNotSet
+    context   <- Context.initialize @context customContextRef event `catch` errorParsing `catch` variableNotSet
 
     (((invokeAndRun callback manager lambdaApi event context
       `Checked.catch` \err -> Publish.parsingError err lambdaApi context manager)
@@ -51,11 +56,11 @@
 invokeAndRun
   :: Throws Error.Invocation
   => Throws Error.EnvironmentVariableNotSet
-  => Runtime.RunCallback
+  => Runtime.RunCallback context
   -> Http.Manager
   -> String
   -> ApiInfo.Event
-  -> Context.Context
+  -> Context.Context context
   -> IO ()
 invokeAndRun callback manager lambdaApi event context = do
   result <- invokeWithCallback callback event context
@@ -66,17 +71,17 @@
 invokeWithCallback
   :: Throws Error.Invocation
   => Throws Error.EnvironmentVariableNotSet
-  => Runtime.RunCallback
+  => Runtime.RunCallback context
   -> ApiInfo.Event
-  -> Context.Context
+  -> Context.Context context
   -> IO Runtime.LambdaResult
 invokeWithCallback callback event context = do
   handlerName <- Environment.handlerName
   let lambdaOptions = Runtime.LambdaOptions
-                      { eventObject = LazyByteString.unpack $ ApiInfo.event event
-                      , contextObject = LazyByteString.unpack . encode $ context
+                      { eventObject = ApiInfo.event event
                       , functionHandler = handlerName
                       , executionUuid = ""  -- DirectCall doesnt use UUID
+                      , contextObject = context
                       }
   result <- callback lambdaOptions
   -- Flush output to insure output goes into CloudWatch logs
diff --git a/src/Aws/Lambda/Runtime/ApiGatewayInfo.hs b/src/Aws/Lambda/Runtime/ApiGatewayInfo.hs
--- a/src/Aws/Lambda/Runtime/ApiGatewayInfo.hs
+++ b/src/Aws/Lambda/Runtime/ApiGatewayInfo.hs
@@ -1,23 +1,30 @@
-{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE DefaultSignatures          #-}
+{-# LANGUAGE DerivingStrategies         #-}
+{-# LANGUAGE DuplicateRecordFields      #-}
+{-# LANGUAGE FlexibleInstances          #-}
+{-# LANGUAGE GeneralisedNewtypeDeriving #-}
+{-# LANGUAGE UndecidableInstances       #-}
 
 module Aws.Lambda.Runtime.ApiGatewayInfo
   ( ApiGatewayRequest(..)
   , ApiGatewayRequestContext(..)
   , ApiGatewayRequestContextIdentity(..)
   , ApiGatewayResponse(..)
+  , ApiGatewayResponseBody(..)
+  , ToApiGatewayResponseBody(..)
   , mkApiGatewayResponse ) where
 
+import Aws.Lambda.Utilities
 import Data.Aeson
+import Data.Aeson.Types (Parser)
+import qualified Data.Aeson.Types as T
+import qualified Data.CaseInsensitive as CI
 import Data.HashMap.Strict (HashMap)
 import Data.Text (Text)
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
 import GHC.Generics (Generic)
 import Network.HTTP.Types
-import qualified Data.CaseInsensitive as CI
-import qualified Data.Aeson.Types as T
-import qualified Data.ByteString.Lazy.Char8 as LazyByteString
-import Data.Aeson.Types (Parser)
 
 data ApiGatewayRequest body = ApiGatewayRequest
   { apiGatewayRequestResource              :: !Text
@@ -32,20 +39,43 @@
   , apiGatewayRequestBody                  :: !(Maybe body)
   } deriving (Show)
 
+-- We special case String and Text in order
+-- to avoid unneeded encoding which will wrap them in quotes and break parsing
+instance {-# OVERLAPPING #-} FromJSON (ApiGatewayRequest Text) where
+  parseJSON = parseApiGatewayRequest (.:)
+
+instance {-# OVERLAPPING #-} FromJSON (ApiGatewayRequest String) where
+  parseJSON = parseApiGatewayRequest (.:)
+
 instance FromJSON body => FromJSON (ApiGatewayRequest body) where
-  parseJSON (Object v) = ApiGatewayRequest <$>
-    v .: "resource" <*>
-    v .: "path" <*>
-    v .: "httpMethod" <*>
-    v .: "headers" <*>
-    v .: "queryStringParameters" <*>
-    v .: "pathParameters" <*>
-    v .: "stageVariables" <*>
-    v .: "isBase64Encoded" <*>
-    v .: "requestContext" <*>
-    v `parseObjectFromStringField` "body"
-  parseJSON _ = fail "Expected ApiGatewayRequest to be an object."
+  parseJSON = parseApiGatewayRequest parseObjectFromStringField
 
+-- We need this because API Gateway is going to send us the payload as a JSON string
+parseObjectFromStringField :: FromJSON a => Object -> Text -> Parser (Maybe a)
+parseObjectFromStringField obj fieldName = do
+  fieldContents <- obj .: fieldName
+  case fieldContents of
+    String stringContents ->
+      case eitherDecodeStrict (T.encodeUtf8 stringContents) of
+        Right success -> pure success
+        Left err      -> fail err
+    Null -> pure Nothing
+    other -> T.typeMismatch "String or Null" other
+
+parseApiGatewayRequest :: (Object -> Text -> Parser (Maybe body)) -> Value -> Parser (ApiGatewayRequest body)
+parseApiGatewayRequest bodyParser (Object v) = ApiGatewayRequest <$>
+  v .: "resource" <*>
+  v .: "path" <*>
+  v .: "httpMethod" <*>
+  v .: "headers" <*>
+  v .: "queryStringParameters" <*>
+  v .: "pathParameters" <*>
+  v .: "stageVariables" <*>
+  v .: "isBase64Encoded" <*>
+  v .: "requestContext" <*>
+  v `bodyParser` "body"
+parseApiGatewayRequest _ _ = fail "Expected ApiGatewayRequest to be an object."
+
 data ApiGatewayRequestContext = ApiGatewayRequestContext
   { apiGatewayRequestContextResourceId        :: !Text
   , apiGatewayRequestContextResourcePath      :: !Text
@@ -112,18 +142,23 @@
     v .: "user"
   parseJSON _ = fail "Expected ApiGatewayRequestContextIdentity to be an object."
 
--- We need this because API Gateway is going to send us the payload as a JSON string
-parseObjectFromStringField :: FromJSON a => Object -> Text -> Parser (Maybe a)
-parseObjectFromStringField obj fieldName = do
-  fieldContents <- obj .: fieldName
-  case fieldContents of
-    String stringContents ->
-      case eitherDecodeStrict (T.encodeUtf8 stringContents) of
-        Right success -> pure success
-        Left err -> fail err
-    Null -> pure Nothing
-    other -> T.typeMismatch "String or Null" other
+newtype ApiGatewayResponseBody =
+  ApiGatewayResponseBody Text
+  deriving newtype (ToJSON, FromJSON)
 
+class ToApiGatewayResponseBody a where
+  toApiGatewayResponseBody :: a -> ApiGatewayResponseBody
+
+-- We special case Text and String to avoid unneeded encoding which will wrap them in quotes
+instance {-# OVERLAPPING #-} ToApiGatewayResponseBody Text where
+  toApiGatewayResponseBody = ApiGatewayResponseBody
+
+instance {-# OVERLAPPING #-} ToApiGatewayResponseBody String where
+  toApiGatewayResponseBody = ApiGatewayResponseBody . T.pack
+
+instance ToJSON a => ToApiGatewayResponseBody a where
+  toApiGatewayResponseBody = ApiGatewayResponseBody . toJSONText
+
 data ApiGatewayResponse body = ApiGatewayResponse
   { apiGatewayResponseStatusCode      :: !Int
   , apiGatewayResponseHeaders         :: !ResponseHeaders
@@ -135,12 +170,15 @@
   fmap f v = v { apiGatewayResponseBody = f (apiGatewayResponseBody v) }
 
 instance ToJSON body => ToJSON (ApiGatewayResponse body)  where
-  toJSON ApiGatewayResponse {..} = object
-    [ "statusCode" .= apiGatewayResponseStatusCode
-    , "body" .= (String . T.pack . LazyByteString.unpack . encode @body $ apiGatewayResponseBody)
-    , "headers" .= object (map headerToPair apiGatewayResponseHeaders)
-    , "isBase64Encoded" .= apiGatewayResponseIsBase64Encoded
-    ]
+  toJSON = apiGatewayResponseToJSON toJSON
+
+apiGatewayResponseToJSON :: (body -> Value) -> ApiGatewayResponse body -> Value
+apiGatewayResponseToJSON bodyTransformer ApiGatewayResponse {..} = object
+  [ "statusCode" .= apiGatewayResponseStatusCode
+  , "body" .= bodyTransformer apiGatewayResponseBody
+  , "headers" .= object (map headerToPair apiGatewayResponseHeaders)
+  , "isBase64Encoded" .= apiGatewayResponseIsBase64Encoded
+  ]
 
 mkApiGatewayResponse :: Int -> payload -> ApiGatewayResponse payload
 mkApiGatewayResponse code payload =
diff --git a/src/Aws/Lambda/Runtime/Common.hs b/src/Aws/Lambda/Runtime/Common.hs
--- a/src/Aws/Lambda/Runtime/Common.hs
+++ b/src/Aws/Lambda/Runtime/Common.hs
@@ -1,4 +1,8 @@
-{-# LANGUAGE DeriveLift #-}
+{-# LANGUAGE DeriveLift                 #-}
+{-# LANGUAGE DerivingStrategies         #-}
+{-# LANGUAGE FlexibleInstances          #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE UndecidableInstances       #-}
 
 module Aws.Lambda.Runtime.Common
   ( RunCallback
@@ -8,11 +12,18 @@
   , DispatcherOptions(..)
   , ApiGatewayDispatcherOptions(..)
   , DispatcherStrategy(..)
+  , ToLambdaResponseBody(..)
+  , unLambdaResponseBody
   , defaultDispatcherOptions
   ) where
 
 import Aws.Lambda.Runtime.ApiGatewayInfo
-import Data.Aeson (Value)
+import Aws.Lambda.Runtime.Context (Context)
+import Aws.Lambda.Utilities
+import Data.Aeson (FromJSON, ToJSON)
+import qualified Data.ByteString.Lazy as Lazy
+import Data.Text (Text)
+import qualified Data.Text as Text
 import GHC.Generics (Generic)
 import Language.Haskell.TH.Syntax (Lift)
 
@@ -38,23 +49,42 @@
   deriving (Lift)
 
 -- | Callback that we pass to the dispatcher function
-type RunCallback =
-  LambdaOptions -> IO (Either LambdaError LambdaResult)
+type RunCallback context =
+  LambdaOptions context -> IO (Either LambdaError LambdaResult)
 
+-- | Wrapper type for lambda response body
+newtype LambdaResponseBody =
+  LambdaResponseBody { unLambdaResponseBody :: Text }
+  deriving newtype (ToJSON, FromJSON)
+
+class ToLambdaResponseBody a where
+  toStandaloneLambdaResponse :: a -> LambdaResponseBody
+
+-- We need to special case String and Text to avoid unneeded encoding
+-- which results in extra quotes put around plain text responses
+instance {-# OVERLAPPING #-} ToLambdaResponseBody String where
+  toStandaloneLambdaResponse = LambdaResponseBody . Text.pack
+
+instance {-# OVERLAPPING #-} ToLambdaResponseBody Text where
+  toStandaloneLambdaResponse = LambdaResponseBody
+
+instance ToJSON a => ToLambdaResponseBody a where
+  toStandaloneLambdaResponse = LambdaResponseBody . toJSONText
+
 -- | Wrapper type for lambda execution results
 data LambdaError =
-  StandaloneLambdaError String |
-  ApiGatewayLambdaError (ApiGatewayResponse Value)
+  StandaloneLambdaError LambdaResponseBody |
+  ApiGatewayLambdaError (ApiGatewayResponse ApiGatewayResponseBody)
 
 -- | Wrapper type to handle the result of the user
 data LambdaResult =
-  StandaloneLambdaResult String |
-  ApiGatewayResult (ApiGatewayResponse Value)
+  StandaloneLambdaResult LambdaResponseBody |
+  ApiGatewayResult (ApiGatewayResponse ApiGatewayResponseBody)
 
 -- | Options that the generated main expects
-data LambdaOptions = LambdaOptions
-  { eventObject     :: !String
-  , contextObject   :: !String
+data LambdaOptions context = LambdaOptions
+  { eventObject     :: !Lazy.ByteString
   , functionHandler :: !String
   , executionUuid   :: !String
+  , contextObject   :: !(Context context)
   } deriving (Generic)
diff --git a/src/Aws/Lambda/Runtime/Context.hs b/src/Aws/Lambda/Runtime/Context.hs
--- a/src/Aws/Lambda/Runtime/Context.hs
+++ b/src/Aws/Lambda/Runtime/Context.hs
@@ -4,15 +4,14 @@
   ) where
 
 import Control.Exception.Safe.Checked
-import Data.Aeson (FromJSON (..), ToJSON (..))
-import GHC.Generics (Generic)
+import Data.IORef
 
 import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo
 import qualified Aws.Lambda.Runtime.Environment as Environment
 import qualified Aws.Lambda.Runtime.Error as Error
 
 -- | Context that is passed to all the handlers
-data Context = Context
+data Context context = Context
   { memoryLimitInMb    :: !Int
   , functionName       :: !String
   , functionVersion    :: !String
@@ -22,16 +21,23 @@
   , logStreamName      :: !String
   , logGroupName       :: !String
   , deadline           :: !Int
-  } deriving (Generic, FromJSON, ToJSON)
+  , customContext      :: !(IORef context)
+  }
 
 -- | Initializes the context out of the environment
-initialize :: Throws Error.Parsing => Throws Error.EnvironmentVariableNotSet => ApiInfo.Event -> IO Context
-initialize ApiInfo.Event{..} = do
+initialize
+  :: Throws Error.Parsing
+  => Throws Error.EnvironmentVariableNotSet
+  => IORef context
+  -> ApiInfo.Event
+  -> IO (Context context)
+initialize customContextRef ApiInfo.Event{..} = do
   functionName          <- Environment.functionName
   version               <- Environment.functionVersion
   logStream             <- Environment.logStreamName
   logGroup              <- Environment.logGroupName
   memoryLimitInMb       <- Environment.functionMemory
+
   Environment.setXRayTrace traceId
   pure Context
     { functionName       = functionName
@@ -43,4 +49,5 @@
     , xrayTraceId        = traceId
     , awsRequestId       = awsRequestId
     , deadline           = deadlineMs
+    , customContext      = customContextRef
     }
diff --git a/src/Aws/Lambda/Runtime/Publish.hs b/src/Aws/Lambda/Runtime/Publish.hs
--- a/src/Aws/Lambda/Runtime/Publish.hs
+++ b/src/Aws/Lambda/Runtime/Publish.hs
@@ -9,7 +9,7 @@
 
 import Control.Monad (void)
 import Data.Aeson
-import qualified Data.ByteString.Char8 as ByteString
+import qualified Data.Text.Encoding as T
 import qualified Network.HTTP.Client as Http
 
 import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints
@@ -18,14 +18,14 @@
 import qualified Aws.Lambda.Runtime.Error as Error
 
 -- | Publishes the result back to AWS Lambda
-result :: LambdaResult -> String -> Context -> Http.Manager -> IO ()
+result :: LambdaResult -> String -> Context context -> Http.Manager -> IO ()
 result lambdaResult lambdaApi context manager = do
   let Endpoints.Endpoint endpoint = Endpoints.response lambdaApi (awsRequestId context)
   rawRequest <- Http.parseRequest endpoint
 
   let requestBody = case lambdaResult of
-        (StandaloneLambdaResult res) -> Http.RequestBodyBS (ByteString.pack res)
-        (ApiGatewayResult res) -> Http.RequestBodyLBS (encode res)
+        (StandaloneLambdaResult res) -> Http.RequestBodyBS (T.encodeUtf8 . unLambdaResponseBody $ res)
+        (ApiGatewayResult res)       -> Http.RequestBodyLBS (encode res)
       request = rawRequest
                 { Http.method = "POST"
                 , Http.requestBody = requestBody
@@ -34,22 +34,22 @@
   void $ Http.httpNoBody request manager
 
 -- | Publishes an invocation error back to AWS Lambda
-invocationError :: Error.Invocation -> String -> Context -> Http.Manager -> IO ()
+invocationError :: Error.Invocation -> String -> Context context -> Http.Manager -> IO ()
 invocationError err lambdaApi context =
   publish err (Endpoints.invocationError lambdaApi $ awsRequestId context) context
 
 -- | Publishes a parsing error back to AWS Lambda
-parsingError :: Error.Parsing -> String -> Context -> Http.Manager -> IO ()
+parsingError :: Error.Parsing -> String -> Context context -> Http.Manager -> IO ()
 parsingError err lambdaApi context =
   publish err (Endpoints.invocationError lambdaApi $ awsRequestId context)
     context
 
 -- | Publishes a runtime initialization error back to AWS Lambda
-runtimeInitError :: ToJSON err => err -> String -> Context -> Http.Manager -> IO ()
+runtimeInitError :: ToJSON err => err -> String -> Context context -> Http.Manager -> IO ()
 runtimeInitError err lambdaApi =
   publish err (Endpoints.runtimeInitError lambdaApi)
 
-publish :: ToJSON err => err -> Endpoints.Endpoint -> Context -> Http.Manager -> IO ()
+publish :: ToJSON err => err -> Endpoints.Endpoint -> Context context -> Http.Manager -> IO ()
 publish err (Endpoints.Endpoint endpoint) Context{..} manager = do
   rawRequest <- Http.parseRequest endpoint
 
diff --git a/src/Aws/Lambda/Utilities.hs b/src/Aws/Lambda/Utilities.hs
new file mode 100644
--- /dev/null
+++ b/src/Aws/Lambda/Utilities.hs
@@ -0,0 +1,9 @@
+module Aws.Lambda.Utilities (toJSONText) where
+
+import Data.Aeson
+import qualified Data.ByteString.Lazy.Char8 as LazyByteString
+import Data.Text
+import qualified Data.Text as T
+
+toJSONText :: ToJSON a => a -> Text
+toJSONText = T.pack . LazyByteString.unpack . encode
