{"record":{"id":"cebd59f5ad3b3603","repo":"signalapp/Signal-Server","slug":"server-does-not-recognize-the-requested-receipt-le","errorCode":null,"errorMessage":"server does not recognize the requested receipt level","messagePattern":"server does not recognize the requested receipt level","errorType":"exception","errorClass":"BackupInvalidArgumentException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupAuthManager.java","lineNumber":261,"sourceCode":"  public void redeemReceipt(\n      final Account account,\n      final ReceiptCredentialPresentation receiptCredentialPresentation)\n      throws BackupBadReceiptException, BackupInvalidArgumentException, BackupMissingIdCommitmentException {\n    try {\n      serverZkReceiptOperations.verifyReceiptCredentialPresentation(receiptCredentialPresentation);\n    } catch (VerificationFailedException e) {\n      throw new BackupBadReceiptException(\"receipt credential presentation verification failed\");\n    }\n    final ReceiptSerial receiptSerial = receiptCredentialPresentation.getReceiptSerial();\n    final Instant receiptExpiration = Instant.ofEpochSecond(receiptCredentialPresentation.getReceiptExpirationTime());\n    if (clock.instant().isAfter(receiptExpiration)) {\n      throw new BackupBadReceiptException(\"receipt is already expired\");\n    }\n\n    final long receiptLevel = receiptCredentialPresentation.getReceiptLevel();\n\n    if (BackupLevelUtil.fromReceiptLevel(receiptLevel) != BackupLevel.PAID) {\n      throw new BackupInvalidArgumentException(\"server does not recognize the requested receipt level\");\n    }\n\n    if (account.getBackupCredentialRequest(BackupCredentialType.MEDIA).isEmpty()) {\n      throw new BackupMissingIdCommitmentException();\n    }\n\n    boolean receiptAllowed = redeemedReceiptsManager\n        .put(receiptSerial, receiptExpiration, receiptLevel, account.getAccountIdentifier());\n    if (!receiptAllowed) {\n      throw new BackupBadReceiptException(\"receipt serial is already redeemed\");\n    }\n    extendBackupVoucher(account, new Account.BackupVoucher(receiptLevel, receiptExpiration));\n  }\n\n  /**\n   * Extend the duration of the backup voucher on an account.\n   *\n   * @param account The account to update","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupAuthManager.java#L243-L279","documentation":"BackupInvalidArgumentException thrown in redeemReceipt when BackupLevelUtil.fromReceiptLevel(receiptLevel) does not resolve to BackupLevel.PAID. The server only recognizes receipt levels that map to the paid backup tier; any unknown or free-tier level is rejected before a voucher is created. It indicates the credential encodes an entitlement level the server will not honor for backups.","triggerScenarios":"Redeeming a ReceiptCredentialPresentation with a receiptLevel that maps to FREE or an unmapped numeric level (e.g. 0, test levels, or levels for other products) instead of the paid backup level.","commonSituations":"Clients compiled against newer/older receipt-level constants than the server; test or staging credentials used against production; receipts bought for a different subscription product; hand-crafted presentations in integration tests.","solutions":["Redeem a receipt issued for the paid backup tier (the level sold by the current donation/subscription flow)","Verify client receipt-level constants match the server's BackupLevelUtil mapping","Regenerate the presentation from a fresh purchase rather than a legacy credential","If you are the operator, check that server config/feature flags define the expected backup receipt levels"],"exampleFix":"// before\nlong level = 0; // free/test level\nredeemReceipt(presentationWithLevel(level)); // 400: server does not recognize the requested receipt level\n// after\nlong level = PAID_BACKUP_RECEIPT_LEVEL; // level from a real paid purchase\nredeemReceipt(presentationWithLevel(level));","handlingStrategy":"validation","validationCode":"if (BackupLevelUtil.fromReceiptLevel(presentation.getReceiptLevel()) != BackupLevel.PAID) {\n  throw new IllegalStateException(\"receipt level not valid for paid backups\");\n}","typeGuard":null,"tryCatchPattern":"try { redeemReceipt(presentation); }\ncatch (BackupInvalidArgumentException e) {\n  if (e.getMessage().contains(\"receipt level\")) { purchaseCorrectTierReceipt(); } else { throw e; }\n}","preventionTips":["Only redeem receipts from the official paid backup purchase flow","Keep client receipt-level constants in sync with server BackupLevelUtil","Never hardcode receipt-level numbers in tests; use the level from a real presentation","Re-validate tier mapping after client library upgrades"],"tags":["zk-receipt","backup-auth","receipt-level"],"backgroundTag":"invalid-enum-value","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"}