diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,12 @@
+# Changelog
+
+All notable changes to this package will be documented in this file.
+
+The format is based on
+[Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to the
+[Haskell Package Versioning Policy](https://pvp.haskell.org/).
+
+## 1.0.0.0 - 2018-12-20
+
+- Initial release
diff --git a/library/Stripe/Concepts.hs b/library/Stripe/Concepts.hs
new file mode 100644
--- /dev/null
+++ b/library/Stripe/Concepts.hs
@@ -0,0 +1,200 @@
+{-# OPTIONS_GHC -Wall #-}
+
+module Stripe.Concepts
+  (
+  -- * Modes
+    Mode (..), BothModes (..), applyMode
+  -- ** Conversion with Bool
+  , isLiveMode, isTestMode, isLiveMode', isTestMode'
+
+  -- * Keys
+  -- $keys
+  -- ** Publishable API key
+  , PublishableApiKey (..)
+  -- ** Secret API key
+  , ApiSecretKey (..), textToApiSecretKey
+  -- ** Webhook secret
+  , WebhookSecretKey (..), textToWebhookSecretKey
+
+  -- * Identifiers
+  , TokenId (..), CustomerId (..), ProductId (..)
+  , PlanId (..), SubscriptionId (..), InvoiceId (..)
+
+  ) where
+
+-- bytestring
+import qualified Data.ByteString
+
+-- text
+import qualified Data.Text
+import qualified Data.Text.Encoding
+
+------------------------------------------------------------
+
+{- | "To make the API as explorable as possible, accounts have test mode and
+live mode API keys. There is no 'switch' for changing between modes, just use
+the appropriate key to perform a live or test transaction. Requests made with
+test mode credentials never hit the banking networks and incur no cost." -
+<https://stripe.com/docs/api Stripe>
+
+This library provides functions to convert back and forth between 'Mode' and
+'Bool':
+
+- 'isLiveMode' (and its inverse, 'isLiveMode'')
+- 'isTestMode' (and its inverse, 'isTestMode'') -}
+
+data Mode = LiveMode | TestMode
+
+-- | LiveMode → True; TestMode → False
+isLiveMode :: Mode -> Bool
+isLiveMode LiveMode = True
+isLiveMode TestMode = False
+
+-- | True → LiveMode; False → TestMode
+isLiveMode' :: Bool -> Mode
+isLiveMode' True  = LiveMode
+isLiveMode' False = TestMode
+
+-- | LiveMode → False; TestMode → True
+isTestMode :: Mode -> Bool
+isTestMode LiveMode = False
+isTestMode TestMode = True
+
+-- | True → TestMode; False → LiveMode
+isTestMode' :: Bool -> Mode
+isTestMode' True  = TestMode
+isTestMode' False = LiveMode
+
+{- | A pair of values of the same type, one for live mode and one for test mode.
+
+For example, you may wish to use a value of type @'BothModes'
+'PublishableApiKey'@ to represent your publishable API keys for both live mode
+and test mode. -}
+
+data BothModes a = BothModes { liveMode :: a, testMode :: a }
+
+instance Functor BothModes
+  where
+    fmap f (BothModes a b) = BothModes (f a) (f b)
+
+applyMode :: Mode -> BothModes a -> a
+applyMode LiveMode = liveMode
+applyMode TestMode = testMode
+
+------------------------------------------------------------
+
+{- $keys
+
+Each Stripe account has a pair of API keys involved in making requests to the
+Stripe API:
+
+  * The publishable key ('PublishableApiKey')
+  * The secret key ('ApiSecretKey')
+
+Each webhook endpoint you set up has a "signing secret" ('WebhookSecretKey')
+that you use to verify the authenticity of the webhook events you receive *from*
+Stripe. -}
+
+{- | API secret keys are used to make requests to Stripe.
+
+"Authenticate your account when using the API by including your secret API key
+in the request. You can manage your API keys in the Dashboard. Your API keys
+carry many privileges, so be sure to keep them secret!" -
+<https://stripe.com/docs/api#authentication Stripe>
+
+The key is represented here as a 'Data.ByteString.ByteString', but you are
+likely have the data as a 'Data.Text.Text' value. You can use
+'textToApiSecretKey' to do this conversion. -}
+
+newtype ApiSecretKey = ApiSecretKey Data.ByteString.ByteString
+
+{- | Publishable API keys are used in client-side code.
+
+"Publishable API keys are meant solely to identify your account with Stripe,
+they aren’t secret. In other words, they can safely be published in places like
+your Stripe.js JavaScript code, or in an Android or iPhone app. Publishable keys
+only have the power to create tokens." - <https://stripe.com/docs/keys Stripe>
+-}
+
+newtype PublishableApiKey = PublishableApiKey Data.Text.Text
+
+{- | Webhook secrets are used to verify the authenticity of webhook events that
+you receive from Stripe.
+
+"Stripe can optionally sign the webhook events it sends to your endpoints. We do
+so by including a signature in each event’s Stripe-Signature header. This allows
+you to validate that the events were sent by Stripe, not by a third party. [...]
+Before you can verify signatures, you need to retrieve your endpoint’s secret
+from your Dashboard’s Webhooks settings. -
+<https://stripe.com/docs/webhooks/signatures Stripe>
+
+The key is represented here as a 'Data.ByteString.ByteString', but you are
+likely have the data as a 'Data.Text.Text' value. You can use
+'textToWebhookSecretKey' to do this conversion. -}
+
+newtype WebhookSecretKey = WebhookSecretKey Data.ByteString.ByteString
+
+{- | Convert a 'Data.Text.Text' representation of a Stripe API key (that looks
+something like @"sk_test_BQokikJOvBiI2HlWgH4olfQ2"@) to an 'ApiSecretKey'. -}
+
+textToApiSecretKey :: Data.Text.Text -> ApiSecretKey
+textToApiSecretKey = ApiSecretKey . Data.Text.Encoding.encodeUtf8
+
+{- | Convert a 'Data.Text.Text' representation of a Stripe webhook secret (that
+looks something like @"whsec_ojm5cmJMGMTw3w7ngjI7mgkRsFGLRtCt"@) to a
+'WebhookSecretKey'. -}
+
+textToWebhookSecretKey :: Data.Text.Text -> WebhookSecretKey
+textToWebhookSecretKey = WebhookSecretKey . Data.Text.Encoding.encodeUtf8
+
+------------------------------------------------------------
+
+{- | Identifier of a Stripe "token", which represents a payment source that was
+submitted by a user to Stripe.
+
+"This ensures that no sensitive card data touches your server, and allows your
+integration to operate in a PCI-compliant way." -
+<https://stripe.com/docs/api/tokens Stripe> -}
+
+newtype TokenId = TokenId Data.Text.Text deriving Eq
+
+{- | A customer identifier assigned by Stripe.
+
+"Customer objects allow you to perform recurring charges, and to track multiple
+charges, that are associated with the same customer." -
+<https://stripe.com/docs/api/customers Stripe> -}
+
+newtype CustomerId = CustomerId Data.Text.Text deriving Eq
+
+{- | The ID of a Stripe product.
+
+"Product objects describe items that your customers can subscribe to with a
+Subscription. An associated Plan determines the product pricing." -
+<https://stripe.com/docs/api/service_products Stripe> -}
+
+newtype ProductId = ProductId Data.Text.Text deriving Eq
+
+{- | The ID of a Stripe subscription plan.
+
+"Plans define the base price, currency, and billing cycle for subscriptions. For
+example, you might have a $5/month plan that provides limited access to your
+products, and a $15/month plan that allows full access." -
+<https://stripe.com/docs/api/plans Stripe> -}
+
+newtype PlanId = PlanId Data.Text.Text deriving Eq
+
+{- | Identifier for a customer's subscription to a product.
+
+"Subscriptions allow you to charge a customer on a recurring basis. A
+subscription ties a customer to a particular plan you've created." -
+<https://stripe.com/docs/api/subscriptions Stripe> -}
+
+newtype SubscriptionId = SubscriptionId Data.Text.Text deriving Eq
+
+{- | The ID of a Stripe invoice.
+
+"Invoices are statements of amounts owed by a customer, and are either generated
+one-off, or generated periodically from a subscription." -
+<https://stripe.com/docs/api/invoices Stripe> -}
+
+newtype InvoiceId = InvoiceId Data.Text.Text deriving Eq
diff --git a/license.txt b/license.txt
new file mode 100644
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,18 @@
+Copyright 2018 Typeclass Consulting, LLC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/stripe-concepts.cabal b/stripe-concepts.cabal
new file mode 100644
--- /dev/null
+++ b/stripe-concepts.cabal
@@ -0,0 +1,38 @@
+name: stripe-concepts
+version: 1.0.0.0
+
+synopsis: Types for the Stripe API
+category: Web
+
+description:
+    This is a minimal package that just defines a common set of types
+    for working with the <https://stripe.com/ Stripe> API.
+
+homepage:    https://github.com/typeclasses/stripe
+bug-reports: https://github.com/typeclasses/stripe/issues
+
+author:     Chris Martin
+maintainer: Chris Martin, Julie Moronuki
+
+copyright: 2018 Typeclass Consulting, LLC
+license: MIT
+license-file: license.txt
+
+build-type: Simple
+cabal-version: >=1.10
+tested-with: GHC==8.2.2, GHC==8.4.3, GHC==8.6.1
+
+extra-source-files:
+    changelog.md
+
+library
+    hs-source-dirs: library
+    default-language: Haskell2010
+
+    exposed-modules:
+        Stripe.Concepts
+
+    build-depends:
+        base >=4.10 && <5
+      , bytestring
+      , text
