diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py
index 46a495ffdc06ab2517f3d69aecd9f3724bbc383b..9d9663b92b0b9a8f7c4b24a83db3653c27801ba1 100644
--- a/src/_zkapauthorizer/controller.py
+++ b/src/_zkapauthorizer/controller.py
@@ -137,7 +137,7 @@ class IRedeemer(Interface):
     """
     An ``IRedeemer`` can exchange a voucher for one or more passes.
     """
-    def random_tokens_for_voucher(voucher, count):
+    def random_tokens_for_voucher(voucher, counter, count):
         """
         Generate a number of random tokens to use in the redemption process for
         the given voucher.
@@ -212,8 +212,8 @@ class NonRedeemer(object):
     def make(cls, section_name, node_config, announcement, reactor):
         return cls()
 
-    def random_tokens_for_voucher(self, voucher, count):
-        return dummy_random_tokens(voucher, count)
+    def random_tokens_for_voucher(self, voucher, counter, count):
+        return dummy_random_tokens(voucher, counter, count)
 
     def redeemWithCounter(self, voucher, counter, random_tokens):
         # Don't try to redeem them.
@@ -242,8 +242,8 @@ class ErrorRedeemer(object):
         ).decode("ascii")
         return cls(details)
 
-    def random_tokens_for_voucher(self, voucher, count):
-        return dummy_random_tokens(voucher, count)
+    def random_tokens_for_voucher(self, voucher, counter, count):
+        return dummy_random_tokens(voucher, counter, count)
 
     def redeemWithCounter(self, voucher, counter, random_tokens):
         return fail(Exception(self.details))
@@ -265,8 +265,8 @@ class DoubleSpendRedeemer(object):
     def make(cls, section_name, node_config, announcement, reactor):
         return cls()
 
-    def random_tokens_for_voucher(self, voucher, count):
-        return dummy_random_tokens(voucher, count)
+    def random_tokens_for_voucher(self, voucher, counter, count):
+        return dummy_random_tokens(voucher, counter, count)
 
     def redeemWithCounter(self, voucher, counter, random_tokens):
         return fail(AlreadySpent(voucher))
@@ -283,21 +283,21 @@ class UnpaidRedeemer(object):
     def make(cls, section_name, node_config, announcement, reactor):
         return cls()
 
-    def random_tokens_for_voucher(self, voucher, count):
-        return dummy_random_tokens(voucher, count)
+    def random_tokens_for_voucher(self, voucher, counter, count):
+        return dummy_random_tokens(voucher, counter, count)
 
     def redeemWithCounter(self, voucher, counter, random_tokens):
         return fail(Unpaid(voucher))
 
 
-def dummy_random_tokens(voucher, count):
+def dummy_random_tokens(voucher, counter, count):
     v = urlsafe_b64decode(voucher.number.encode("ascii"))
     def dummy_random_token(n):
         return RandomToken(
             # Padding is 96 (random token length) - 32 (decoded voucher
-            # length)
+            # length) - 4 (fixed-width counter)
             b64encode(
-                v + u"{:0>64}".format(n).encode("ascii"),
+                v + u"{:0>4}{:0>60}".format(counter, n).encode("ascii"),
             ).decode("ascii"),
         )
     return list(
@@ -321,12 +321,12 @@ class DummyRedeemer(object):
     def make(cls, section_name, node_config, announcement, reactor):
         return cls()
 
-    def random_tokens_for_voucher(self, voucher, count):
+    def random_tokens_for_voucher(self, voucher, counter, count):
         """
         Generate some number of random tokens to submit along with a voucher for
         redemption.
         """
-        return dummy_random_tokens(voucher, count)
+        return dummy_random_tokens(voucher, counter, count)
 
     def redeemWithCounter(self, voucher, counter, random_tokens):
         """
@@ -440,7 +440,7 @@ class RistrettoRedeemer(object):
             URL.from_text(configured_issuer),
         )
 
-    def random_tokens_for_voucher(self, voucher, count):
+    def random_tokens_for_voucher(self, voucher, counter, count):
         return list(
             RandomToken(
                 challenge_bypass_ristretto.RandomToken.create().encode_base64().decode("ascii"),
@@ -748,6 +748,7 @@ class PaymentController(object):
             )
             return self.redeemer.random_tokens_for_voucher(
                 Voucher(voucher),
+                counter,
                 num_tokens,
             )
 
diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py
index 9c618973a3ae0ca633e8b4416cc1dda25ab2e19b..678d0d7d6c25524413c248d9048cc0b948dd8409 100644
--- a/src/_zkapauthorizer/tests/test_controller.py
+++ b/src/_zkapauthorizer/tests/test_controller.py
@@ -416,7 +416,7 @@ class RistrettoRedeemerTests(TestCase):
         issuer = RistrettoRedemption(signing_key)
         treq = treq_for_loopback_ristretto(issuer)
         redeemer = RistrettoRedeemer(treq, NOWHERE)
-        random_tokens = redeemer.random_tokens_for_voucher(voucher, num_tokens)
+        random_tokens = redeemer.random_tokens_for_voucher(voucher, counter, num_tokens)
         d = redeemer.redeemWithCounter(
             voucher,
             counter,
@@ -450,7 +450,7 @@ class RistrettoRedeemerTests(TestCase):
         issuer = AlreadySpentRedemption()
         treq = treq_for_loopback_ristretto(issuer)
         redeemer = RistrettoRedeemer(treq, NOWHERE)
-        random_tokens = redeemer.random_tokens_for_voucher(voucher, num_tokens)
+        random_tokens = redeemer.random_tokens_for_voucher(voucher, counter, num_tokens)
         d = redeemer.redeemWithCounter(
             voucher,
             counter,
@@ -477,7 +477,7 @@ class RistrettoRedeemerTests(TestCase):
         issuer = UnpaidRedemption()
         treq = treq_for_loopback_ristretto(issuer)
         redeemer = RistrettoRedeemer(treq, NOWHERE)
-        random_tokens = redeemer.random_tokens_for_voucher(voucher, num_tokens)
+        random_tokens = redeemer.random_tokens_for_voucher(voucher, counter, num_tokens)
         d = redeemer.redeemWithCounter(
             voucher,
             counter,
@@ -510,7 +510,7 @@ class RistrettoRedeemerTests(TestCase):
 
         treq = treq_for_loopback_ristretto(issuer)
         redeemer = RistrettoRedeemer(treq, NOWHERE)
-        random_tokens = redeemer.random_tokens_for_voucher(voucher, num_tokens)
+        random_tokens = redeemer.random_tokens_for_voucher(voucher, counter, num_tokens)
         d = redeemer.redeemWithCounter(
             voucher,
             counter,
@@ -540,7 +540,7 @@ class RistrettoRedeemerTests(TestCase):
         treq = treq_for_loopback_ristretto(issuer)
         redeemer = RistrettoRedeemer(treq, NOWHERE)
 
-        random_tokens = redeemer.random_tokens_for_voucher(voucher, num_tokens)
+        random_tokens = redeemer.random_tokens_for_voucher(voucher, counter, num_tokens)
         d = redeemer.redeemWithCounter(
             voucher,
             counter,