API Errors Fresh
Stripe uses conventional HTTP status codes and structured error objects. Handle errors by checking the error type, then the code or decline_code for specifics.
HTTP status codes
| Code | Status | Meaning |
|---|---|---|
| 200 | OK | Request succeeded |
| 400 | Bad Request | Missing or invalid parameter |
| 401 | Unauthorized | No valid API key provided |
| 402 | Request Failed | Parameters valid, but request failed (e.g., card declined) |
| 403 | Forbidden | API key lacks permission for this operation |
| 404 | Not Found | Requested resource doesn't exist |
| 409 | Conflict | Request conflicts with another (e.g., duplicate idempotency key) |
| 424 | External Dependency Failed | External dependency (issuer, bank) failed |
| 429 | Too Many Requests | Rate limit exceeded — use exponential backoff |
| 500, 502, 503, 504 | Server Errors | Something wrong on Stripe's end (rare) |
Error types
| Type | Description |
|---|---|
api_error | Temporary problem with Stripe's servers — extremely uncommon |
card_error | Card was declined or had an issue — most common type to handle |
idempotency_error | Idempotency key reused with different request parameters |
invalid_request_error | Request had invalid parameters (missing required field, wrong type, etc.) |
Error object attributes
json
{
"error": {
"type": "card_error",
"code": "card_declined",
"decline_code": "insufficient_funds",
"message": "Your card has insufficient funds.",
"param": null,
"charge": "ch_3Nxxx",
"payment_intent": { ... },
"doc_url": "https://stripe.com/docs/error-codes/card-declined"
}
}| Attribute | Type | Description |
|---|---|---|
type | string | One of: api_error, card_error, idempotency_error, invalid_request_error |
code | string | Short code identifying the error (e.g., card_declined, missing) |
decline_code | string | Card issuer's reason for decline (card errors only) |
message | string | Human-readable description — safe to show customers for card_error type |
param | string | The parameter that caused the error (for invalid_request_error) |
charge | string | ID of the failed charge (card errors only) |
payment_intent | object | The PaymentIntent that caused the error (if applicable) |
payment_method | object | The PaymentMethod that caused the error (if applicable) |
setup_intent | object | The SetupIntent that caused the error (if applicable) |
advice_code | string | Suggested next steps from the card issuer |
network_advice_code | string | Two-digit code from the card network |
network_decline_code | string | Alphanumeric code from the network for declined payments |
request_log_url | string | URL to the request log in Dashboard |
doc_url | string | URL to error code documentation |
Common error codes
Card errors (type: "card_error")
| Code | Meaning |
|---|---|
card_declined | Card was declined — check decline_code for the specific reason |
expired_card | Card has expired |
incorrect_cvc | CVC number is incorrect |
incorrect_number | Card number is incorrect |
insufficient_funds | Insufficient funds |
invalid_expiry_month | Invalid expiry month |
invalid_expiry_year | Invalid expiry year |
Invalid request errors (type: "invalid_request_error")
| Code | Meaning |
|---|---|
missing | Required parameter is missing |
parameter_invalid_empty | Parameter is empty but required |
parameter_invalid_integer | Expected an integer |
parameter_invalid_string_blank | Value must not be blank |
resource_missing | Resource (customer, card, etc.) not found |
idempotency_key_in_use | Idempotency key currently in use |
amount_too_small | Amount is below minimum charge amount |
amount_too_large | Amount exceeds maximum charge amount |
currency_not_supported | Currency not supported for this account |
invalid_charge_amount | Amount is invalid |
Error handling pattern
javascript
const stripe = require('stripe')('sk_test_YOUR_KEY');
async function createCharge(amount, currency, paymentMethodId) {
try {
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
payment_method: paymentMethodId,
confirm: true,
});
return { success: true, paymentIntent };
} catch (err) {
switch (err.type) {
case 'StripeCardError':
// Safe to show card error messages to the customer
return {
success: false,
message: err.message,
decline_code: err.decline_code,
};
case 'StripeInvalidRequestError':
// Bad API call — log internally, don't expose to user
console.error('Invalid request:', err.message, 'param:', err.param);
return { success: false, message: 'An error occurred. Please try again.' };
case 'StripeAPIError':
// Stripe server error — retry with backoff
console.error('Stripe API error:', err.message);
return { success: false, message: 'Temporarily unavailable. Please try again.' };
case 'StripeConnectionError':
// Network error — retry
return { success: false, message: 'Network error. Please try again.' };
case 'StripeAuthenticationError':
// Bad API key — alert your team
console.error('Authentication error — check API key');
return { success: false, message: 'Configuration error.' };
default:
console.error('Unexpected error:', err);
return { success: false, message: 'An unexpected error occurred.' };
}
}
}Idempotency
Use idempotency keys to safely retry requests without creating duplicate charges:
bash
curl https://api.stripe.com/v1/payment_intents \
-u "sk_test_YOUR_KEY:" \
-H "Idempotency-Key: your-unique-key-per-request" \
-d amount=1000 \
-d currency=usd- Generate a unique key per logical operation (e.g., UUID)
- Reuse the same key to safely retry the same request
- Stripe returns the cached result for up to 24 hours
- Keys are scoped per API key
Rate limit handling
javascript
async function retryWithBackoff(fn, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
if (err.statusCode === 429 && attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw err;
}
}
}
}