{"record":{"id":"8faae07376c89c83","repo":"signalapp/Signal-Server","slug":"receipt-credential-request-failed-verification-8faae0","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/OneTimeDonationController.java","lineNumber":402,"sourceCode":"      throw new BadRequestException(\"invalid receipt credential request\", e);\n    }\n    final Instant paidAt = oneTimeDonationsManager.getPaidAt(request.processor, paymentDetails.id(), paymentDetails.created());\n    final Instant expiration = paidAt\n        .plus(levelDetails.levelExpiration())\n        .truncatedTo(ChronoUnit.DAYS)\n        .plus(1, ChronoUnit.DAYS);\n    try {\n      issuedReceiptsManager.recordOneTimeIssuance(paymentDetails.id(), request.processor,\n          receiptCredentialRequest, expiration);\n    } catch (WriteConflictException _) {\n      throw new WebApplicationException(Response.Status.CONFLICT);\n    }\n    final ReceiptCredentialResponse receiptCredentialResponse;\n    try {\n      receiptCredentialResponse = zkReceiptOperations.issueReceiptCredential(\n          receiptCredentialRequest, expiration.getEpochSecond(), levelDetails.level().getValue());\n    } catch (final VerificationFailedException e) {\n      throw new BadRequestException(\"receipt credential request failed verification\", e);\n    }\n    Metrics.counter(SubscriptionController.RECEIPT_ISSUED_COUNTER_NAME,\n            Tags.of(\n                Tag.of(SubscriptionController.PROCESSOR_TAG_NAME, request.processor.toString()),\n                Tag.of(SubscriptionController.TYPE_TAG_NAME, \"boost\"),\n                UserAgentTagUtil.getPlatformTag(userAgent)))\n        .increment();\n    return Response.ok(\n            new CreateBoostReceiptCredentialsSuccessResponse(receiptCredentialResponse.serialize()))\n        .build();\n  }\n}\n","sourceCodeStart":384,"sourceCodeEnd":415,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/OneTimeDonationController.java#L384-L415","documentation":"Receipt credential requests carry a zero-knowledge proof that is verified server-side before a receipt is issued. If issueReceiptCredential throws VerificationFailedException, the proof did not verify against the server's secret keys, so the request is rejected with a 400 BadRequest. This means the request bytes parse but are cryptographically invalid (tampered, replayed, or for a different server).","triggerScenarios":"POST to the boost donation endpoint with a ReceiptCredentialRequest whose ZK proof fails verification — e.g. modified expiration/level fields, credentials generated against different server parameters, or a replayed request.","commonSituations":"Client built against a staging server's public parameters but pointed at production; tampered or reconstructed credential requests; protocol version drift between client and server key material.","solutions":["Generate the ReceiptCredentialRequest fresh using the current production server's public parameters","Do not modify any fields of the credential request after generation","Ensure the client is compiled against the same serverSecretParams/public keys the server actually uses"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// client-side: generate and locally verify the proof before sending\nReceiptCredentialRequest req = zkReceiptOperations.createReceiptCredentialRequest(publicParams, receiptSerial, amount);\n// ensure it was created against the CURRENT server public parameters","typeGuard":null,"tryCatchPattern":"try { /* API call */ } catch (BadRequestException e) { if (e.getMessage().contains(\"failed verification\")) { rebuildWithCurrentServerParams(); } }","preventionTips":["Never mutate a ReceiptCredentialRequest after creation","Use production server public parameters for production requests","Regenerate requests rather than replaying old ones"],"tags":["zkp","verification-failed","bad-request","donations"],"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"}