{"record":{"id":"bb0544808be3d164","repo":"signalapp/Signal-Server","slug":"receipt-credential-request-failed-verification","errorCode":null,"errorMessage":"receipt credential request failed verification","messagePattern":"receipt credential request failed verification","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/LoginPurchaseController.java","lineNumber":131,"sourceCode":"    }\n\n    final ReceiptCredentialRequest receiptCredentialRequest;\n    try {\n      receiptCredentialRequest = new ReceiptCredentialRequest(request.receiptCredentialRequest);\n    } catch (final InvalidInputException e) {\n      throw new BadRequestException(\"invalid receipt credential request\", e);\n    }\n\n    try {\n      final ReceiptCredentialResponse receiptCredentialResponse = loginPurchaseManager.generateReceipt(\n          request.paymentProvider, request.purchaseIdentifier, receiptCredentialRequest);\n      return Response.ok(\n              new CreateLoginReceiptCredentialResponse(receiptCredentialResponse.serialize()))\n          .build();\n    } catch (SubscriptionReceiptRequestedForOpenPaymentException e) {\n      return Response.noContent().build();\n    } catch (VerificationFailedException e) {\n      throw new BadRequestException(\"receipt credential request failed verification\", e);\n    }\n  }\n}\n","sourceCodeStart":113,"sourceCodeEnd":135,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/LoginPurchaseController.java#L113-L135","documentation":"The receipt credential request parsed but failed zk verification (SubscriptionReceiptRequestedForOpenPaymentException aside, a VerificationFailedException is converted to this 400). The server rejected the cryptographic proof embedded in the request, so no receipt credential response is returned.","triggerScenarios":"POST to the login receipt credential endpoint where loginPurchaseManager.generateReceipt(...) raises VerificationFailedException — i.e. the request's zk proof does not verify against the server's verification key.","commonSituations":"Client and server built with different zk parameters/keys (library version skew); receipt request constructed for a different server environment; corrupted proof bytes; replayed or tampered request.","solutions":["Regenerate the receipt credential request with the current libsignal version so the zk proof matches the server's verification key.","Verify the client targets the correct environment (staging vs production server keys).","Ensure the request bytes are not modified or truncated between client generation and server receipt."],"exampleFix":"// before\n// client sends stale request generated with old zk params -> server VerificationFailedException\n// after\nReceiptCredentialRequest rcr = client.createReceiptCredentialRequest(newReceiptRequest(), serverParams.getCurrentVerificationKey());\nsendToServer(rcr.serialize()); // fresh request verified against current server key","handlingStrategy":"retry","validationCode":"// ensure the request was generated against the current server key\nif (!receiptRequest.verifyAgainst(serverVerificationKey)) { regenerate before sending }","typeGuard":null,"tryCatchPattern":"try { /* send */ } catch (BadRequestException e) { if (e.getMessage().contains(\"failed verification\")) { regenerateReceiptRequest(); } }","preventionTips":["Keep client zk library version in sync with server expectations","Target the correct environment's verification key (staging vs prod)","Never mutate serialized request bytes after generation"],"tags":["http-400","zk-verification","receipt-credentials"],"backgroundTag":"verification-failed","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T14:17:13.074Z"}