Error handling
Handle failed requests, webhook events, and stored errors with a Stripe SDK.
Use a Stripe SDK to catch failed API requests, handle webhook notifications, and inspect stored errors. Error details help you decide whether to correct a request, ask your customer to act, or investigate an uncertain outcome. Select your SDK language to see its examples and error types.
Choose your next action based on what you know about the request’s outcome.
| Situation | Next action |
|---|---|
| Parameters are invalid or authentication fails | Correct the request or API key before trying again. |
| A payment is declined or needs customer action | Handle the payment error and ask the customer to complete the required action. |
| A connection fails or a request is still executing | Retry safely with the same idempotency key and parameters. |
| A server error or another failure leaves the outcome unknown | Reconcile the request before starting another operation. |
Before you begin
Before running the examples:
- Install a supported Stripe SDK and its required runtime. For Python, use Python 3.9 or later. These examples use the SDK’s
v1services where available. - Use a sandbox with test payment methods.
- Create a restricted API key with only the permissions needed by the examples you run. See the permissions reference:
payment_to createintent_ write PaymentIntentobjects.payment_to retrieveintent_ read PaymentIntentobjects.charge_to retrieveread Chargeobjects.
- Load your key from a secure environment variable or secrets manager. For examples that read
STRIPE_, set that variable to your restricted key.SECRET_ KEY - For webhook examples, set up a webhook endpoint, install the example’s web framework, and set
STRIPE_to that endpoint’s signing secret. When forwarding events with the Stripe CLI, use the signing secret fromWEBHOOK_ SECRET stripe listen.
Read error details
When an API request fails, use the error response to identify the problem. Keep the request ID so you can find the request in your logs or share it with Stripe support. Depending on the error, the response can include the following details:
| Property | Description |
|---|---|
code | The error code. |
doc_ | A link to the Stripe documentation for the specific error code. |
message | A description of the problem. |
param | The request parameter associated with the error, if applicable. |
request_ | A link to the request log in the Stripe Dashboard. |
| Request ID | A unique identifier for the request, beginning with req_. The response headers contain this value. The examples show how to read it from an SDK exception or error value. |
type | The error category. |
Use these techniques to handle errors identified through different channels. Use the error type to choose a response.
| Technique | Where to use it |
|---|---|
| Handle API request errors | Exceptions or error values returned by individual API requests. |
| Monitor webhooks | Failures reported through events, including failures that occur after an API request completes. |
| Inspect stored errors | Errors or error-related information stored in objects. |
Handle API request errors
Stripe SDKs expose failed API requests as exceptions or error values. Handle these in your application and use their details to choose a response.
Use Ruby’s rescue keyword to catch Stripe::StripeError or its subclasses. Each subclass represents a different kind of error. Use the exception class to choose a response.
require 'stripe' # Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. def example_function(params) client = Stripe::StripeClient.new(ENV.fetch('STRIPE_SECRET_KEY')) begin client.v1.payment_intents.create(params) rescue Stripe::CardError => e puts "A payment error occurred: #{e.error.message} (request id: #{e.request_id})" rescue Stripe::InvalidRequestError => e puts "An invalid request occurred. (request id: #{e.request_id})" # Add additional catch cases for other Stripe exception types as needed. # See all exception types at https://docs.stripe.com/api/errors/handling?lang=ruby rescue Stripe::StripeError => e # All other Stripe errors puts "Status: #{e.http_status}, Code: #{e.code}, Message: #{e.message}, Request ID: #{e.request_id}" else puts "No error." end end
Test API request errors
Run the following scenarios in a sandbox to test your error handler. The examples explicitly select card payments so that automatic payment method defaults don’t require a return_ before the intended result. Log formatting and request IDs vary by SDK.
Monitor webhooks
Use webhooks to learn about failures reported through events. These can include failures that aren’t returned in an API response:
- A dispute is lost.
- A recurring payment fails.
- Your front end confirms a payment, then disconnects before receiving the result, but your server can still receive the failure event.
In your webhook handler, verify the signature using the raw request body, the Stripe-Signature header, and your endpoint’s signing secret before processing the event. Reject invalid payloads or signatures with a 400 response. If you use Express, as in the Node.js example, register this route before any middleware that parses JSON, because verification requires the unmodified request body.
If you restrict inbound traffic by IP address, allow Stripe’s public webhook IP addresses.
The following examples handle snapshot events, which include the affected object in data.. For thin events, follow the thin-event processing guidance to retrieve the details you need.
After verifying a snapshot event that reports a failure, inspect the affected object:
- Access event.data.object to retrieve the affected object.
- Inspect the stored error, if the object contains one.
- Use the error type to choose a response.
require 'stripe' require 'sinatra' endpoint_secret = ENV.fetch('STRIPE_WEBHOOK_SECRET') post '/webhook' do payload = request.body.read begin event = Stripe::Webhook.construct_event( payload, request.env['HTTP_STRIPE_SIGNATURE'], endpoint_secret ) rescue JSON::ParserError, Stripe::SignatureVerificationError halt 400 end # Use the event type to find out what happened case event.type when 'payment_intent.payment_failed' # Get the object affected payment_intent = event.data.object # Use stored information to get an error object e = payment_intent.last_payment_error halt 200 if e.nil? # Use its type to choose a response case e.type when 'card_error' puts "A payment error occurred: #{e.message}" when 'invalid_request_error' puts "An invalid request occurred." else puts "Another problem occurred, maybe unrelated to Stripe." end end content_type 'application/json' { status: 'success' }.to_json end
Test webhook handling
Set up local webhook testing, then trigger a failed payment to test your handler:
stripe trigger payment_intent.payment_failed
A payment error occurred: Your card was declined.
Inspect stored errors
Some Stripe objects store details about a failure. Retrieve the object and inspect its error properties to investigate the problem or add context to an event. For example, a PaymentIntent stores the error from its previous confirmation attempt in last_payment_error.
An empty value doesn’t prove that the payment never failed, because Stripe clears last_ when you update the PaymentIntent. This property isn’t a historical error log.
- Retrieve the
PaymentIntent. - Check whether
last_contains an error.payment_ error - If present, log its type and the
PaymentIntentID.
require 'stripe' # Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. client = Stripe::StripeClient.new('sk_test_...') payment_intent = client.v1.payment_intents.retrieve('{{PAYMENT_INTENT_ID}}') e = payment_intent.last_payment_error if !e.nil? puts "PaymentIntent #{payment_intent.id} experienced a #{e.type}." end
Here are common objects that store information about failures.
| Object | Attribute | Values |
|---|---|---|
| PaymentIntent | last_ | An error object |
| SetupIntent | last_ | An error object |
| Invoice | last_ | An error object |
| SetupAttempt | setup_ | An error object |
| Payout | failure_ | A payout failure code |
| Refund | failure_ | A refund failure code |
Test stored errors
Simulate a failure in a sandbox, then retrieve the affected object to check its error properties:
Choose a response by error type
Use the error type to choose a response. SDK exception names and raw API error types can differ. Stored errors, such as last_, use API type values such as card_ and invalid_. Compare these values with API types, not SDK exception names.
In the Stripe Ruby library, exceptions belong to Stripe::StripeError and its subclasses. Use the documentation for each class for advice on responding.
| Name | SDK exception | Description |
|---|---|---|
| Payment error | An error occurred during a payment, involving one of these situations: | |
| Invalid request error | You made an API call with the wrong parameters, in the wrong state, or in an invalid way. | |
| Connection error | There was a network problem between your server and Stripe. | |
| API error | Something went wrong on Stripe’s end. (These are rare.) | |
| Authentication error | Stripe can’t authenticate you with the information provided. | |
| Idempotency error | You used an idempotency key for something unexpected, like replaying a request but passing different parameters. | |
| Permission error | The API key used for this request doesn’t have the necessary permissions. | |
| Rate limit error | You made too many API calls in too short a time. | |
| Signature verification error | You’re using webhook signature verification and couldn’t verify that a webhook event is authentic. |
Payment errors
Payment errors can occur with any payment method, even when the SDK calls them card errors. They fall into three categories:
To choose a response, check the error code, decline code, and charge outcome. To identify the correct charge, check whether the error contains a PaymentIntent with a latest Charge, then retrieve that Charge.
If the charge is absent or the lookup fails, use the original payment error code. A missing charge or failed lookup doesn’t necessarily mean that Radar blocked the payment.
Use a restricted API key with payment_ to create PaymentIntent objects and charge_ to retrieve Charge objects. See the permissions reference. Load the key from the STRIPE_ environment variable, following the key management guidance.
These examples use latest_, which is available in API version 2022-11-15 and later. If your integration uses an older API version, upgrade before using these examples with a current SDK.
require 'stripe' def example_function(params) client = Stripe::StripeClient.new(ENV.fetch('STRIPE_SECRET_KEY')) begin client.v1.payment_intents.create(params) rescue Stripe::CardError => e charge = nil charge_id = e.error&.payment_intent&.latest_charge if charge_id begin charge = client.v1.charges.retrieve(charge_id) rescue Stripe::StripeError warn 'Could not retrieve the charge; using the original payment error.' end end if charge&.outcome&.type == 'blocked' puts 'Payment blocked for suspected fraud.' elsif e.code == 'card_declined' puts 'Payment declined.' elsif e.code == 'expired_card' puts 'Card expired.' else puts 'Other card error.' end end end
Test payment errors
Use test cards in a sandbox to trigger the error types your integration handles:
Run the following scenarios with the error handler to check its response.
Payment blocked for suspected fraud
| Type |
|
Charge outcome | Retrieve the charge as shown in the Payment errors example, which checks whether a charge is available and handles lookup failures. After a successful lookup, check its outcome:
If no charge or outcome is available, don’t infer that the payment was blocked. |
| Problem | The Stripe fraud prevention system, Radar, blocked the payment |
Solutions | This error can occur when your integration is working correctly. Catch it and prompt the customer for a different payment method. To block fewer legitimate payments, try these:
Radar Plus customers have these additional options:
You can test your integration’s settings with test cards that simulate fraud. If you have custom Radar rules, follow the testing advice in the Radar documentation. |
Payment declined by the issuer
| Type |
|
| Codes |
|
| Problem | The card issuer declined the payment. |
Solutions | This error can occur when your integration is working correctly. It reflects an action by the issuer, and that action might be legitimate. Use the decline code to determine what next steps are appropriate. See the documentation on decline codes for appropriate responses to each code. You can also:
Test how your integration handles declines with test cards that simulate successful and declined payments. |
Other payment errors
| Type |
|
| Problem | Another payment error occurred. |
| Solutions | This error can occur when your integration is working correctly. Use the error code to determine what next steps are appropriate. See the documentation on error codes for appropriate responses to each code. |
Invalid request errors
| Type |
|
| Problem | You made an API call with the wrong parameters, in the wrong state, or in an invalid way. |
| Solutions | In most cases, the problem is with the request itself. Either its parameters are invalid or it can’t be carried out in your integration’s current state.
|
Connection errors
| Type |
|
| Problem | There was a network problem between your server and Stripe. |
Solutions | Treat the result of the API call as indeterminate. That is, don’t assume that it succeeded or that it failed. To find out if it succeeded, you can:
To help recover from connection errors, you can:
This error can mask others. It’s possible that when the connection error resolves, some other error becomes apparent. Check for errors in all of these solutions just as you would in the original request. |
API errors
| Type |
|
| Problem | Something went wrong on Stripe’s end. (These are rare.) |
Solutions | Treat the result of the API call as indeterminate. That is, don’t assume that it succeeded or that it failed. Rely on webhooks for information about the outcome. Whenever possible, Stripe fires webhooks for any new objects we create as we solve a problem. To set your integration up for maximum robustness in unusual situations, see this advanced discussion of server errors. |
Authentication errors
| Type |
|
| Problem | Stripe can’t authenticate you with the information provided. |
| Solutions |
|
Idempotency errors
| Type |
|
| Problem | You used an idempotency key for something unexpected, like replaying a request but passing different parameters. |
| Solutions |
|
Permission errors
| Type |
|
| Problem | The API key used for this request doesn’t have the necessary permissions. |
| Solutions |
|
Rate limit errors
| Type |
|
| Problem | You made too many API calls in too short a time. |
| Solutions |
|
Signature verification errors
| Type |
|
| Problem | You’re using webhook signature verification and couldn’t verify that a webhook event is authentic. |
Solutions | This error can occur when your integration is working correctly. If you use webhook signature verification and a third party attempts to send you a fake or malicious webhook, then verification fails and this error is the result. Catch it and respond with a If you receive this error when you shouldn’t—for instance, with webhooks that you know originate with Stripe—then see the documentation on checking webhook signatures for further advice. In particular, make sure you’re using the correct endpoint secret. This is different from your API key. |