signalapp/Signal-Server · error · BadRequestException
receipt credential request failed verification
Error message
receipt credential request failed verification
What it means
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).
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
Defensive patterns
Strategy: validation
Validate before calling
// client-side: generate and locally verify the proof before sending ReceiptCredentialRequest req = zkReceiptOperations.createReceiptCredentialRequest(publicParams, receiptSerial, amount); // ensure it was created against the CURRENT server public parameters
Try / catch
try { /* API call */ } catch (BadRequestException e) { if (e.getMessage().contains("failed verification")) { rebuildWithCurrentServerParams(); } } Prevention
- Never mutate a ReceiptCredentialRequest after creation
- Use production server public parameters for production requests
- Regenerate requests rather than replaying old ones
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cannot use play billing for one-time donations
- cannot use app store purchases for one-time donations
- invalid receipt credential request
- receipt credential presentation verification failed
- account does not have a phone number
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/8faae07376c89c83.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/OneTimeDonationController.java:402
throw new BadRequestException("invalid receipt credential request", e);
}
final Instant paidAt = oneTimeDonationsManager.getPaidAt(request.processor, paymentDetails.id(), paymentDetails.created());
final Instant expiration = paidAt
.plus(levelDetails.levelExpiration())
.truncatedTo(ChronoUnit.DAYS)
.plus(1, ChronoUnit.DAYS);
try {
issuedReceiptsManager.recordOneTimeIssuance(paymentDetails.id(), request.processor,
receiptCredentialRequest, expiration);
} catch (WriteConflictException _) {
throw new WebApplicationException(Response.Status.CONFLICT);
}
final ReceiptCredentialResponse receiptCredentialResponse;
try {
receiptCredentialResponse = zkReceiptOperations.issueReceiptCredential(
receiptCredentialRequest, expiration.getEpochSecond(), levelDetails.level().getValue());
} catch (final VerificationFailedException e) {
throw new BadRequestException("receipt credential request failed verification", e);
}
Metrics.counter(SubscriptionController.RECEIPT_ISSUED_COUNTER_NAME,
Tags.of(
Tag.of(SubscriptionController.PROCESSOR_TAG_NAME, request.processor.toString()),
Tag.of(SubscriptionController.TYPE_TAG_NAME, "boost"),
UserAgentTagUtil.getPlatformTag(userAgent)))
.increment();
return Response.ok(
new CreateBoostReceiptCredentialsSuccessResponse(receiptCredentialResponse.serialize()))
.build();
}
}
View on GitHub (pinned to 100ab61c82)