When a payment attempt fails, Chargebee returns a structured error you can handle programmatically. This reference covers every error category, what causes it, and what to do about it.
All payment errors follow this shape:
{
"api_error_code": "payment_processing_failed",
"message": "The card was declined.",
"error_code": 3000,
"http_status_code": 400,
"type": "payment",
"payment_error_code": "card_declined"
}| Field | Type | Description |
|---|---|---|
api_error_code |
string | Machine-readable code. Use this for branching logic. |
message |
string | Human-readable description. Do not parse for logic — wording may change. |
error_code |
integer | Chargebee internal code. Reference only. |
http_status_code |
integer | HTTP status. Payment errors are always 400. |
type |
string | Error category. Payment errors are always "payment". |
payment_error_code |
string | Specific gateway reason code. See tables below. |
Payment errors fall into four categories. The right recovery path depends on the category.
| Category | What it means | Recovery |
|---|---|---|
| Insufficient funds | The card has no available balance | Retry after funds available, or ask customer to use a different card |
| Card authentication | 3DS or CVV verification failed | Customer must re-authenticate or use a different card |
| Card restriction | Card blocked for this transaction type | Customer must contact their bank or use a different card |
| Gateway / network | Temporary processing failure | Retry with exponential backoff — usually resolves on its own |
payment_error_code |
Meaning | What to do |
|---|---|---|
insufficient_funds |
Card balance too low for the charge amount | Show customer a message to add funds or use a different card. Retry after 3–5 days. |
withdrawal_count_limit_exceeded |
Customer's bank limits the number of daily transactions | Retry the next day, or prompt for a different card. |
credit_limit_exceeded |
Credit limit reached | Prompt customer to use a different card. No retry benefit. |
Recommended message to customer:
"Your payment didn't go through due to insufficient funds. Please add funds to your account or use a different payment method."
payment_error_code |
Meaning | What to do |
|---|---|---|
card_declined_3ds |
3D Secure authentication failed | Re-trigger the checkout flow so the customer can re-authenticate. |
authentication_required |
Bank requires 3DS but it wasn't used | Re-initiate payment with 3DS enabled. |
invalid_cvv |
CVV doesn't match | Ask customer to re-enter their card details. Do not retry with the same CVV. |
card_expired |
Card expiration date has passed | Ask customer to update payment method in your billing portal. |
invalid_expiry_date |
Expiration date format is incorrect | Validate expiry format client-side before submission. |
3DS handling note: Chargebee's hosted pages handle 3DS automatically. If you use a custom integration, implement the 3DS flow before retrying.
These errors are set by the customer's bank and cannot be resolved by retrying.
payment_error_code |
Meaning | What to do |
|---|---|---|
card_declined |
Generic decline from the bank | Prompt customer to contact their bank or use a different card. |
do_not_honor |
Bank declined without a specific reason | Same as card_declined. Banks often use this for fraud flags. |
transaction_not_permitted |
Card is not enabled for this transaction type | Customer must contact their bank to enable the transaction type or use a different card. |
card_velocity_exceeded |
Too many transactions on this card in a short window | Retry after 24 hours, or prompt for a different card. |
fraudulent |
Bank flagged the transaction as potentially fraudulent | Do not retry. Prompt customer to use a different card and contact their bank. |
stolen_card |
Card reported stolen | Do not retry. Flag this account for review. |
lost_card |
Card reported lost | Do not retry. Flag this account for review. |
pickup_card |
Bank is requesting the card be retrieved (in-person only) | Prompt customer to use a different card. |
⚠️ Never retryfraudulent,stolen_card, orlost_carderrors. Multiple retry attempts on flagged cards can trigger additional fraud signals and may violate your payment processor agreement.
These are transient failures. A retry strategy usually resolves them.
payment_error_code |
Meaning | What to do |
|---|---|---|
processing_error |
Temporary gateway failure | Retry with exponential backoff (see schedule below). |
gateway_timeout |
Gateway didn't respond in time | Retry after 30 seconds. If it persists, check your gateway status page. |
insufficient_gateway_quota |
Rate limit hit on the gateway | Reduce retry frequency. Chargebee's built-in dunning handles this automatically. |
temporary_processing_failure |
Issuing bank is temporarily unavailable | Retry after 1–2 hours. |
Recommended retry schedule for transient errors:
| Attempt | Wait before retry |
|---|---|
| 1st retry | 30 seconds |
| 2nd retry | 5 minutes |
| 3rd retry | 1 hour |
| 4th retry | 24 hours |
| Give up | Notify customer to update payment method |
Chargebee's built-in dunning handles automated retries. Configure the schedule at Settings → Dunning. The custom retry logic above is only needed if you're managing payment collection yourself outside of Chargebee's dunning workflow.
try {
const result = await chargebee.subscription.create({
plan_id: 'pro-monthly',
customer_id: 'cus_KmQ9xT4pRw2v',
}).request();
} catch (err) {
if (err.type === 'payment') {
handlePaymentError(err);
} else if (err.type === 'invalid_request') {
handleValidationError(err);
} else {
throw err; // unexpected error — surface it
}
}
function handlePaymentError(err) {
switch (err.payment_error_code) {
case 'card_declined':
case 'do_not_honor':
case 'insufficient_funds':
// Prompt customer to use a different card
notifyCustomer('payment_failed_soft', err.message);
break;
case 'fraudulent':
case 'stolen_card':
case 'lost_card':
// Do NOT retry — flag for review
flagAccountForReview(err);
break;
case 'processing_error':
case 'gateway_timeout':
case 'temporary_processing_failure':
// Transient — schedule a retry
scheduleRetry();
break;
default:
// Unknown payment error — log and notify customer
logError(err);
notifyCustomer('payment_failed_generic', err.message);
}
}import chargebee
try:
result = chargebee.Subscription.create({
"plan_id": "pro-monthly",
"customer_id": "cus_KmQ9xT4pRw2v",
})
except chargebee.PaymentException as e:
error_code = e.json_obj.get("payment_error_code")
HARD_DECLINES = {"fraudulent", "stolen_card", "lost_card"}
TRANSIENT_ERRORS = {"processing_error", "gateway_timeout", "temporary_processing_failure"}
if error_code in HARD_DECLINES:
flag_account_for_review(e)
elif error_code in TRANSIENT_ERRORS:
schedule_retry()
else:
notify_customer("payment_failed", str(e))Write error messages that tell the customer what happened and what to do next. Avoid exposing raw error codes.
| Scenario | Recommended message |
|---|---|
| Soft decline (funds, velocity) | "Your payment couldn't be processed. Please try a different card or contact your bank." |
| Authentication failure | "We couldn't verify your card. Please re-enter your details or use a different card." |
| Expired card | "The card on file has expired. Please update your payment method." |
| Hard decline (fraud flag) | "Your payment was declined. Please use a different card or contact your bank for details." |
| Transient / retry pending | "We're having trouble processing your payment. We'll try again automatically — no action needed." |
- Webhook events for payment failures — listen for
payment_failedandinvoice_not_paid - Dunning configuration — set up automated retry schedules in Chargebee
- Zero to first charge — Node.js quickstart — end-to-end payment integration walkthrough