diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py
index 62d65c89b447578dc28068a6a569148c16844c3f..d86a8b89178d69e3b24214ce2caa7beddbb52420 100644
--- a/src/_zkapauthorizer/controller.py
+++ b/src/_zkapauthorizer/controller.py
@@ -78,6 +78,14 @@ class Unpaid(Exception):
     """
 
 
+class UnrecognizedFailureReason(Exception):
+    """
+    An attempt was made to redeem a voucher and the response contained an unknown reason.
+
+    The redemption attempt may be automatically retried at some point.
+    """
+
+
 @attr.s
 class RedemptionResult(object):
     """
@@ -522,6 +530,8 @@ class RistrettoRedeemer(object):
             elif reason == u"unpaid":
                 raise Unpaid(voucher)
 
+            raise UnrecognizedFailureReason()
+
         self._log.info(
             "Redeemed: {public_key} {proof} {count}",
             public_key=result[u"public-key"],
diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py
index a87548dfedcc3fc31d8d638c68a790b62c974a8e..51aa113fa6da7f1ccce4db2f61999f89a49e3908 100644
--- a/src/_zkapauthorizer/tests/test_controller.py
+++ b/src/_zkapauthorizer/tests/test_controller.py
@@ -58,6 +58,7 @@ from twisted.web.resource import ErrorPage, Resource
 from zope.interface import implementer
 
 from ..controller import (
+    UnrecognizedFailureReason,
     AlreadySpent,
     DoubleSpendRedeemer,
     ErrorRedeemer,
@@ -885,6 +886,34 @@ class RistrettoRedeemerTests(TestCase):
             ),
         )
 
+    @given(voucher_objects(), voucher_counters(), integers(min_value=0, max_value=100))
+    def test_redemption_unknown_response(self, voucher, counter, extra_tokens):
+        """
+        If the issuer returns a failure without a recognizable reason then
+        ``RistrettoRedeemer.redeemWithCounter`` returns a ``Deferred`` that
+        fails with ``UnrecognizedFailureReason``.
+        """
+        num_tokens = counter + extra_tokens
+        issuer = UnsuccessfulRedemption(u"mysterious")
+        treq = treq_for_loopback_ristretto(issuer)
+        redeemer = RistrettoRedeemer(treq, NOWHERE)
+        random_tokens = redeemer.random_tokens_for_voucher(voucher, counter, num_tokens)
+        d = redeemer.redeemWithCounter(
+            voucher,
+            counter,
+            random_tokens,
+        )
+        self.assertThat(
+            d,
+            failed(
+                AfterPreprocessing(
+                    lambda f: f.value,
+                    IsInstance(UnrecognizedFailureReason),
+                ),
+            ),
+        )
+
+
     @given(voucher_objects(), voucher_counters(), integers(min_value=0, max_value=100))
     def test_bad_ristretto_redemption(self, voucher, counter, extra_tokens):
         """