diff --git a/src/PaymentServer/Redemption.hs b/src/PaymentServer/Redemption.hs
index 11acd39ac8e7a339faa2cad9967da064a629c128..35963cbb8d7e3183e3206acfa9fbd04ca52a6fba 100644
--- a/src/PaymentServer/Redemption.hs
+++ b/src/PaymentServer/Redemption.hs
@@ -9,6 +9,7 @@ module PaymentServer.Redemption
   ( RedemptionAPI
   , BlindedToken
   , Redeem(Redeem)
+  , Result(Failed, Succeeded)
   , redemptionServer
   ) where
 
@@ -28,7 +29,9 @@ import Data.Text.Encoding
   )
 import Data.Aeson
   ( ToJSON(toJSON, toEncoding)
-  , FromJSON
+  , FromJSON(parseJSON)
+  , withObject
+  , (.:)
   , genericToEncoding
   , defaultOptions
   , encode
@@ -80,11 +83,16 @@ instance ToJSON Result where
   toJSON Failed = object [ "success" .= False ]
   toJSON Succeeded = object [ "success" .= True ]
 
+instance FromJSON Result where
+    parseJSON = withObject "Result" $ \v ->
+      v .: "success" >>= \success ->
+      return $ if success then Succeeded else Failed
+
 type RedemptionAPI = ReqBody '[JSON] Redeem :> Post '[JSON] Result
 
 jsonErr400 = err400
   { errBody = encode Failed
-  , errHeaders = [ ("Content-Type", "application/json") ]
+  , errHeaders = [ ("Content-Type", "application/json;charset=utf-8") ]
   }
 
 redemptionServer :: VoucherDatabase d => d -> Server RedemptionAPI
diff --git a/test/SpecRedemption.hs b/test/SpecRedemption.hs
index 7c545b966c4852c4f7e46822c8ffd40658ce12f2..00b9f333888f277d1595b3b0abdeb0413f8af434 100644
--- a/test/SpecRedemption.hs
+++ b/test/SpecRedemption.hs
@@ -15,7 +15,9 @@ import Control.Exception
   ( bracket
   )
 import Data.Aeson
- ( encode
+ ( FromJSON
+ , decode
+ , encode
  )
 import Servant
   ( Application
@@ -33,9 +35,15 @@ import Test.Hspec
   , shouldBe
   , runIO
   )
+import Network.HTTP.Types
+  ( Header
+  )
 import Test.Hspec.Wai
-  ( ResponseMatcher(ResponseMatcher)
+  ( ResponseMatcher(matchBody, matchHeaders)
+  , (<:>)
   , WaiExpectation
+  , Body
+  , MatchBody(MatchBody)
   , with
   , shouldRespondWith
   , liftIO
@@ -63,6 +71,7 @@ import PaymentServer.Redemption
   ( RedemptionAPI
   , BlindedToken
   , Redeem(Redeem)
+  , Result(Failed, Succeeded)
   , redemptionServer
   )
 import PaymentServer.Persistence
@@ -132,15 +141,24 @@ spec_redemption = parallel $ do
 
 
   describe "redemption" $ do
-    with (return . app $ RefuseRedemption NotPaid) $
-      it "receives 400 (Invalid Request) when the voucher is not paid" $ property $
-      \(voucher :: Voucher) (tokens :: [BlindedToken]) ->
-        propertyRedeem path voucher tokens 400
-
-    with (return $ app PermitRedemption) $
-      it "receive 200 (OK) when redemption succeeds" $ property $
-      \(voucher :: Voucher) (tokens :: [BlindedToken]) ->
-        propertyRedeem path voucher tokens 200
+    with (return . app $ RefuseRedemption NotPaid) $ do
+      it "receives a failure response when the voucher is not paid" $ property $
+        \(voucher :: Voucher) (tokens :: [BlindedToken]) ->
+          propertyRedeem path voucher tokens 400
+          { matchBody = matchJSONBody Failed
+          -- major/minor, fine.  charset=utf-8... okay.  but really this is
+          -- overspecified by encoding the exact byte sequence.  I'd rather
+          -- assert semantic equality.
+          , matchHeaders = ["Content-Type" <:> "application/json;charset=utf-8"]
+          }
+
+    with (return $ app PermitRedemption) $ do
+      it "receive a success response when redemption succeeds" $ property $
+        \(voucher :: Voucher) (tokens :: [BlindedToken]) ->
+          propertyRedeem path voucher tokens 200
+          { matchBody = matchJSONBody Succeeded
+          , matchHeaders = ["Content-Type" <:> "application/json;charset=utf-8"]
+          }
 
     -- it "receive 200 (OK) when the voucher is paid and previously redeemed with the same tokens" $
     --   property $ \(voucher :: Voucher) (tokens :: [BlindedToken]) ->
@@ -155,3 +173,19 @@ spec_redemption = parallel $ do
     --     liftIO $ payForVoucher database voucher
     --     postJSON path (encode $ Redeem voucher firstTokens) `shouldRespondWith` 200
     --     postJSON path (encode $ Redeem voucher secondTokens) `shouldRespondWith` 400
+
+matchJSONBody :: Result -> MatchBody
+matchJSONBody expected =
+  let
+    bodyMatcher :: [Header] -> Body -> Maybe String
+    bodyMatcher headers actualBody =
+      case decode actualBody of
+        Nothing ->
+          Just $ "failed to decode body as value of expected type: " ++ show actualBody
+        Just actual ->
+          if actual == expected then
+            Nothing
+          else
+            Just $ "decoded body does not equal expected value: " ++ show actual ++ show expected
+  in
+    MatchBody bodyMatcher