Error Handling
Understanding and handling Stripe API errors effectively
Stripe API requests can fail for various reasons: invalid parameters, authentication issues, card declines, rate limits, and network failures. The StripeError enum provides structured error information to help you handle these cases appropriately.
Error Types
The StripeError enum has several variants:
/// An error encountered when communicating with the Stripe API.
#[derive(Debug, Error)]
pub enum StripeError {
/// Stripe returned a client error.
#[error("error reported by stripe: {0:#?}, status code: {1}")]
Stripe(Box<ApiErrors>, u16),
/// An error occurred when parsing the Stripe response.
#[error("error deserializing a request: {0}")]
JSONDeserialize(String),
/// An error occurred communicating with Stripe.
#[error("error communicating with stripe: {0}")]
ClientError(String),
/// The client configuration was invalid.
#[error("configuration error: {0}")]
ConfigError(String),
/// A blocking request timed out
#[error("timeout communicating with stripe")]
Timeout,
}Handling API Errors
The most common error type is StripeError::Stripe, which contains the error details from Stripe's API along with the HTTP status code.
Basic Error Handling
match Customer::create(&client, CreateCustomer::new()).send().await {
Ok(customer) => info!("Created customer: {}", customer.id),
Err(err) => match err {
StripeError::Stripe(api_error, status_code) => {
error!("Stripe API error ({}): {:?}", status_code, api_error.message);
}
StripeError::ClientError(msg) => {
error!("Network error: {}", msg);
}
_ => {
error!("Other error: {}", err);
}
},
}Handling Specific HTTP Status Codes
Different status codes indicate different types of failures:
match PaymentIntent::retrieve(&client, &payment_intent_id, &[]).await {
Ok(payment) => {
info!("Payment status: {:?}", payment.status);
}
Err(StripeError::Stripe(api_error, status)) => match status {
400 => {
// Bad Request - Invalid parameters
error!("Invalid request: {:?}", api_error.message);
}
401 => {
// Unauthorized - Invalid API key
error!("Authentication failed - check your API key");
}
402 => {
// Payment Required - Card declined or insufficient funds
error!("Payment failed: {:?}", api_error.message);
// The error may contain a decline code
if let Some(code) = &api_error.code {
match code.as_str() {
"card_declined" => error!("Card was declined"),
"insufficient_funds" => error!("Insufficient funds"),
"expired_card" => error!("Card has expired"),
_ => error!("Payment error code: {}", code),
}
}
}
404 => {
// Not Found - Resource doesn't exist
error!("Resource not found");
}
429 => {
// Too Many Requests - Rate limited
warn!("Rate limited - slow down requests");
}
500 | 502 | 503 | 504 => {
// Server errors - retry with backoff
error!("Stripe server error - retry later");
}
_ => {
error!("HTTP {}: {:?}", status, api_error.message);
}
},
Err(e) => {
error!("Non-API error: {}", e);
}
}
}Common Error Codes
Stripe includes error codes in the ApiErrors struct that provide more specific information about what went wrong:
Payment Errors (402)
card_declined- The card was declinedexpired_card- The card has expiredincorrect_cvc- The CVC is incorrectprocessing_error- An error occurred while processing the cardinsufficient_funds- Insufficient funds in the account
Request Errors (400)
parameter_invalid_empty- A required parameter was emptyparameter_unknown- An unknown parameter was providedresource_missing- The requested resource doesn't exist
Authentication Errors (401)
invalid_api_key- The API key is invalid
For a complete list of error codes, see the Stripe Error Codes documentation.
Parsing Errors
By default, async-stripe uses miniserde for deserializing API responses. This significantly reduces compile times and binary size, but provides minimal error messages when deserialization fails.
Understanding Deserialization Failures
If you receive a deserialization error, it may look like:
Error: failed to deserialize responseThis typically means the JSON response from Stripe doesn't match the expected structure. This can happen when:
- Stripe adds new fields to their API
- You're using an outdated version of
async-stripe - The response contains unexpected values
Getting Better Error Messages
For detailed diagnostics about which field failed and why, enable the deserialize feature to use serde instead:
[dependencies]
stripe-core = { version = "1.0.0-alpha.8", features = ["customer", "deserialize"] }This provides comprehensive error context with serde_path_to_error, showing exactly where in the JSON structure the error occurred
at the cost of significantly increased compile times, link times, and binary size. For context, the parser below needs 14MB just
to parse webhook data.
Why Not serde_json?
The compile-time and binary size impact of serde_json is substantial due to monomorphization. Each generic serde function gets compiled separately for every type, leading to code bloat. With hundreds of Stripe types, this results in massive binaries and slow compile times, since stripe needs to generate X00,000 lines of code to define how to deserialize each type. miniserde avoids this by using trait objects instead of generics, dramatically reducing the amount of generated code. For a deep dive into this topic, see The Dark Side of Inlining and Monomorphization.
Testing Event Parsing
You can test how async-stripe parses Stripe events using the interactive parser below. This uses the actual async-stripe parser compiled to WebAssembly with serde_path_to_error, which reports exactly where deserialization failed:
See the Performance documentation for more details on the hybrid serialization strategy and when to use the deserialize feature.
Retry Strategies
For transient errors (network issues, server errors), use the built-in retry strategies:
let client = Client::builder(secret_key)
.request_strategy(RequestStrategy::ExponentialBackoff(3))
.build();
// Replace with an actual customer ID for testing
let customer_id = "cus_example";
// This request will automatically retry up to 3 times with backoff
let customer = Customer::retrieve(&client, &customer_id, &[]).await;
match customer {
Ok(customer) => info!("Retrieved customer: {}", customer.id),
Err(e) => error!("Failed to retrieve customer after retries: {}", e),
}See the Request Strategies documentation for more details on retry behavior.
Best Practices
1. Handle Specific Errors
Don't just log all errors the same way. Handle payment failures differently from configuration errors:
match result {
Err(StripeError::Stripe(api_error, 402)) => {
// Show user-friendly message for payment failures
show_payment_error_to_user(&api_error);
}
Err(StripeError::Stripe(api_error, 400)) => {
// Log parameter errors for debugging
error!("Invalid parameters: {:?}", api_error);
}
Err(e) => {
// Log unexpected errors and alert monitoring
error!("Unexpected Stripe error: {}", e);
}
Ok(_result) => {
// Success
info!("Operation succeeded");
}
}2. Use Idempotency Keys
For critical operations (especially payment creation), always use idempotency keys to prevent accidental duplicate charges:
let key = IdempotencyKey::new("order_12345").unwrap();
let payment = CreatePaymentIntent::new(1000, Currency::USD)
.request_strategy(RequestStrategy::Idempotent(key))
.send(client)
.await;
match payment {
Ok(payment_intent) => {
info!("Created payment intent: {}", payment_intent.id);
}
Err(e) => {
error!("Failed to create payment: {}", e);
}
}3. Log Error Details
The ApiErrors struct contains useful debugging information:
let result = PaymentIntent::retrieve(client, "pi_example", &[]).await;
if let Err(StripeError::Stripe(api_error, status)) = result {
error!(
status = status,
error_type = ?api_error.error_type,
code = ?api_error.code,
message = ?api_error.message,
param = ?api_error.param,
"Stripe error"
);
}4. Don't Retry Client Errors
4xx errors (except 429 rate limits) usually indicate a problem with your request that won't be fixed by retrying. Only retry 5xx server errors and network failures.
The built-in RequestStrategy::ExponentialBackoff handles this correctly for you.
Never retry failed payments without user confirmation. A failed payment could be intentional (e.g., user canceled) or indicate fraud prevention.