diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
diff --git a/bugsnag-types.cabal b/bugsnag-types.cabal
new file mode 100644
--- /dev/null
+++ b/bugsnag-types.cabal
@@ -0,0 +1,102 @@
+cabal-version:   1.18
+name:            bugsnag-types
+version:         1.0.0.0
+license:         MIT
+maintainer:      pbrisbin@gmail.com
+author:          Patrick Brisbin
+homepage:        https://codeberg.org/pbrisbin/bugsnag-haskell#readme
+synopsis:        Types for the Bugsnag error reporter for Haskell
+description:     Please see README.md
+category:        Web
+build-type:      Simple
+extra-doc-files:
+    CHANGELOG.md
+    README.md
+
+flag examples
+    description: Build the examples
+    default:     False
+
+library
+    exposed-modules:
+        Data.Bugsnag.Types.Application
+        Data.Bugsnag.Types.BinaryArch
+        Data.Bugsnag.Types.Breadcrumb
+        Data.Bugsnag.Types.BreadcrumbType
+        Data.Bugsnag.Types.DependentNotifier
+        Data.Bugsnag.Types.Device
+        Data.Bugsnag.Types.ErrorType
+        Data.Bugsnag.Types.Event
+        Data.Bugsnag.Types.Exception
+        Data.Bugsnag.Types.FeatureFlag
+        Data.Bugsnag.Types.Notifier
+        Data.Bugsnag.Types.Payload
+        Data.Bugsnag.Types.Prelude
+        Data.Bugsnag.Types.Request
+        Data.Bugsnag.Types.RuntimeVersion
+        Data.Bugsnag.Types.Session
+        Data.Bugsnag.Types.SessionEvent
+        Data.Bugsnag.Types.Severity
+        Data.Bugsnag.Types.SeverityReason
+        Data.Bugsnag.Types.SeverityReasonAttributes
+        Data.Bugsnag.Types.SeverityReasonType
+        Data.Bugsnag.Types.Stackframe
+        Data.Bugsnag.Types.Thread
+        Data.Bugsnag.Types.User
+
+    hs-source-dirs:     src
+    other-modules:      Paths_bugsnag_types
+    default-language:   GHC2021
+    default-extensions:
+        DeriveAnyClass DerivingStrategies DuplicateRecordFields LambdaCase
+        NoFieldSelectors NoImplicitPrelude OverloadedRecordDot
+        OverloadedStrings
+
+    ghc-options:
+        -fwrite-ide-info -Weverything -Wno-all-missed-specialisations
+        -Wno-missed-specialisations -Wno-missing-exported-signatures
+        -Wno-missing-import-lists -Wno-missing-kind-signatures
+        -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode
+        -Wno-monomorphism-restriction -Wno-prepositive-qualified-module
+        -Wno-safe -Wno-unsafe
+
+    build-depends:
+        aeson >=2.1.2.1,
+        base >=4.17.2.1 && <5,
+        text >=2.0.2
+
+    if impl(ghc >=9.8)
+        ghc-options:
+            -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures
+
+test-suite spec
+    type:               exitcode-stdio-1.0
+    main-is:            Spec.hs
+    hs-source-dirs:     test
+    other-modules:
+        Data.Bugsnag.Types.PayloadSpec
+        Paths_bugsnag_types
+
+    default-language:   GHC2021
+    default-extensions:
+        DeriveAnyClass DerivingStrategies DuplicateRecordFields LambdaCase
+        NoFieldSelectors NoImplicitPrelude OverloadedRecordDot
+        OverloadedStrings
+
+    ghc-options:
+        -fwrite-ide-info -Weverything -Wno-all-missed-specialisations
+        -Wno-missed-specialisations -Wno-missing-exported-signatures
+        -Wno-missing-import-lists -Wno-missing-kind-signatures
+        -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode
+        -Wno-monomorphism-restriction -Wno-prepositive-qualified-module
+        -Wno-safe -Wno-unsafe -threaded -rtsopts -with-rtsopts=-N
+
+    build-depends:
+        aeson >=2.1.2.1,
+        base >=4.17.2.1 && <5,
+        bugsnag-types,
+        hspec >=2.10.10
+
+    if impl(ghc >=9.8)
+        ghc-options:
+            -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures
diff --git a/src/Data/Bugsnag/Types/Application.hs b/src/Data/Bugsnag/Types/Application.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Application.hs
@@ -0,0 +1,105 @@
+module Data.Bugsnag.Types.Application
+  ( Application (..)
+  , exampleApplication
+  , defaultApplication
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+import Data.Bugsnag.Types.BinaryArch
+
+-- | Information about the app where the error occurred. These fields are
+-- optional but highly recommended. To display custom app data alongside
+-- these standard fields on the dashboard, the custom data should be
+-- included in the @metaData@ object in an @app@ object.
+data Application = Application
+  { id :: Maybe Text
+  -- ^ A unique ID for the application.
+  , version :: Maybe Text
+  -- ^ The version number of the application which generated the error.
+  , versionCode :: Maybe Int
+  -- ^ The
+  -- <https://developer.android.com/studio/publish/versioning.html version code>
+  -- of the application (Android only)
+  , bundleVersion :: Maybe Text
+  -- ^ The
+  -- <https://developer.apple.com/library/content/technotes/tn2420/_index.html bundle version\/build number>
+  -- of the application (iOS\/macOS\/tvOS only)
+  , codeBundleId :: Maybe Text
+  -- ^ A unique identifier to identify a code bundle release when using tools
+  -- like CodePush (mobile only).
+  , buildUUID :: Maybe Text
+  -- ^ A build ID that is required to identify a specific build when the
+  -- version and version code are the same.
+  , releaseStage :: Maybe Text
+  -- ^ The release stage that this error occurred in, for example
+  -- \"development\", \"staging\" or \"production\".
+  , type_ :: Maybe Text
+  -- ^ A specialized type of the application, such as the worker queue or web
+  -- framework used, like \"rails\", \"mailman\", or \"celery\".
+  , dsymUUIDs :: Maybe [Text]
+  -- ^ The UUIDs of the <http://lldb.llvm.org/symbols.html debug symbols file>
+  -- corresponding to this application, if any.
+  , duration :: Maybe Int
+  -- ^ How long the app has been running for in milliseconds.
+  , durationInForeground :: Maybe Int
+  -- ^ How long the app has been in the foreground of the device in
+  -- milliseconds.
+  , inForeground :: Maybe Bool
+  -- ^ Whether or not the app was in the foreground when the error occurred.
+  , isLaunching :: Maybe Bool
+  -- ^ Whether or not the app was still launching when the error occurred.
+  , binaryArch :: Maybe BinaryArch
+  -- ^ The architecture of the running binary.
+  , runningOnRosetta :: Maybe Bool
+  -- ^ Whether the application is running on the Rosetta emulator for Mac.
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON Application where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Application where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultApplication :: Application
+defaultApplication =
+  Application
+    { id = Nothing
+    , version = Nothing
+    , versionCode = Nothing
+    , bundleVersion = Nothing
+    , codeBundleId = Nothing
+    , buildUUID = Nothing
+    , releaseStage = Nothing
+    , type_ = Nothing
+    , dsymUUIDs = Nothing
+    , duration = Nothing
+    , durationInForeground = Nothing
+    , inForeground = Nothing
+    , isLaunching = Nothing
+    , binaryArch = Nothing
+    , runningOnRosetta = Nothing
+    }
+
+exampleApplication :: Application
+exampleApplication =
+  Application
+    { id = Just "com.bugsnag.android.example.debug"
+    , version = Just "1.1.3"
+    , versionCode = Just 12
+    , bundleVersion = Just "1.0.2"
+    , codeBundleId = Just "1.0-1234"
+    , buildUUID = Just "BE5BA3D0-971C-4418-9ECF-E2D1ABCB66BE"
+    , releaseStage = Just "staging"
+    , type_ = Just "rails"
+    , dsymUUIDs = Just ["e6173678256785afd940392abee"]
+    , duration = Just 1275
+    , durationInForeground = Just 983
+    , inForeground = Just True
+    , isLaunching = Just True
+    , binaryArch = Just exampleBinaryArch
+    , runningOnRosetta = Just True
+    }
diff --git a/src/Data/Bugsnag/Types/BinaryArch.hs b/src/Data/Bugsnag/Types/BinaryArch.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/BinaryArch.hs
@@ -0,0 +1,33 @@
+module Data.Bugsnag.Types.BinaryArch
+  ( BinaryArch (..)
+  , exampleBinaryArch
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+-- | The architecture of the running binary.
+data BinaryArch
+  = X86
+  | X86_64
+  | Arm32
+  | Arm64
+  | Arm64e
+  | Armv6
+  | Armv7
+  | Armv7f
+  | Armv7k
+  | Armv7s
+  | Armv8
+  | Amd64
+  deriving stock (Bounded, Enum, Eq, Generic, Show)
+
+instance FromJSON BinaryArch where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON BinaryArch where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+exampleBinaryArch :: BinaryArch
+exampleBinaryArch = X86
diff --git a/src/Data/Bugsnag/Types/Breadcrumb.hs b/src/Data/Bugsnag/Types/Breadcrumb.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Breadcrumb.hs
@@ -0,0 +1,77 @@
+module Data.Bugsnag.Types.Breadcrumb
+  ( Breadcrumb (..)
+  , exampleBreadcrumb
+  , defaultBreadcrumb
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+import Data.Bugsnag.Types.BreadcrumbType
+
+data Breadcrumb = Breadcrumb
+  { timestamp :: Text
+  -- ^ The time at which the event occurred, in
+  -- <https://tools.ietf.org/html/rfc3339#section-5.8 ISO 8601 format>.
+  , name :: Text
+  -- ^ A short summary describing the event, such as the user action taken or a
+  -- new application state.
+  , type_ :: BreadcrumbType
+  -- ^ A category which describes the breadcrumb, from the list of allowed
+  -- values.
+  --
+  -- -   @navigation@ - Changing screens or content being displayed, with a
+  --     defined destination and optionally a previous location.
+  --
+  -- -   @request@ - Sending and receiving requests and responses.
+  --
+  -- -   @process@ - Performing an intensive task or query.
+  --
+  -- -   @log@ - Messages and severity sent to a logging platform.
+  --
+  -- -   @user@ - Actions performed by the user, like text input, button
+  --     presses, or confirming\/cancelling an alert dialog.
+  --
+  -- -   @state@ - Changing the overall state of an app, such as closing,
+  --     pausing, or being moved to the background, as well as device state
+  --     changes like memory or battery warnings and network connectivity
+  --     changes.
+  --
+  -- -   @error@ - An error which was reported to BugSnag encountered in the
+  --     same session.
+  --
+  -- -   @manual@ - User-defined, manually added breadcrumbs.
+  , metaData :: Maybe (KeyMap Text)
+  -- ^ Additional information about the event, as key\/value pairs.
+  --
+  -- > {
+  -- >     "to": "PauseView",
+  -- >     "from": "PodBayDoorView"
+  -- > }
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON Breadcrumb where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Breadcrumb where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultBreadcrumb :: Text -> Text -> BreadcrumbType -> Breadcrumb
+defaultBreadcrumb timestamp name type_ =
+  Breadcrumb
+    { timestamp
+    , name
+    , type_
+    , metaData = Nothing
+    }
+
+exampleBreadcrumb :: Breadcrumb
+exampleBreadcrumb =
+  Breadcrumb
+    { timestamp = "2016-07-19T12:17:27-0700"
+    , name = "Open menu"
+    , type_ = exampleBreadcrumbType
+    , metaData = Nothing
+    }
diff --git a/src/Data/Bugsnag/Types/BreadcrumbType.hs b/src/Data/Bugsnag/Types/BreadcrumbType.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/BreadcrumbType.hs
@@ -0,0 +1,52 @@
+module Data.Bugsnag.Types.BreadcrumbType
+  ( BreadcrumbType (..)
+  , exampleBreadcrumbType
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+-- | A category which describes the breadcrumb, from the list of allowed
+-- values.
+--
+-- -   @navigation@ - Changing screens or content being displayed, with a
+--     defined destination and optionally a previous location.
+--
+-- -   @request@ - Sending and receiving requests and responses.
+--
+-- -   @process@ - Performing an intensive task or query.
+--
+-- -   @log@ - Messages and severity sent to a logging platform.
+--
+-- -   @user@ - Actions performed by the user, like text input, button
+--     presses, or confirming\/cancelling an alert dialog.
+--
+-- -   @state@ - Changing the overall state of an app, such as closing,
+--     pausing, or being moved to the background, as well as device state
+--     changes like memory or battery warnings and network connectivity
+--     changes.
+--
+-- -   @error@ - An error which was reported to BugSnag encountered in the
+--     same session.
+--
+-- -   @manual@ - User-defined, manually added breadcrumbs.
+data BreadcrumbType
+  = Navigation
+  | Request
+  | Process
+  | Log
+  | User
+  | State
+  | Error
+  | Manual
+  deriving stock (Bounded, Enum, Eq, Generic, Show)
+
+instance FromJSON BreadcrumbType where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON BreadcrumbType where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+exampleBreadcrumbType :: BreadcrumbType
+exampleBreadcrumbType = Navigation
diff --git a/src/Data/Bugsnag/Types/DependentNotifier.hs b/src/Data/Bugsnag/Types/DependentNotifier.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/DependentNotifier.hs
@@ -0,0 +1,41 @@
+module Data.Bugsnag.Types.DependentNotifier
+  ( DependentNotifier (..)
+  , exampleDependentNotifier
+  , defaultDependentNotifier
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+data DependentNotifier = DependentNotifier
+  { name :: Text
+  -- ^ The notifier name.
+  , version :: Text
+  -- ^ The notifier\'s current version.
+  , url :: Text
+  -- ^ The URL associated with the notifier.
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON DependentNotifier where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON DependentNotifier where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultDependentNotifier :: Text -> Text -> Text -> DependentNotifier
+defaultDependentNotifier name version url =
+  DependentNotifier
+    { name
+    , version
+    , url
+    }
+
+exampleDependentNotifier :: DependentNotifier
+exampleDependentNotifier =
+  DependentNotifier
+    { name = "Bugsnag Android"
+    , version = "2.1.10"
+    , url = "https://github.com/bugsnag/bugsnag-android"
+    }
diff --git a/src/Data/Bugsnag/Types/Device.hs b/src/Data/Bugsnag/Types/Device.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Device.hs
@@ -0,0 +1,108 @@
+module Data.Bugsnag.Types.Device
+  ( Device (..)
+  , exampleDevice
+  , defaultDevice
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+import Data.Bugsnag.Types.RuntimeVersion
+
+-- | Information about the computer\/device running the app. These fields are
+-- optional but highly recommended. To display custom device data alongside
+-- these standard fields on the dashboard, the custom data should be
+-- included in the @metaData@ object in a @device@ object.
+data Device = Device
+  { hostname :: Maybe Text
+  -- ^ The hostname of the server running your code, if applicable.
+  , id :: Maybe Text
+  -- ^ A unique identifier for the device.
+  , manufacturer :: Maybe Text
+  -- ^ The manufacturer of the device.
+  , model :: Maybe Text
+  -- ^ The model of the device.
+  , modelNumber :: Maybe Text
+  -- ^ The model number of the device.
+  , osName :: Maybe Text
+  -- ^ The device\'s operating system name.
+  , osVersion :: Maybe Text
+  -- ^ The device\'s operating system version.
+  , freeMemory :: Maybe Int
+  -- ^ The number of bytes unused in the device\'s RAM.
+  , totalMemory :: Maybe Int
+  -- ^ The number of total bytes in the device\'s RAM.
+  , freeDisk :: Maybe Int
+  -- ^ The number of unused bytes on the drive running the application.
+  , browserName :: Maybe Text
+  -- ^ If a web application, the web browser used by the device.
+  , browserVersion :: Maybe Text
+  -- ^ If a web application, the version of the browser used by the device.
+  , jailbroken :: Maybe Bool
+  -- ^ Whether or not the device has been modified to give users root access.
+  , orientation :: Maybe Text
+  -- ^ The orientation of the device at the time of the error.
+  , time :: Maybe Text
+  -- ^ The time at which the error occurred, in
+  -- <https://tools.ietf.org/html/rfc3339#section-5.8 ISO 8601 format>.
+  , cpuAbi :: Maybe [Text]
+  -- ^ The <https://developer.android.com/ndk/guides/abis ABIs> supported by
+  -- the device (Android only).
+  , runtimeVersions :: Maybe RuntimeVersion
+  , macCatalystiOSVersion :: Maybe Text
+  -- ^ The device\'s Mac Catalyst iOS version.
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON Device where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Device where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultDevice :: Device
+defaultDevice =
+  Device
+    { hostname = Nothing
+    , id = Nothing
+    , manufacturer = Nothing
+    , model = Nothing
+    , modelNumber = Nothing
+    , osName = Nothing
+    , osVersion = Nothing
+    , freeMemory = Nothing
+    , totalMemory = Nothing
+    , freeDisk = Nothing
+    , browserName = Nothing
+    , browserVersion = Nothing
+    , jailbroken = Nothing
+    , orientation = Nothing
+    , time = Nothing
+    , cpuAbi = Nothing
+    , runtimeVersions = Nothing
+    , macCatalystiOSVersion = Nothing
+    }
+
+exampleDevice :: Device
+exampleDevice =
+  Device
+    { hostname = Just "web1.internal"
+    , id = Just "fd124e87760c4281aef"
+    , manufacturer = Just "LGE"
+    , model = Just "Nexus 6P"
+    , modelNumber = Just "600"
+    , osName = Just "android"
+    , osVersion = Just "8.0.1"
+    , freeMemory = Just 183879616
+    , totalMemory = Just 201326592
+    , freeDisk = Just 13478064128
+    , browserName = Just "Chrome"
+    , browserVersion = Just "61.0.3163.100"
+    , jailbroken = Nothing
+    , orientation = Just "portrait"
+    , time = Just "2018-08-07T10:16:34.564Z"
+    , cpuAbi = Just ["x86_64"]
+    , runtimeVersions = Just exampleRuntimeVersion
+    , macCatalystiOSVersion = Just "14.0"
+    }
diff --git a/src/Data/Bugsnag/Types/ErrorType.hs b/src/Data/Bugsnag/Types/ErrorType.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/ErrorType.hs
@@ -0,0 +1,37 @@
+module Data.Bugsnag.Types.ErrorType
+  ( ErrorType (..)
+  , exampleErrorType
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+-- | This should be set to indicate the type of runtime the thread was
+-- running on so that the stacktrace can be parsed correctly.
+data ErrorType
+  = Android
+  | Browserjs
+  | C
+  | Cocoa
+  | Csharp
+  | Electronnodejs
+  | Electronrendererjs
+  | Expojs
+  | Go
+  | Java
+  | Nodejs
+  | Php
+  | Python
+  | Reactnativejs
+  | Ruby
+  deriving stock (Bounded, Enum, Eq, Generic, Show)
+
+instance FromJSON ErrorType where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON ErrorType where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+exampleErrorType :: ErrorType
+exampleErrorType = Android
diff --git a/src/Data/Bugsnag/Types/Event.hs b/src/Data/Bugsnag/Types/Event.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Event.hs
@@ -0,0 +1,181 @@
+module Data.Bugsnag.Types.Event
+  ( Event (..)
+  , exampleEvent
+  , defaultEvent
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+import Data.Bugsnag.Types.Application
+import Data.Bugsnag.Types.Breadcrumb
+import Data.Bugsnag.Types.Device
+import Data.Bugsnag.Types.Exception
+import Data.Bugsnag.Types.FeatureFlag
+import Data.Bugsnag.Types.Request
+import Data.Bugsnag.Types.Session
+import Data.Bugsnag.Types.Severity
+import Data.Bugsnag.Types.SeverityReason
+import Data.Bugsnag.Types.Thread
+import Data.Bugsnag.Types.User
+
+data Event = Event
+  { exceptions :: [Exception]
+  -- ^ An array of exceptions that occurred during this event. There must be at
+  -- least one entry. Most of the time there will only be one exception, but
+  -- some languages support \"nested\" or \"caused by\" exceptions. In this
+  -- case, exceptions should be unwrapped and added to the array one at a
+  -- time. The array should be in order from the outermost error to the
+  -- innermost. For example, the first element in the array should be the
+  -- error caused by the second element, and so on.
+  , breadcrumbs :: Maybe [Breadcrumb]
+  -- ^ An array of user- and system-initiated events which led up to an error,
+  -- providing additional context. This list is sequential and ordered newest
+  -- to oldest.
+  , request :: Maybe Request
+  , threads :: Maybe [Thread]
+  -- ^ An array of background threads. This is optional but recommended for
+  -- apps that rely heavily on threading. Threads should be in an order that
+  -- makes sense for your application.
+  , context :: Maybe Text
+  -- ^ A string representing what was happening in the application at the time
+  -- of the error. This string could be used for grouping purposes, depending
+  -- on the event. Usually this would represent the controller and action in
+  -- a server based project. It could represent the screen that the user was
+  -- interacting with in a client side project. For example:
+  --
+  -- -   On Ruby on Rails the context could be @controller#action@.
+  --
+  -- -   In Android, the context could be the top most Activity.
+  --
+  -- -   In iOS, the context could be the name of the top most
+  --     UIViewController.
+  , groupingHash :: Maybe Text
+  -- ^ The
+  -- <https://docs.bugsnag.com/product/error-grouping default error grouping>
+  -- can be overridden by specifying a custom grouping hash.
+  , unhandled :: Maybe Bool
+  -- ^ Whether the error was unhandled. If true, the error was detected by the
+  -- notifier because it was not handled by the application. If false, the
+  -- errors was handled and reported using Bugsnag.notify.
+  , severity :: Maybe Severity
+  -- ^ The severity of the error
+  --
+  -- -   @error@ - The default for unhandled errors.
+  --
+  -- -   @warning@ - The default when Bugsnag.notify is called.
+  --
+  -- -   @info@ - Can be used in manual Bugsnag.notify calls.
+  , severityReason :: Maybe SeverityReason
+  , projectPackages :: Maybe [Text]
+  -- ^ Sets which package names should be considered as a part of the running
+  -- application. We mark stacktrace lines as in-project if they originate
+  -- from any of these packages and this allows us to improve the visual
+  -- display of the stacktrace on the dashboard.
+  , user :: Maybe User
+  , app :: Maybe Application
+  , device :: Maybe Device
+  , session :: Maybe Session
+  , featureFlags :: Maybe [FeatureFlag]
+  -- ^ Details of the feature flags (experiments) and associated variants that
+  -- were active when the error occurred.
+  , metaData :: Maybe (KeyMap (KeyMap Text))
+  -- ^ An object containing any further data you wish to attach to this error
+  -- event. This should contain one or more objects, with each object being
+  -- displayed in its own tab on the event details on the dashboard.
+  --
+  -- >   {
+  -- >       // Custom user data to be displayed in the User tab along with standard
+  -- >       // user fields on the dashboard.
+  -- >       "user": {
+  -- >          ...
+  -- >       },
+  -- >
+  -- >       // Custom app data to be displayed in the App tab along with standard
+  -- >       // app fields on the dashboard.
+  -- >       "app": {
+  -- >          ...
+  -- >       },
+  -- >
+  -- >       // Custom device data to be displayed in the Device tab along with
+  -- >       //standard device fields on the dashboard.
+  -- >       "device": {
+  -- >          ...
+  -- >       },
+  -- >
+  -- >       Custom request data to be displayed in the Request tab along with
+  -- >       standard request fields on the dashboard.
+  -- >       "request": {
+  -- >          ...
+  -- >       },
+  -- >
+  -- >       // This will be displayed as an extra tab on the dashboard.
+  -- >       "Some data": {
+  -- >
+  -- >           // A key value pair that will be displayed in the first tab.
+  -- >           "key": "value",
+  -- >
+  -- >           // Key value pairs can be contained in nested objects which helps
+  -- >           // to organize the information presented in the tab.
+  -- >           "setOfKeys": {
+  -- >               "key": "value",
+  -- >               "key2": "value"
+  -- >           }
+  -- >       },
+  -- >
+  -- >       // This would be the second extra tab on the dashboard.
+  -- >       "Some more data": {
+  -- >           ...
+  -- >       }
+  -- >   }
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON Event where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Event where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultEvent :: [Exception] -> Event
+defaultEvent exceptions =
+  Event
+    { exceptions
+    , breadcrumbs = Nothing
+    , request = Nothing
+    , threads = Nothing
+    , context = Nothing
+    , groupingHash = Nothing
+    , unhandled = Nothing
+    , severity = Nothing
+    , severityReason = Nothing
+    , projectPackages = Nothing
+    , user = Nothing
+    , app = Nothing
+    , device = Nothing
+    , session = Nothing
+    , featureFlags = Nothing
+    , metaData = Nothing
+    }
+
+exampleEvent :: Event
+exampleEvent =
+  Event
+    { exceptions = [exampleException]
+    , breadcrumbs = Just [exampleBreadcrumb]
+    , request = Just exampleRequest
+    , threads = Just [exampleThread]
+    , context = Just "auth/session#create"
+    , groupingHash = Just "buggy_file.rb"
+    , unhandled = Just True
+    , severity = Just exampleSeverity
+    , severityReason = Just exampleSeverityReason
+    , projectPackages = Just ["com.example.package1", "com.example.package2"]
+    , user = Just exampleUser
+    , app = Just exampleApplication
+    , device = Just exampleDevice
+    , session = Just exampleSession
+    , featureFlags = Just [exampleFeatureFlag]
+    , metaData = Nothing
+    }
diff --git a/src/Data/Bugsnag/Types/Exception.hs b/src/Data/Bugsnag/Types/Exception.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Exception.hs
@@ -0,0 +1,54 @@
+module Data.Bugsnag.Types.Exception
+  ( Exception (..)
+  , exampleException
+  , defaultException
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+import Data.Bugsnag.Types.ErrorType
+import Data.Bugsnag.Types.Stackframe
+
+data Exception = Exception
+  { errorClass :: Text
+  -- ^ The class of error which occurred. This field is used to group the
+  -- errors together so should not contain any contextual information that
+  -- would prevent correct grouping. This would ordinarily be the Exception
+  -- name when dealing with an exception.
+  , message :: Maybe Text
+  -- ^ The error message associated with the error. Usually this will contain
+  -- some information about this specific instance of the error and is not
+  -- used to group the errors.
+  , stacktrace :: [Stackframe]
+  -- ^ An array of stackframe objects. Each object represents one line in the
+  -- exception\'s stacktrace. This information is used to help with error
+  -- grouping, as well as displaying it to the user.
+  , type_ :: Maybe ErrorType
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON Exception where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Exception where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultException :: Text -> [Stackframe] -> Exception
+defaultException errorClass stacktrace =
+  Exception
+    { errorClass
+    , stacktrace
+    , message = Nothing
+    , type_ = Nothing
+    }
+
+exampleException :: Exception
+exampleException =
+  Exception
+    { errorClass = "NoMethodError"
+    , message = Just "Unable to connect to database"
+    , stacktrace = [exampleStackframe]
+    , type_ = Just exampleErrorType
+    }
diff --git a/src/Data/Bugsnag/Types/FeatureFlag.hs b/src/Data/Bugsnag/Types/FeatureFlag.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/FeatureFlag.hs
@@ -0,0 +1,37 @@
+module Data.Bugsnag.Types.FeatureFlag
+  ( FeatureFlag (..)
+  , exampleFeatureFlag
+  , defaultFeatureFlag
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+data FeatureFlag = FeatureFlag
+  { featureFlag :: Text
+  -- ^ The name of the feature flag.
+  , variant :: Maybe Text
+  -- ^ The name of the variant.
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON FeatureFlag where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON FeatureFlag where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultFeatureFlag :: Text -> FeatureFlag
+defaultFeatureFlag featureFlag =
+  FeatureFlag
+    { featureFlag
+    , variant = Nothing
+    }
+
+exampleFeatureFlag :: FeatureFlag
+exampleFeatureFlag =
+  FeatureFlag
+    { featureFlag = "share_ios_photo_sharing_button"
+    , variant = Just "blue_button"
+    }
diff --git a/src/Data/Bugsnag/Types/Notifier.hs b/src/Data/Bugsnag/Types/Notifier.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Notifier.hs
@@ -0,0 +1,51 @@
+module Data.Bugsnag.Types.Notifier
+  ( Notifier (..)
+  , exampleNotifier
+  , defaultNotifier
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+import Data.Bugsnag.Types.DependentNotifier
+
+-- | Describes the notifier itself. These properties are used within BugSnag
+-- to track error rates from a notifier.
+data Notifier = Notifier
+  { name :: Text
+  -- ^ The notifier name.
+  , version :: Text
+  -- ^ The notifier\'s current version.
+  , url :: Text
+  -- ^ The URL associated with the notifier.
+  , dependencies :: Maybe [DependentNotifier]
+  -- ^ For platforms where multiple notifiers are used to capture an event, an
+  -- optional array containing the details of the additional notifiers can be
+  -- specified.
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON Notifier where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Notifier where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultNotifier :: Text -> Text -> Text -> Notifier
+defaultNotifier name version url =
+  Notifier
+    { name
+    , version
+    , url
+    , dependencies = Nothing
+    }
+
+exampleNotifier :: Notifier
+exampleNotifier =
+  Notifier
+    { name = "Bugsnag Ruby"
+    , version = "1.0.11"
+    , url = "https://github.com/bugsnag/bugsnag-ruby"
+    , dependencies = Just [exampleDependentNotifier]
+    }
diff --git a/src/Data/Bugsnag/Types/Payload.hs b/src/Data/Bugsnag/Types/Payload.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Payload.hs
@@ -0,0 +1,54 @@
+module Data.Bugsnag.Types.Payload
+  ( Payload (..)
+  , examplePayload
+  , defaultPayload
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+import Data.Bugsnag.Types.Event
+import Data.Bugsnag.Types.Notifier
+
+data Payload = Payload
+  { apiKey :: Maybe Text
+  -- ^ The API Key associated with the project. Informs BugSnag which project
+  -- has generated this error. This is provided for legacy notifiers. It is
+  -- preferable to use the @Bugsnag-Api-Key@ header instead.
+  , payloadVersion :: Text
+  -- ^ The version number of the payload. This is currently @5@. The
+  -- @Bugsnag-Payload-Version@ header should be included as well for
+  -- compatibility reasons.
+  , notifier :: Notifier
+  , events :: [Event]
+  -- ^ An array of error events that BugSnag should be notified of. A notifier
+  -- can choose to group notices into an array to minimize network traffic,
+  -- or can notify BugSnag each time an event occurs. Should contain at least
+  -- 1 event.
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON Payload where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Payload where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultPayload :: Text -> Notifier -> [Event] -> Payload
+defaultPayload payloadVersion notifier events =
+  Payload
+    { payloadVersion
+    , notifier
+    , events
+    , apiKey = Nothing
+    }
+
+examplePayload :: Payload
+examplePayload =
+  Payload
+    { apiKey = Just "YOUR-NOTIFIER-API-KEY"
+    , payloadVersion = "5"
+    , notifier = exampleNotifier
+    , events = [exampleEvent]
+    }
diff --git a/src/Data/Bugsnag/Types/Prelude.hs b/src/Data/Bugsnag/Types/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Prelude.hs
@@ -0,0 +1,34 @@
+module Data.Bugsnag.Types.Prelude
+  ( module X
+  , aesonOptions
+  )
+where
+
+import Prelude as X hiding (id)
+
+import Control.Monad as X (void)
+import Data.Aeson (Options (..), defaultOptions)
+import Data.Aeson as X
+  ( FromJSON (..)
+  , ToJSON (..)
+  , genericParseJSON
+  , genericToEncoding
+  , genericToJSON
+  )
+import Data.Aeson.KeyMap as X (KeyMap)
+import Data.Char (toLower)
+import Data.Foldable as X (for_, traverse_)
+import Data.Maybe as X (catMaybes, fromMaybe, mapMaybe)
+import Data.Text as X (Text, pack, unpack)
+import Data.Traversable as X (for)
+import GHC.Generics as X (Generic)
+
+aesonOptions :: Options
+aesonOptions =
+  defaultOptions
+    { fieldLabelModifier = \case
+        "type_" -> "type"
+        x -> x
+    , constructorTagModifier = map toLower
+    , omitNothingFields = True
+    }
diff --git a/src/Data/Bugsnag/Types/Request.hs b/src/Data/Bugsnag/Types/Request.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Request.hs
@@ -0,0 +1,59 @@
+module Data.Bugsnag.Types.Request
+  ( Request (..)
+  , exampleRequest
+  , defaultRequest
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+-- | Details about the web request from the client that experienced the
+-- error, if relevant. To display custom request data alongside these
+-- standard fields on the dashboard, the custom data should be included in
+-- the @metaData@ object in a @request@ object.
+data Request = Request
+  { clientIp :: Maybe Text
+  -- ^ The IP address of the client that experienced the error.
+  , headers :: Maybe (KeyMap Text)
+  -- ^ The headers sent with the request.
+  --
+  -- > {
+  -- >     "Accept": "*/*",
+  -- >     "Accept-Encoding": "gzip, deflate, sdch, br",
+  -- >     "Accept-Language": "en-US,en;q=0.8"
+  -- > }
+  , httpMethod :: Maybe Text
+  -- ^ The HTTP method used.
+  , url :: Maybe Text
+  -- ^ The URL of the request.
+  , referer :: Maybe Text
+  -- ^ The <https://en.wikipedia.org/wiki/HTTP_referer HTTP referer>
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON Request where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Request where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultRequest :: Request
+defaultRequest =
+  Request
+    { clientIp = Nothing
+    , headers = Nothing
+    , httpMethod = Nothing
+    , url = Nothing
+    , referer = Nothing
+    }
+
+exampleRequest :: Request
+exampleRequest =
+  Request
+    { clientIp = Just "192.168.44.0"
+    , headers = Nothing
+    , httpMethod = Just "PUT"
+    , url = Just "http://example.com/users/19/settings"
+    , referer = Just "http://example.com?q=awesome"
+    }
diff --git a/src/Data/Bugsnag/Types/RuntimeVersion.hs b/src/Data/Bugsnag/Types/RuntimeVersion.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/RuntimeVersion.hs
@@ -0,0 +1,239 @@
+module Data.Bugsnag.Types.RuntimeVersion
+  ( RuntimeVersion (..)
+  , exampleRuntimeVersion
+  , defaultRuntimeVersion
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+-- | The versions of the relevant runtimes, languages and\/or frameworks for
+-- the platform.
+data RuntimeVersion = RuntimeVersion
+  { androidApiLevel :: Maybe Text
+  -- ^ The Android API level (Android only).
+  , bottle :: Maybe Text
+  -- ^ Bottle framework version (Python only).
+  , celery :: Maybe Text
+  -- ^ Celery task queue version (Python only).
+  , clangVersion :: Maybe Text
+  -- ^ Clang compiler version (iOS\/tvOS\/macOS only).
+  , cocos2dx :: Maybe Text
+  -- ^ Cocos2d-x framework version (Cocos2d-x only).
+  , delayedJob :: Maybe Text
+  -- ^ Delayed Job framework version (Ruby only).
+  , django :: Maybe Text
+  -- ^ Django framework version (Python only).
+  , dotnet :: Maybe Text
+  -- ^ Description of the framework (.NET only).
+  , dotnetApiCompatibility :: Maybe Text
+  -- ^ .NET API compatibility level (Unity only).
+  , dotnetClr :: Maybe Text
+  -- ^ Version of .NET Common Language Runtime (.NET only).
+  , dotnetScriptingRuntime :: Maybe Text
+  -- ^ .NET scripting runtime version (Unity only).
+  , electron :: Maybe Text
+  -- ^ Electron platform version (Electron only).
+  , eventMachine :: Maybe Text
+  -- ^ EventMachine library version (Ruby only).
+  , expoApp :: Maybe Text
+  -- ^ Expo app version (Expo only).
+  , expoSdk :: Maybe Text
+  -- ^ Expo SDK version (Expo only).
+  , flask :: Maybe Text
+  -- ^ Flask framework version (Python only).
+  , gin :: Maybe Text
+  -- ^ Gin framework version (Go only).
+  , go :: Maybe Text
+  -- ^ Go language version (Go only).
+  , javaType :: Maybe Text
+  -- ^ Java platform implementation type (Java only).
+  , javaVersion :: Maybe Text
+  -- ^ Java runtime version (Java only).
+  , jruby :: Maybe Text
+  -- ^ Version of JRuby (Ruby only).
+  , laravel :: Maybe Text
+  -- ^ Laravel framework version (PHP only).
+  , lumen :: Maybe Text
+  -- ^ Lumen framework version (PHP only).
+  , magento :: Maybe Text
+  -- ^ Magento platform version (PHP only).
+  , mailman :: Maybe Text
+  -- ^ Mailman framework version (Ruby only).
+  , martini :: Maybe Text
+  -- ^ Martini framework version (Go only).
+  , negroni :: Maybe Text
+  -- ^ Negroni framework version (Go only).
+  , node :: Maybe Text
+  -- ^ Node.js version (Javascript only).
+  , osBuild :: Maybe Text
+  -- ^ Build number of the OS (iOS\/tvOS\/macOS only).
+  , php :: Maybe Text
+  -- ^ Version of PHP (PHP only).
+  , python :: Maybe Text
+  -- ^ Version of Python (Python only).
+  , que :: Maybe Text
+  -- ^ Que job queue version (Ruby only).
+  , rack :: Maybe Text
+  -- ^ Rack webserver version (Ruby only).
+  , rails :: Maybe Text
+  -- ^ Ruby on Rails version (Ruby only).
+  , rake :: Maybe Text
+  -- ^ Rake tool version (Ruby only).
+  , reactNative :: Maybe Text
+  -- ^ Version of React Native (React Native\/Expo only).
+  , reactNativeJsEngine :: Maybe Text
+  -- ^ Javascript engine type (React Native\/Expo only).
+  , resque :: Maybe Text
+  -- ^ Resque library version (Ruby only).
+  , revel :: Maybe Text
+  -- ^ Revel framework version (Go only).
+  , ruby :: Maybe Text
+  -- ^ Version of Ruby (Ruby only).
+  , shoryoken :: Maybe Text
+  -- ^ Shoryoken framework version (Ruby only).
+  , sidekiq :: Maybe Text
+  -- ^ Sidekiq scheduler version (Ruby only).
+  , silex :: Maybe Text
+  -- ^ Silex framework version (PHP only).
+  , sinatra :: Maybe Text
+  -- ^ Sinatra DSL version (Ruby only).
+  , springBoot :: Maybe Text
+  -- ^ Spring Boot framework version (Java only).
+  , springFramework :: Maybe Text
+  -- ^ Spring framework version (Java only).
+  , swift :: Maybe Text
+  -- ^ Swift language version (iOS\/tvOS\/macOS only).
+  , symfony :: Maybe Text
+  -- ^ Symfony framework version (PHP only).
+  , tornado :: Maybe Text
+  -- ^ Tornado framework version (Python only).
+  , unity :: Maybe Text
+  -- ^ Version of Unity.
+  , unityScriptingBackend :: Maybe Text
+  -- ^ The Unity scripting backend - Mono or IL2CPP (Unity only).
+  , wordpress :: Maybe Text
+  -- ^ Wordpress version (PHP only).
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON RuntimeVersion where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON RuntimeVersion where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultRuntimeVersion :: RuntimeVersion
+defaultRuntimeVersion =
+  RuntimeVersion
+    { androidApiLevel = Nothing
+    , bottle = Nothing
+    , celery = Nothing
+    , clangVersion = Nothing
+    , cocos2dx = Nothing
+    , delayedJob = Nothing
+    , django = Nothing
+    , dotnet = Nothing
+    , dotnetApiCompatibility = Nothing
+    , dotnetClr = Nothing
+    , dotnetScriptingRuntime = Nothing
+    , electron = Nothing
+    , eventMachine = Nothing
+    , expoApp = Nothing
+    , expoSdk = Nothing
+    , flask = Nothing
+    , gin = Nothing
+    , go = Nothing
+    , javaType = Nothing
+    , javaVersion = Nothing
+    , jruby = Nothing
+    , laravel = Nothing
+    , lumen = Nothing
+    , magento = Nothing
+    , mailman = Nothing
+    , martini = Nothing
+    , negroni = Nothing
+    , node = Nothing
+    , osBuild = Nothing
+    , php = Nothing
+    , python = Nothing
+    , que = Nothing
+    , rack = Nothing
+    , rails = Nothing
+    , rake = Nothing
+    , reactNative = Nothing
+    , reactNativeJsEngine = Nothing
+    , resque = Nothing
+    , revel = Nothing
+    , ruby = Nothing
+    , shoryoken = Nothing
+    , sidekiq = Nothing
+    , silex = Nothing
+    , sinatra = Nothing
+    , springBoot = Nothing
+    , springFramework = Nothing
+    , swift = Nothing
+    , symfony = Nothing
+    , tornado = Nothing
+    , unity = Nothing
+    , unityScriptingBackend = Nothing
+    , wordpress = Nothing
+    }
+
+exampleRuntimeVersion :: RuntimeVersion
+exampleRuntimeVersion =
+  RuntimeVersion
+    { androidApiLevel = Just "28"
+    , bottle = Just "0.12.8"
+    , celery = Just "4.2.0"
+    , clangVersion = Just "7.0.1"
+    , cocos2dx = Just "3.17.2"
+    , delayedJob = Just "4.1.8"
+    , django = Just "2.1.7"
+    , dotnet = Just ".NET Framework 4.7.3416.0"
+    , dotnetApiCompatibility = Just ".NET 2.0 Subset"
+    , dotnetClr = Just "4.0.30319.17379"
+    , dotnetScriptingRuntime = Just ".NET 3.5 equivalent"
+    , electron = Just "11.4.3"
+    , eventMachine = Just "1.2.7"
+    , expoApp = Just "2.10.0.105125"
+    , expoSdk = Just "32.0.0"
+    , flask = Just "1.0.2"
+    , gin = Just "1.3.0"
+    , go = Just "1.11.2"
+    , javaType = Just "Java(TM) SE Runtime Environment"
+    , javaVersion = Just "1.8.0_131-b11"
+    , jruby = Just "9.2.5.0"
+    , laravel = Just "5.7.22"
+    , lumen = Just "5.7"
+    , magento = Just "2.3.0"
+    , mailman = Just "0.8.0"
+    , martini = Just "1.0"
+    , negroni = Just "1.0.0"
+    , node = Just "10.14.1"
+    , osBuild = Just "16D57"
+    , php = Just "7.2.12"
+    , python = Just "3.7.1"
+    , que = Just "0.14.3"
+    , rack = Just "2.0.6"
+    , rails = Just "5.2.2"
+    , rake = Just "12.3.2"
+    , reactNative = Just "0.57.5"
+    , reactNativeJsEngine = Just "hermes"
+    , resque = Just "2.0.0"
+    , revel = Just "0.21.0"
+    , ruby = Just "2.5.3"
+    , shoryoken = Just "5.0.1"
+    , sidekiq = Just "5.1.3"
+    , silex = Just "2.1.0"
+    , sinatra = Just "2.0.3"
+    , springBoot = Just "1.5.13.RELEASE"
+    , springFramework = Just "4.2.13.RELEASE"
+    , swift = Just "4.2"
+    , symfony = Just "4.2.11"
+    , tornado = Just "5.1.1"
+    , unity = Just "2018.2.17"
+    , unityScriptingBackend = Just "Mono"
+    , wordpress = Just "5.1"
+    }
diff --git a/src/Data/Bugsnag/Types/Session.hs b/src/Data/Bugsnag/Types/Session.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Session.hs
@@ -0,0 +1,49 @@
+module Data.Bugsnag.Types.Session
+  ( Session (..)
+  , exampleSession
+  , defaultSession
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+import Data.Bugsnag.Types.SessionEvent
+
+-- | Details of any session information associated with the event.
+--
+-- This can be used alongside the
+-- <https://smartbear.portal.swaggerhub.com/bugsnag/default/bugsnag-session-reporting-api Session Tracking API>
+-- to associate the event with a session so that a release\'s crash rate
+-- can be determined.
+data Session = Session
+  { id :: Text
+  -- ^ The unique identifier of the session.
+  , startedAt :: Text
+  -- ^ The time (in <https://en.wikipedia.org/wiki/ISO_8601 ISO 8601> format)
+  -- at which the session started.
+  , events :: SessionEvent
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON Session where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Session where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultSession :: Text -> Text -> SessionEvent -> Session
+defaultSession id startedAt events =
+  Session
+    { id
+    , startedAt
+    , events
+    }
+
+exampleSession :: Session
+exampleSession =
+  Session
+    { id = "9f65c975-8155-456f-91e5-c4c4b3db0555"
+    , startedAt = "2017-01-01T14:30:00.000Z"
+    , events = exampleSessionEvent
+    }
diff --git a/src/Data/Bugsnag/Types/SessionEvent.hs b/src/Data/Bugsnag/Types/SessionEvent.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/SessionEvent.hs
@@ -0,0 +1,41 @@
+module Data.Bugsnag.Types.SessionEvent
+  ( SessionEvent (..)
+  , exampleSessionEvent
+  , defaultSessionEvent
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+-- | Details of the number of handled and unhandled events that have occurred
+-- so far in this session.
+data SessionEvent = SessionEvent
+  { handled :: Int
+  -- ^ The number of handled events that have occurred in this session
+  -- (including this event).
+  , unhandled :: Int
+  -- ^ The number of unhandled events that have occurred in this session
+  -- (including this event)
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON SessionEvent where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON SessionEvent where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultSessionEvent :: Int -> Int -> SessionEvent
+defaultSessionEvent handled unhandled =
+  SessionEvent
+    { handled
+    , unhandled
+    }
+
+exampleSessionEvent :: SessionEvent
+exampleSessionEvent =
+  SessionEvent
+    { handled = 1
+    , unhandled = 1
+    }
diff --git a/src/Data/Bugsnag/Types/Severity.hs b/src/Data/Bugsnag/Types/Severity.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Severity.hs
@@ -0,0 +1,30 @@
+module Data.Bugsnag.Types.Severity
+  ( Severity (..)
+  , exampleSeverity
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+-- | The severity of the error
+--
+-- -   @error@ - The default for unhandled errors.
+--
+-- -   @warning@ - The default when Bugsnag.notify is called.
+--
+-- -   @info@ - Can be used in manual Bugsnag.notify calls.
+data Severity
+  = Error
+  | Warning
+  | Info
+  deriving stock (Bounded, Enum, Eq, Generic, Show)
+
+instance FromJSON Severity where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Severity where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+exampleSeverity :: Severity
+exampleSeverity = Error
diff --git a/src/Data/Bugsnag/Types/SeverityReason.hs b/src/Data/Bugsnag/Types/SeverityReason.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/SeverityReason.hs
@@ -0,0 +1,82 @@
+module Data.Bugsnag.Types.SeverityReason
+  ( SeverityReason (..)
+  , exampleSeverityReason
+  , defaultSeverityReason
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+import Data.Bugsnag.Types.SeverityReasonAttributes
+import Data.Bugsnag.Types.SeverityReasonType
+
+-- | Information about why the severity was picked.
+data SeverityReason = SeverityReason
+  { type_ :: Maybe SeverityReasonType
+  -- ^ A type key that represents the reason for the assigned severity.
+  --
+  -- -   @unhandledException@- Whenever an uncaught exception is discovered
+  --     (generic).
+  -- -   @unhandledError@ - When an error is discovered (PHP).
+  -- -   @log@ - Whenever a log message is sent (generic).
+  -- -   @signal@ - Whenever a \"fatal\" signal is discovered (iOS).
+  -- -   @strictMode@ - Whenever a strictMode issue is discovered (Android).
+  -- -   @unhandledPromiseRejection@ Whenever an unhandled promise rejection
+  --     is discovered (JS\/Node JS\/React Native).
+  -- -   @callbackErrorIntercept@- callbackErrorIntercept (Node JS).
+  -- -   @errorClass@ - Whenever an exception with a particular class is
+  --     automatically sent (Ruby).
+  -- -   @exceptionClass@ - Whenever an exception with a particular class is
+  --     automatically sent (Java).
+  -- -   @unhandledPanic@ - When a panic is unhandled and crashes the app
+  --     (Go).
+  -- -   @unhandledErrorMiddleware@ - Unhandled errors in ${Negroni}
+  --     middleware have a default severity:error.
+  -- -   @unhandledExceptionMiddleware@ - Unhandled exceptions in ${Rack}
+  --     middleware have a default severity:error.
+  -- -   @userCallbackSetSeverity@ - Whenever a callback changes a report\'s
+  --     severity (generic).
+  -- -   @userSpecifiedSeverity@ - Whenever a severity is set through a
+  --     manual notify call (generic).
+  -- -   @handledException@ - Whenever a handled exception is sent through
+  --     (generic).
+  -- -   @handledError@ - Whenever a handled error is sent through (PHP).
+  -- -   @handledPanic@ - Whenever a panic is handled through AutoNotify or
+  --     Recover (Go).
+  -- -   @userContextSetSeverity@ - When a user creates a context which
+  --     changes the severity (Python).
+  -- -   @anrError@ - Whenever an ANR is detected (Android).
+  -- -   @appHang@ - Whenever an app hang is detected (Cocoa).
+  -- -   @outOfMemory@ - When an app is terminated because it used too much
+  --     memory (Cocoa).
+  -- -   @thermalKill@ - When an app is terminated because the device is
+  --     overheating (Cocoa).
+  , attributes :: Maybe SeverityReasonAttributes
+  , unhandledOverridden :: Maybe Bool
+  -- ^ Whether the event\'s @unhandled@ flag was overridden from the
+  -- notifier\'s default value.
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON SeverityReason where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON SeverityReason where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultSeverityReason :: SeverityReason
+defaultSeverityReason =
+  SeverityReason
+    { type_ = Nothing
+    , attributes = Nothing
+    , unhandledOverridden = Nothing
+    }
+
+exampleSeverityReason :: SeverityReason
+exampleSeverityReason =
+  SeverityReason
+    { type_ = Just exampleSeverityReasonType
+    , attributes = Just exampleSeverityReasonAttributes
+    , unhandledOverridden = Just False
+    }
diff --git a/src/Data/Bugsnag/Types/SeverityReasonAttributes.hs b/src/Data/Bugsnag/Types/SeverityReasonAttributes.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/SeverityReasonAttributes.hs
@@ -0,0 +1,65 @@
+module Data.Bugsnag.Types.SeverityReasonAttributes
+  ( SeverityReasonAttributes (..)
+  , exampleSeverityReasonAttributes
+  , defaultSeverityReasonAttributes
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+-- | Optional attributes to provide extra information about the severity
+-- reason.
+data SeverityReasonAttributes = SeverityReasonAttributes
+  { errorType :: Maybe Text
+  -- ^ Included for @unhandledError@ severity reason. See
+  -- <http://php.net/manual/en/errorfunc.constants.php PHP Error Constants>.
+  , level :: Maybe Text
+  -- ^ Included for @log@ severity reason.
+  , signalType :: Maybe Text
+  -- ^ Included for @signal@ severity reason. See
+  -- <https://en.wikipedia.org/wiki/C_signal_handling Signal Codes>.
+  , violationType :: Maybe Text
+  -- ^ Included for @strictMode@ severity reason. See
+  -- <https://developer.android.com/reference/android/os/StrictMode.html Strict Mode>.
+  , errorClass :: Maybe Text
+  -- ^ Included for @errorClass@ severity reason. Specifies the error class
+  -- that is automatically sent.
+  , framework :: Maybe Text
+  -- ^ Included for middleware severity reasons, specifies the middleware that
+  -- had an error.
+  , exceptionClass :: Maybe Text
+  -- ^ Included for @exceptionClass@ severity reason. Specifies the error class
+  -- that is automatically sent.
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON SeverityReasonAttributes where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON SeverityReasonAttributes where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultSeverityReasonAttributes :: SeverityReasonAttributes
+defaultSeverityReasonAttributes =
+  SeverityReasonAttributes
+    { errorType = Nothing
+    , level = Nothing
+    , signalType = Nothing
+    , violationType = Nothing
+    , errorClass = Nothing
+    , framework = Nothing
+    , exceptionClass = Nothing
+    }
+
+exampleSeverityReasonAttributes :: SeverityReasonAttributes
+exampleSeverityReasonAttributes =
+  SeverityReasonAttributes
+    { errorType = Just "E_ERROR"
+    , level = Just "Error"
+    , signalType = Just "SIGSEGV"
+    , violationType = Just "NetworkOnMainThread"
+    , errorClass = Just "ActiveRecord::RecordNotFound"
+    , framework = Just "Rack"
+    , exceptionClass = Just "TypeMismatchException"
+    }
diff --git a/src/Data/Bugsnag/Types/SeverityReasonType.hs b/src/Data/Bugsnag/Types/SeverityReasonType.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/SeverityReasonType.hs
@@ -0,0 +1,80 @@
+module Data.Bugsnag.Types.SeverityReasonType
+  ( SeverityReasonType (..)
+  , exampleSeverityReasonType
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+-- | A type key that represents the reason for the assigned severity.
+--
+-- -   @unhandledException@- Whenever an uncaught exception is discovered
+--     (generic).
+-- -   @unhandledError@ - When an error is discovered (PHP).
+-- -   @log@ - Whenever a log message is sent (generic).
+-- -   @signal@ - Whenever a \"fatal\" signal is discovered (iOS).
+-- -   @strictMode@ - Whenever a strictMode issue is discovered (Android).
+-- -   @unhandledPromiseRejection@ Whenever an unhandled promise rejection
+--     is discovered (JS\/Node JS\/React Native).
+-- -   @callbackErrorIntercept@- callbackErrorIntercept (Node JS).
+-- -   @errorClass@ - Whenever an exception with a particular class is
+--     automatically sent (Ruby).
+-- -   @exceptionClass@ - Whenever an exception with a particular class is
+--     automatically sent (Java).
+-- -   @unhandledPanic@ - When a panic is unhandled and crashes the app
+--     (Go).
+-- -   @unhandledErrorMiddleware@ - Unhandled errors in ${Negroni}
+--     middleware have a default severity:error.
+-- -   @unhandledExceptionMiddleware@ - Unhandled exceptions in ${Rack}
+--     middleware have a default severity:error.
+-- -   @userCallbackSetSeverity@ - Whenever a callback changes a report\'s
+--     severity (generic).
+-- -   @userSpecifiedSeverity@ - Whenever a severity is set through a
+--     manual notify call (generic).
+-- -   @handledException@ - Whenever a handled exception is sent through
+--     (generic).
+-- -   @handledError@ - Whenever a handled error is sent through (PHP).
+-- -   @handledPanic@ - Whenever a panic is handled through AutoNotify or
+--     Recover (Go).
+-- -   @userContextSetSeverity@ - When a user creates a context which
+--     changes the severity (Python).
+-- -   @anrError@ - Whenever an ANR is detected (Android).
+-- -   @appHang@ - Whenever an app hang is detected (Cocoa).
+-- -   @outOfMemory@ - When an app is terminated because it used too much
+--     memory (Cocoa).
+-- -   @thermalKill@ - When an app is terminated because the device is
+--     overheating (Cocoa).
+data SeverityReasonType
+  = UnhandledException
+  | UnhandledError
+  | Log
+  | Signal
+  | StrictMode
+  | UnhandledPromiseRejection
+  | CallbackErrorIntercept
+  | ErrorClass
+  | ExceptionClass
+  | UnhandledPanic
+  | UnhandledErrorMiddleware
+  | UnhandledExceptionMiddleware
+  | UserCallbackSetSeverity
+  | UserSpecifiedSeverity
+  | HandledException
+  | HandledError
+  | HandledPanic
+  | UserContextSetSeverity
+  | AnrError
+  | AppHang
+  | OutOfMemory
+  | ThermalKill
+  deriving stock (Bounded, Enum, Eq, Generic, Show)
+
+instance FromJSON SeverityReasonType where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON SeverityReasonType where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+exampleSeverityReasonType :: SeverityReasonType
+exampleSeverityReasonType = UnhandledException
diff --git a/src/Data/Bugsnag/Types/Stackframe.hs b/src/Data/Bugsnag/Types/Stackframe.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Stackframe.hs
@@ -0,0 +1,108 @@
+module Data.Bugsnag.Types.Stackframe
+  ( Stackframe (..)
+  , exampleStackframe
+  , defaultStackframe
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+data Stackframe = Stackframe
+  { file :: Text
+  -- ^ The file that this stack frame was executing. It is recommended that you
+  -- strip any unnecessary or common information from the beginning of the
+  -- path.
+  , lineNumber :: Int
+  -- ^ The line of the file that this frame of the stack was in.
+  , columnNumber :: Maybe Int
+  -- ^ The column of the file that this frame of the stack was in.
+  , method :: Text
+  -- ^ The method that this particular stack frame is within.
+  , inProject :: Maybe Bool
+  -- ^ If this stacktrace line is in the user\'s project code, set this to
+  -- true. It is useful for developers to be able to see which lines of a
+  -- stacktrace are within their own application, and which are within third
+  -- party libraries. This information is shown in the stacktrace on the
+  -- dashboard and is used to help group errors better.
+  , code :: Maybe (KeyMap Text)
+  , frameAddress :: Maybe Text
+  -- ^ The run time memory address of the instruction being executed (for the
+  -- first stack frame) or to which control will return after the function
+  -- call or subroutine completes.
+  , loadAddress :: Maybe Text
+  -- ^ The address in memory where the binary has been loaded at run time.
+  , isLR :: Maybe Bool
+  -- ^ True if @frameAddress@ corresponds to value of the Link Register. This
+  -- is sometimes the case for the second frame in a stack - e.g. for signals
+  -- and mach exceptions.
+  , isPC :: Maybe Bool
+  -- ^ True if @frameAddress@ corresponds to the Program Counter.
+  , symbolAddress :: Maybe Text
+  -- ^ The run time memory address of the symbol that most closely matches
+  -- @frameAddress@.
+  , machoFile :: Maybe Text
+  -- ^ The full path of the Mach-O binary (iOS\/macOS\/tvOS only).
+  , machoLoadAddress :: Maybe Text
+  -- ^ The address in memory where the Mach-O binary has been loaded at run
+  -- time. This may differ from @machoVMAddress@ due to Address Space Layout
+  -- Randomization. The difference between @machoLoadAddress@ and
+  -- @machoVMAddress@ is known as the \"slide\" (iOS\/macOS\/tvOS only).
+  , machoUUID :: Maybe Text
+  -- ^ A UUID which uniquely identifies this Mach-O binary (iOS\/macOS\/tvOS
+  -- only).
+  , machoVMAddress :: Maybe Text
+  -- ^ The memory address assigned to the Mach-O binary by the linker at build
+  -- time (iOS\/macOS\/tvOS only).
+  , codeIdentifier :: Maybe Text
+  -- ^ The ID of the executable\/library that the frame relates to.
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON Stackframe where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Stackframe where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultStackframe :: Text -> Int -> Text -> Stackframe
+defaultStackframe file lineNumber method =
+  Stackframe
+    { file
+    , lineNumber
+    , method
+    , columnNumber = Nothing
+    , inProject = Nothing
+    , code = Nothing
+    , frameAddress = Nothing
+    , loadAddress = Nothing
+    , isLR = Nothing
+    , isPC = Nothing
+    , symbolAddress = Nothing
+    , machoFile = Nothing
+    , machoLoadAddress = Nothing
+    , machoUUID = Nothing
+    , machoVMAddress = Nothing
+    , codeIdentifier = Nothing
+    }
+
+exampleStackframe :: Stackframe
+exampleStackframe =
+  Stackframe
+    { file = "controllers/auth/session_controller.rb"
+    , lineNumber = 1234
+    , columnNumber = Just 123
+    , method = "create"
+    , inProject = Just True
+    , code = Nothing
+    , frameAddress = Just "0x000000010d131040"
+    , loadAddress = Just "0x000000010d12e000"
+    , isLR = Nothing
+    , isPC = Just True
+    , symbolAddress = Just "0x000000010d130fe6"
+    , machoFile = Just "/System/Library/Frameworks/Foundation.framework/Foundation"
+    , machoLoadAddress = Just "0x000000010d12e000"
+    , machoUUID = Just "B3196052-BAF1-3EA5-AD28-F9E6ECCF0B03"
+    , machoVMAddress = Just "0x0000000100000000"
+    , codeIdentifier = Just "b6b529d5e58421a0fba0fa2c5c2c5f3e"
+    }
diff --git a/src/Data/Bugsnag/Types/Thread.hs b/src/Data/Bugsnag/Types/Thread.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/Thread.hs
@@ -0,0 +1,57 @@
+module Data.Bugsnag.Types.Thread
+  ( Thread (..)
+  , exampleThread
+  , defaultThread
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+import Data.Bugsnag.Types.ErrorType
+import Data.Bugsnag.Types.Stackframe
+
+data Thread = Thread
+  { id :: Maybe Text
+  -- ^ The id of the thread in your application.
+  , name :: Maybe Text
+  -- ^ A human readable name for the thread.
+  , errorReportingThread :: Maybe Bool
+  -- ^ If this is the thread that the error was reported from (either an
+  -- unhandled error or a call to bugsnag.notify), set this to true.
+  , stacktrace :: Maybe [Stackframe]
+  -- ^ An array of stacktrace objects. Each object represents one line in the
+  -- stacktrace of the thread at the point that the error occurred.
+  , state :: Maybe Text
+  -- ^ The state of the thread at the time of the error.
+  , type_ :: Maybe ErrorType
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON Thread where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON Thread where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultThread :: Thread
+defaultThread =
+  Thread
+    { id = Nothing
+    , name = Nothing
+    , errorReportingThread = Nothing
+    , stacktrace = Nothing
+    , state = Nothing
+    , type_ = Nothing
+    }
+
+exampleThread :: Thread
+exampleThread =
+  Thread
+    { id = Just "thread_id"
+    , name = Just "thread_name"
+    , errorReportingThread = Just True
+    , stacktrace = Just [exampleStackframe]
+    , state = Just "WAITING"
+    , type_ = Just exampleErrorType
+    }
diff --git a/src/Data/Bugsnag/Types/User.hs b/src/Data/Bugsnag/Types/User.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bugsnag/Types/User.hs
@@ -0,0 +1,46 @@
+module Data.Bugsnag.Types.User
+  ( User (..)
+  , exampleUser
+  , defaultUser
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+-- | Information about the user affected by the error. These fields are
+-- optional but highly recommended. To display custom user data alongside
+-- these standard fields on the dashboard, the custom data should be
+-- included in the @metaData@ object in a @user@ object.
+data User = User
+  { id :: Maybe Text
+  -- ^ A unique identifier for a user affected by this event. This could be any
+  -- distinct identifier that makes sense for your application\/platform.
+  , name :: Maybe Text
+  -- ^ The user\'s name, or a string you use to identify them.
+  , email :: Maybe Text
+  -- ^ The user\'s email address.
+  }
+  deriving stock (Eq, Generic, Show)
+
+instance FromJSON User where
+  parseJSON = genericParseJSON aesonOptions
+
+instance ToJSON User where
+  toJSON = genericToJSON aesonOptions
+  toEncoding = genericToEncoding aesonOptions
+
+defaultUser :: User
+defaultUser =
+  User
+    { id = Nothing
+    , name = Nothing
+    , email = Nothing
+    }
+
+exampleUser :: User
+exampleUser =
+  User
+    { id = Just "19"
+    , name = Just "Robert Hawkins"
+    , email = Just "bob@example.com"
+    }
diff --git a/test/Data/Bugsnag/Types/PayloadSpec.hs b/test/Data/Bugsnag/Types/PayloadSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Data/Bugsnag/Types/PayloadSpec.hs
@@ -0,0 +1,179 @@
+module Data.Bugsnag.Types.PayloadSpec
+  ( spec
+  )
+where
+
+import Data.Bugsnag.Types.Prelude
+
+import Data.Aeson
+import Data.Bugsnag.Types.Payload
+import Test.Hspec
+
+spec :: Spec
+spec = do
+  describe "ToJSON" $ do
+    it "renders" $ do
+      encode examplePayload
+        `shouldBe` mconcat
+          [ "{\"apiKey\":\"YOUR-NOTIFIER-API-KEY\""
+          , ",\"payloadVersion\":\"5\""
+          , ",\"notifier\":{\"name\":\"Bugsnag Ruby\""
+          , ",\"version\":\"1.0.11\""
+          , ",\"url\":\"https://github.com/bugsnag/bugsnag-ruby\""
+          , ",\"dependencies\":[{\"name\":\"Bugsnag Android\""
+          , ",\"version\":\"2.1.10\""
+          , ",\"url\":\"https://github.com/bugsnag/bugsnag-android\"}]}"
+          , ",\"events\":[{\"exceptions\":[{\"errorClass\":\"NoMethodError\""
+          , ",\"message\":\"Unable to connect to database\""
+          , ",\"stacktrace\":[{\"file\":\"controllers/auth/session_controller.rb\""
+          , ",\"lineNumber\":1234"
+          , ",\"columnNumber\":123"
+          , ",\"method\":\"create\""
+          , ",\"inProject\":true"
+          , ",\"frameAddress\":\"0x000000010d131040\""
+          , ",\"loadAddress\":\"0x000000010d12e000\""
+          , ",\"isPC\":true"
+          , ",\"symbolAddress\":\"0x000000010d130fe6\""
+          , ",\"machoFile\":\"/System/Library/Frameworks/Foundation.framework/Foundation\""
+          , ",\"machoLoadAddress\":\"0x000000010d12e000\""
+          , ",\"machoUUID\":\"B3196052-BAF1-3EA5-AD28-F9E6ECCF0B03\""
+          , ",\"machoVMAddress\":\"0x0000000100000000\""
+          , ",\"codeIdentifier\":\"b6b529d5e58421a0fba0fa2c5c2c5f3e\"}]"
+          , ",\"type\":\"android\"}]"
+          , ",\"breadcrumbs\":[{\"timestamp\":\"2016-07-19T12:17:27-0700\""
+          , ",\"name\":\"Open menu\""
+          , ",\"type\":\"navigation\"}]"
+          , ",\"request\":{\"clientIp\":\"192.168.44.0\""
+          , ",\"httpMethod\":\"PUT\""
+          , ",\"url\":\"http://example.com/users/19/settings\""
+          , ",\"referer\":\"http://example.com?q=awesome\"}"
+          , ",\"threads\":[{\"id\":\"thread_id\""
+          , ",\"name\":\"thread_name\""
+          , ",\"errorReportingThread\":true"
+          , ",\"stacktrace\":[{\"file\":\"controllers/auth/session_controller.rb\""
+          , ",\"lineNumber\":1234"
+          , ",\"columnNumber\":123"
+          , ",\"method\":\"create\""
+          , ",\"inProject\":true"
+          , ",\"frameAddress\":\"0x000000010d131040\""
+          , ",\"loadAddress\":\"0x000000010d12e000\""
+          , ",\"isPC\":true"
+          , ",\"symbolAddress\":\"0x000000010d130fe6\""
+          , ",\"machoFile\":\"/System/Library/Frameworks/Foundation.framework/Foundation\""
+          , ",\"machoLoadAddress\":\"0x000000010d12e000\""
+          , ",\"machoUUID\":\"B3196052-BAF1-3EA5-AD28-F9E6ECCF0B03\""
+          , ",\"machoVMAddress\":\"0x0000000100000000\""
+          , ",\"codeIdentifier\":\"b6b529d5e58421a0fba0fa2c5c2c5f3e\"}]"
+          , ",\"state\":\"WAITING\""
+          , ",\"type\":\"android\"}]"
+          , ",\"context\":\"auth/session#create\""
+          , ",\"groupingHash\":\"buggy_file.rb\""
+          , ",\"unhandled\":true"
+          , ",\"severity\":\"error\""
+          , ",\"severityReason\":{\"type\":\"unhandledexception\""
+          , ",\"attributes\":{\"errorType\":\"E_ERROR\""
+          , ",\"level\":\"Error\""
+          , ",\"signalType\":\"SIGSEGV\""
+          , ",\"violationType\":\"NetworkOnMainThread\""
+          , ",\"errorClass\":\"ActiveRecord::RecordNotFound\""
+          , ",\"framework\":\"Rack\""
+          , ",\"exceptionClass\":\"TypeMismatchException\"}"
+          , ",\"unhandledOverridden\":false}"
+          , ",\"projectPackages\":[\"com.example.package1\""
+          , ",\"com.example.package2\"]"
+          , ",\"user\":{\"id\":\"19\""
+          , ",\"name\":\"Robert Hawkins\""
+          , ",\"email\":\"bob@example.com\"}"
+          , ",\"app\":{\"id\":\"com.bugsnag.android.example.debug\""
+          , ",\"version\":\"1.1.3\""
+          , ",\"versionCode\":12"
+          , ",\"bundleVersion\":\"1.0.2\""
+          , ",\"codeBundleId\":\"1.0-1234\""
+          , ",\"buildUUID\":\"BE5BA3D0-971C-4418-9ECF-E2D1ABCB66BE\""
+          , ",\"releaseStage\":\"staging\""
+          , ",\"type\":\"rails\""
+          , ",\"dsymUUIDs\":[\"e6173678256785afd940392abee\"]"
+          , ",\"duration\":1275"
+          , ",\"durationInForeground\":983"
+          , ",\"inForeground\":true"
+          , ",\"isLaunching\":true"
+          , ",\"binaryArch\":\"x86\""
+          , ",\"runningOnRosetta\":true}"
+          , ",\"device\":{\"hostname\":\"web1.internal\""
+          , ",\"id\":\"fd124e87760c4281aef\""
+          , ",\"manufacturer\":\"LGE\""
+          , ",\"model\":\"Nexus 6P\""
+          , ",\"modelNumber\":\"600\""
+          , ",\"osName\":\"android\""
+          , ",\"osVersion\":\"8.0.1\""
+          , ",\"freeMemory\":183879616"
+          , ",\"totalMemory\":201326592"
+          , ",\"freeDisk\":13478064128"
+          , ",\"browserName\":\"Chrome\""
+          , ",\"browserVersion\":\"61.0.3163.100\""
+          , ",\"orientation\":\"portrait\""
+          , ",\"time\":\"2018-08-07T10:16:34.564Z\""
+          , ",\"cpuAbi\":[\"x86_64\"]"
+          , ",\"runtimeVersions\":{\"androidApiLevel\":\"28\""
+          , ",\"bottle\":\"0.12.8\""
+          , ",\"celery\":\"4.2.0\""
+          , ",\"clangVersion\":\"7.0.1\""
+          , ",\"cocos2dx\":\"3.17.2\""
+          , ",\"delayedJob\":\"4.1.8\""
+          , ",\"django\":\"2.1.7\""
+          , ",\"dotnet\":\".NET Framework 4.7.3416.0\""
+          , ",\"dotnetApiCompatibility\":\".NET 2.0 Subset\""
+          , ",\"dotnetClr\":\"4.0.30319.17379\""
+          , ",\"dotnetScriptingRuntime\":\".NET 3.5 equivalent\""
+          , ",\"electron\":\"11.4.3\""
+          , ",\"eventMachine\":\"1.2.7\""
+          , ",\"expoApp\":\"2.10.0.105125\""
+          , ",\"expoSdk\":\"32.0.0\""
+          , ",\"flask\":\"1.0.2\""
+          , ",\"gin\":\"1.3.0\""
+          , ",\"go\":\"1.11.2\""
+          , ",\"javaType\":\"Java(TM) SE Runtime Environment\""
+          , ",\"javaVersion\":\"1.8.0_131-b11\""
+          , ",\"jruby\":\"9.2.5.0\""
+          , ",\"laravel\":\"5.7.22\""
+          , ",\"lumen\":\"5.7\""
+          , ",\"magento\":\"2.3.0\""
+          , ",\"mailman\":\"0.8.0\""
+          , ",\"martini\":\"1.0\""
+          , ",\"negroni\":\"1.0.0\""
+          , ",\"node\":\"10.14.1\""
+          , ",\"osBuild\":\"16D57\""
+          , ",\"php\":\"7.2.12\""
+          , ",\"python\":\"3.7.1\""
+          , ",\"que\":\"0.14.3\""
+          , ",\"rack\":\"2.0.6\""
+          , ",\"rails\":\"5.2.2\""
+          , ",\"rake\":\"12.3.2\""
+          , ",\"reactNative\":\"0.57.5\""
+          , ",\"reactNativeJsEngine\":\"hermes\""
+          , ",\"resque\":\"2.0.0\""
+          , ",\"revel\":\"0.21.0\""
+          , ",\"ruby\":\"2.5.3\""
+          , ",\"shoryoken\":\"5.0.1\""
+          , ",\"sidekiq\":\"5.1.3\""
+          , ",\"silex\":\"2.1.0\""
+          , ",\"sinatra\":\"2.0.3\""
+          , ",\"springBoot\":\"1.5.13.RELEASE\""
+          , ",\"springFramework\":\"4.2.13.RELEASE\""
+          , ",\"swift\":\"4.2\""
+          , ",\"symfony\":\"4.2.11\""
+          , ",\"tornado\":\"5.1.1\""
+          , ",\"unity\":\"2018.2.17\""
+          , ",\"unityScriptingBackend\":\"Mono\""
+          , ",\"wordpress\":\"5.1\"}"
+          , ",\"macCatalystiOSVersion\":\"14.0\"}"
+          , ",\"session\":{\"id\":\"9f65c975-8155-456f-91e5-c4c4b3db0555\""
+          , ",\"startedAt\":\"2017-01-01T14:30:00.000Z\""
+          , ",\"events\":{\"handled\":1"
+          , ",\"unhandled\":1}}"
+          , ",\"featureFlags\":[{\"featureFlag\":\"share_ios_photo_sharing_button\""
+          , ",\"variant\":\"blue_button\"}]}]}"
+          ]
+
+    it "round-trips" $ do
+      eitherDecode (encode examplePayload) `shouldBe` Right examplePayload
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+{-# OPTIONS_GHC -Wno-missing-export-lists #-}
