signalapp/Signal-Server · error · BadRequestException
invalid receipt credential request
Error message
invalid receipt credential request
What it means
The server could not deserialize the client-supplied receiptCredentialRequest bytes into a valid ReceiptCredentialRequest. This is thrown as a BadRequestException (HTTP 400) when the zk receipt credential request fails parsing with InvalidInputException. It means the bytes are malformed, not merely that purchase verification failed.
Solutions
- Regenerate the ReceiptCredentialRequest with a current, matching version of libsignal and ensure it is serialized correctly before sending.
- Check the client is sending the raw serialized bytes in request.receiptCredentialRequest, not a base64/hex string or re-encoded copy.
- Confirm the endpoint and request body shape match the current Signal API (CreateLoginReceiptCredentialRequest).
Example fix
// before
byte[] requestBytes = Base64.getDecoder().decode(userSuppliedString); // may be corrupt/misencoded
// after
ReceiptCredentialRequest rcr = new ReceiptCredentialRequest(requestBytes); // validate client-side before POST
if (!rcrVerifyOk(rcr)) { throw new IllegalArgumentException("malformed receipt credential request"); } Defensive patterns
Strategy: validation
Validate before calling
if (requestBytes == null || requestBytes.length == 0) throw new IllegalArgumentException("empty receipt credential request");
try { new ReceiptCredentialRequest(requestBytes); } catch (InvalidInputException e) { throw new IllegalArgumentException("malformed receipt credential request", e); } Try / catch
try { /* send request */ } catch (BadRequestException e) { if (e.getMessage().contains("invalid receipt credential request")) { regenerateRequestAndRetry(); } } Prevention
- Always generate receipt credential requests with a current libsignal version
- Never re-encode serialized bytes (avoid base64/hex round trips unless required)
- Validate the request deserializes client-side before POSTing
When it happens
Trigger: POST to the login purchase receipt endpoint with a body whose receiptCredentialRequest field is not a validly serialized ReceiptCredentialRequest (e.g. truncated, corrupted, or random bytes).
Common situations: Client library version mismatch or protocol changes; byte array mangled by encoding (base64 vs raw bytes, JSON escaping); manually constructed or hand-copied request bytes; buggy client code writing to the wrong field.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Invalid create call link credential request
- receipt credential request failed verification
- Invalid length
- Invalid protobuf entity
- Group send endorsement tokens should not be combined with…
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/40f743b13425a415.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/LoginPurchaseController.java:119
@ManagedAsync
public Response createLoginReceiptCredential(
@Auth final Optional<AuthenticatedDevice> authenticatedAccount,
@NotNull @Valid final CreateLoginReceiptCredentialRequest request)
throws IOException, SubscriptionPaymentRequiredException, SubscriptionInvalidArgumentsException, SubscriptionNotFoundException, RateLimitExceededException, SubscriptionReceiptAlreadyRedeemedException {
if (!dynamicConfigurationManager.getConfiguration().getLoginPurchaseConfiguration().enabled()) {
throw new BadRequestException("login purchases are not enabled");
}
if (authenticatedAccount.isPresent()) {
throw new ForbiddenException("must not use authenticated connection for login purchase operations");
}
final ReceiptCredentialRequest receiptCredentialRequest;
try {
receiptCredentialRequest = new ReceiptCredentialRequest(request.receiptCredentialRequest);
} catch (final InvalidInputException e) {
throw new BadRequestException("invalid receipt credential request", e);
}
try {
final ReceiptCredentialResponse receiptCredentialResponse = loginPurchaseManager.generateReceipt(
request.paymentProvider, request.purchaseIdentifier, receiptCredentialRequest);
return Response.ok(
new CreateLoginReceiptCredentialResponse(receiptCredentialResponse.serialize()))
.build();
} catch (SubscriptionReceiptRequestedForOpenPaymentException e) {
return Response.noContent().build();
} catch (VerificationFailedException e) {
throw new BadRequestException("receipt credential request failed verification", e);
}
}
}
View on GitHub (pinned to 100ab61c82)