signalapp/Signal-Server · error · SubscriptionInvalidArgumentsException

invalid receipt credential request

Error message

invalid receipt credential request

What it means

createReceiptCredentials throws SubscriptionInvalidArgumentsException("invalid receipt credential request") when the client-supplied receiptCredentialRequest bytes cannot be parsed into a ReceiptCredentialRequest (InvalidInputException from the zk constructor). The payload is malformed, not merely wrong — it fails structural deserialization before any ZK verification.

Solutions

  1. Regenerate the ReceiptCredentialRequest client-side with a current, version-matched libsignal/zk receipt library and resend.
  2. Ensure the client sends the raw serialized bytes (correct base64 with padding intact) — no truncation, whitespace, or URL-safe alphabet mismatches.
  3. Log the byte length of the received payload and compare against the expected ReceiptCredentialRequest size to spot transport corruption.

Example fix

// before
String req = Base64.getEncoder().encodeToString(requestBytes).replace('+', '-'); // url-safe alphabet changed payload
// after
String req = Base64.getEncoder().encodeToString(new ReceiptCredentialRequest(serialized).serialize());
Defensive patterns

Strategy: validation

Validate before calling

byte[] decoded = Base64.getDecoder().decode(requestBase64);
if (decoded.length != new ReceiptCredentialRequest(validSample).serialize().length) {
  throw new IllegalArgumentException("receiptCredentialRequest has unexpected byte length");
}

Type guard

boolean isValidReceiptCredentialRequest(byte[] bytes) {
  try { new ReceiptCredentialRequest(bytes); return true; } catch (InvalidInputException e) { return false; }
}

Try / catch

try {
  manager.createReceiptCredentials(creds, requestBytes);
} catch (SubscriptionInvalidArgumentsException e) {
  log.warn("malformed receipt request — regenerate client-side");
}

Prevention

When it happens

Trigger: Posting a base64 receiptCredentialRequest body whose decoded bytes do not form a valid ReceiptCredentialRequest: wrong length, wrong serialization, garbage data, or bytes from a different protocol version.

Common situations: Client built the request with a mismatched libsignal/zk library version; the base64 payload got truncated or re-encoded by an intermediary; a test sent an arbitrary string instead of a real serialized request.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/b9879789a56b904f. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/storage/SubscriptionManager.java:207

   * @throws SubscriptionReceiptAlreadyRedeemedException         if the receipt was already redeemed by a different
   *                                                             request
   * @throws RateLimitExceededException                          if rate-limited
   */
  public ReceiptResult createReceiptCredentials(
      final SubscriberCredentials subscriberCredentials,
      final byte[] receiptCredentialRequestBytes,
      final Function<CustomerAwareSubscriptionPaymentProcessor.ReceiptItem, Instant> expiration)
      throws SubscriptionForbiddenException, SubscriptionNotFoundException, SubscriptionInvalidArgumentsException, SubscriptionPaymentRequiredException, RateLimitExceededException, SubscriptionReceiptRequestedForOpenPaymentException, SubscriptionReceiptAlreadyRedeemedException {
    final Subscriptions.Record record = getSubscriber(subscriberCredentials);
    if (record.subscriptionId == null) {
      throw new SubscriptionNotFoundException();
    }

    final ReceiptCredentialRequest receiptCredentialRequest;
    try {
      receiptCredentialRequest = new ReceiptCredentialRequest(receiptCredentialRequestBytes);
    } catch (final InvalidInputException e) {
      throw new SubscriptionInvalidArgumentsException("invalid receipt credential request", e);
    }

    final PaymentProvider processor = record.getProcessorCustomer().orElseThrow().processor();
    final SubscriptionPaymentProcessor manager = getProcessor(processor);
    final SubscriptionPaymentProcessor.ReceiptItem receipt = manager.getReceiptItem(record.subscriptionId);
    final Instant expirationInstant = expiration.apply(receipt);
    final ReceiptCredentialResponse receiptCredentialResponse;
    try {
      issuedReceiptsManager
          .recordIssuance(receipt.itemId(), manager.getProvider(), receiptCredentialRequest, expirationInstant);
      receiptCredentialResponse = zkReceiptOperations.issueReceiptCredential(
          receiptCredentialRequest,
          expirationInstant.getEpochSecond(),
          receipt.level());
    } catch (final VerificationFailedException e) {
      throw new SubscriptionInvalidArgumentsException("receipt credential request failed verification", e);
    } catch (final WriteConflictException _) {
      throw new SubscriptionReceiptAlreadyRedeemedException();

View on GitHub (pinned to 100ab61c82)