Payment Request Invalid
payment_request_invalid400
The payment processor rejected the request as malformed. No charge was made.
What this means
We sent a payment request to the processor (Dodo) and it came back with an "invalid request" response — the request shape didn't match what the processor expected. The user was never charged. This is almost always a server-side issue on our end, not something the user can fix by retrying.
When you'll see this
- A plan or product configuration on our side drifted from what the processor has registered.
- A new field was added to the payment payload that the processor's current API version doesn't recognize.
- A currency mismatch between our request and the processor's expected currency for the user's region.
- A test/production environment mismatch (a sandbox payment payload sent to the live processor or vice versa).
Learn more about how this works
The processor returns this kind of rejection at the request validation layer, before any charge attempt. It's structurally a 4xx from the processor that we surface as a 400 from Asterwise. The distinguishing feature: the user did nothing wrong, the network is fine, the processor is up — but the shape of what we sent didn't pass schema validation on their end.
The most common gotcha: this error sometimes appears immediately after we ship a new plan or change pricing. It usually means we updated our side without updating the processor's product catalog. The fix is on us, not the user — but the user sees the error first.
Example response
{
"success": false,
"error": "payment_request_invalid",
"message": "Payment provider rejected the request as invalid.",
"details": [],
"retry_after": null,
"doc_url": "https://docs.asterwise.com/reference/errors/payment_request_invalid",
"request_id": "req_01HXYZABCDEFGH",
"timestamp": "2026-05-25T12:34:56Z"
}
- Don't retry — the same payload will keep being rejected.
- Refresh asterwise.com/dashboard and try the checkout flow from there (sometimes a stale browser session is involved).
- If it still fails, email [email protected] with the
request_id. This is one of the few errors that almost always needs us to fix something on our side.
Not retriable. Surface clearly and route to support.
Python:
Production handler
- Python
- TypeScript
import httpx
import logging
logger = logging.getLogger(__name__)
class PaymentRequestInvalidError(Exception):
"""Processor rejected the payment request shape. Server-side fix needed."""
def attempt_checkout(plan_id, base_url, headers):
response = httpx.post(
f"{base_url}/v1/billing/subscribe",
headers=headers,
json={"plan_id": plan_id},
timeout=15,
)
if response.status_code == 400:
body = response.json()
if body.get("error") == "payment_request_invalid":
logger.critical(
"Payment processor rejected request shape",
extra={"request_id": body.get("request_id")},
)
raise PaymentRequestInvalidError(
"Payment couldn't be processed. "
)
response.raise_for_status()
return response.json()
class PaymentRequestInvalidError extends Error {}
async function attemptCheckout(
planId: string,
baseUrl: string,
headers: HeadersInit,
) {
const response = await fetch(`${baseUrl}/v1/billing/subscribe`, {
method: "POST",
headers,
body: JSON.stringify({ plan_id: planId }),
});
if (response.status === 400) {
const body = await response.json();
if (body.error === "payment_request_invalid") {
console.error("Payment processor rejected request shape", {
request_id: body.request_id,
});
throw new PaymentRequestInvalidError(
"Payment couldn't be processed. " +
);
}
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
Avoid this error by
- This isn't a client-side issue you can prevent — when it happens, it's a server-side mismatch we need to fix.
- If you're building on top of Asterwise for your own customers, log occurrences with
request_idso you can correlate with our resolution timeline. - Don't construct payment payloads yourself. Always use the official checkout flow from the dashboard.
- After we resolve a known instance, the same
plan_idwill start working again — no client change needed on your end.