{"record":{"id":"816a612501e935ba","repo":"signalapp/Signal-Server","slug":"receipt-credential-request-failed-verification-816a61","errorCode":null,"errorMessage":"receipt credential request failed verification","messagePattern":"receipt credential request failed verification","errorType":"exception","errorClass":"SubscriptionInvalidArgumentsException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/storage/SubscriptionManager.java","lineNumber":223,"sourceCode":"      receiptCredentialRequest = new ReceiptCredentialRequest(receiptCredentialRequestBytes);\n    } catch (final InvalidInputException e) {\n      throw new SubscriptionInvalidArgumentsException(\"invalid receipt credential request\", e);\n    }\n\n    final PaymentProvider processor = record.getProcessorCustomer().orElseThrow().processor();\n    final SubscriptionPaymentProcessor manager = getProcessor(processor);\n    final SubscriptionPaymentProcessor.ReceiptItem receipt = manager.getReceiptItem(record.subscriptionId);\n    final Instant expirationInstant = expiration.apply(receipt);\n    final ReceiptCredentialResponse receiptCredentialResponse;\n    try {\n      issuedReceiptsManager\n          .recordIssuance(receipt.itemId(), manager.getProvider(), receiptCredentialRequest, expirationInstant);\n      receiptCredentialResponse = zkReceiptOperations.issueReceiptCredential(\n          receiptCredentialRequest,\n          expirationInstant.getEpochSecond(),\n          receipt.level());\n    } catch (final VerificationFailedException e) {\n      throw new SubscriptionInvalidArgumentsException(\"receipt credential request failed verification\", e);\n    } catch (final WriteConflictException _) {\n      throw new SubscriptionReceiptAlreadyRedeemedException();\n    }\n    return new ReceiptResult(receiptCredentialResponse, receipt, processor);\n  }\n\n  /**\n   * Add a payment method to a customer in a payment processor and update the table.\n   * <p>\n   * If the customer does not exist in the table, a customer is created via the subscriptionPaymentProcessor and added\n   * to the table. Not all payment processors support server-managed customers, so a payment processor that implements\n   * {@link CustomerAwareSubscriptionPaymentProcessor} must be passed in.\n   *\n   * @param subscriberCredentials        Subscriber credentials derived from the subscriberId\n   * @param subscriptionPaymentProcessor A customer-aware payment processor to use. If the subscriber already has a\n   *                                     payment processor, it must match the existing one.\n   * @param clientPlatform               The platform of the client making the request\n   * @param paymentSetupFunction         A function that takes the payment processor and the customer ID and begins","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/storage/SubscriptionManager.java#L205-L241","documentation":"createReceiptCredentials throws SubscriptionInvalidArgumentsException(\"receipt credential request failed verification\") when the zero-knowledge receipt issuance step (issueReceiptCredential or recordIssuance) raises VerificationFailedException. The request parsed fine but its cryptographic proof did not verify against the server's receipt credential public parameters/issuer key.","triggerScenarios":"Calling createReceiptCredentials where zkReceiptOperations.issueReceiptCredential rejects the ReceiptCredentialRequest proof, or recordIssuance fails verification, for a given subscription record and receipt level.","commonSituations":"Server rotated its ZK issuer key/parameters while the client built the request against old parameters; client and server zk library versions disagree on the proof format; a crafted/replayed request from a non-genuine client.","solutions":["Regenerate the ReceiptCredentialRequest with the latest client library so it is built against the server's current receipt credential parameters.","Confirm server zk ReceiptCredentialOperations issuer keys are unchanged/consistently configured; if keys were rotated, redeploy clients with the new public parameters.","Check client and server use compatible versions of the receipts/zk library; upgrade whichever is stale."],"exampleFix":"// before\nReceiptCredentialRequest req = new ReceiptCredentialRequest(legacySerialized); // built with old params\n// after\nReceiptCredentialRequest req = ReceiptCredentialRequestFactory.create(currentParams, receiptLevel, expiration);","handlingStrategy":"try-catch","validationCode":"// client-side: ensure request built against the server's current public parameters\nif (!paramsVersion.equals(SERVER_CURRENT_PARAMS_VERSION)) {\n  refreshReceiptParameters();\n}","typeGuard":null,"tryCatchPattern":"try {\n  manager.createReceiptCredentials(creds, requestBytes);\n} catch (SubscriptionInvalidArgumentsException e) {\n  // verification failed: rebuild request with current zk parameters and retry once\n} catch (SubscriptionReceiptAlreadyRedeemedException e) {\n  // distinct failure: request already redeemed\n}","preventionTips":["Ship receipt public parameters with the client and rotate them in lockstep with the server","Pin zk receipt library versions across client and server","Monitor VerificationFailedException spikes after server key rotations"],"tags":["zk-receipts","cryptography","verification"],"backgroundTag":"verification-failed","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}