Advanced error handling patterns for Stripe enterprise developers

/Article

In Simple error handling strategies with Stripe Workbench, you learn how to implement simple error handling strategies with the help of Stripe Workbench. This post demonstrates some more advanced patterns to help you build resilient and robust payment systems to integrate Stripe with your enterprise applications. As your integration grows in complexity and volume, these patterns become crucial for maintaining system stability and providing a smooth user experience.

In distributed systems, network issues or unexpected spikes in traffic can occasionally lead to temporary API slowdowns or connectivity problems. When integrating with external services like Stripe, it's crucial to design your application to gracefully handle these scenarios. Without proper safeguards, your application might experience slower response times or resource exhaustion while repeatedly attempting to connect during these periods.

Getting started with Stripe Workbench

Use Workbench to detect when your Stripe account is experiencing high volumes of traffic, unusually high error rates, or regular webhook delivery failures.

To see Workbench:

  1. Navigate to https://dashboard.stripe.com/ in your preferred browser and log into your Stripe account.
  2. If you have multiple accounts configured, use the drop-down in the top-left to select the store API activity you wish to view. Workbench reports and content are scoped to the store level.
  3. In the bottom-right corner of the browser, hover over the terminal icon to expand the menu, then select the caret symbol ^ to open Workbench.
  4. Workbench opens in the lower portion of the window

Workbench is not a browser extension and does not rely on CLIs or other tools in your development machine, so you can use it immediately without the need for installing additional software.

Detecting high traffic periods

The Overview tab provides a snapshot of your Stripe account's API activity. You can also view a breakdown of API versions used in recent requests, with the ability to upgrade your account's default version if needed. Visual representations of your API requests and webhook activity are presented through intuitive graphs, giving you a clear picture of recent interactions. The Insights tab offers actionable recommendations to enhance your Stripe integration, helping you resolve errors, boost performance, and optimize your use of Stripe's APIs.

The following example shows that there was recently a significant increase in API requests made to this Stripe account.

A surge of requests occurred on September 9, with a significant number of these requests failing.

The Errors tab provides an overview of recent issues in your Stripe account. In this example, it’s clear that a recent error was caused by exceeding Stripe's API rate limits. This occurred due to a scheduled job that was triggered across thousands of accounts at the same time, creating reports for monthly usage of metered billing. The job attempted to create usage records for all customers at once, resulting in a high volume of API requests in a short time frame, triggering Stripe's rate limiting protection:

Too many requests?

Stripe implements API rate limits to ensure system stability and prevent abuse. These limits cap the number of API requests that can be made within a specific timeframe, with different thresholds for live and test modes. In live mode, Stripe allows up to 100 read and 100 write operations per second, while test mode permits 25 of each. Certain API resources, such as the Files API and Search API, have stricter limits. If these limits are exceeded, you may encounter 429 error responses.

It's important to design your integration to handle these limits gracefully. It is also good practice to treat the limits as maximums and take proactive measures to reduce the likelihood of receiving a 429 response in the first place. There are several common patterns you can implement to mitigate the chances of reaching the rate limits.

Request Spacing Pattern

This pattern focuses on the temporal aspect of rate limiting, ensuring a steady, spaced-out stream of requests rather than enforcing a strict count within a time window. The Request Spacing Pattern is implemented here using Limiter, a lightweight rate limiter library for Node.js. It ensures a minimum time interval between successive API calls, effectively spacing out requests to prevent overwhelming the API endpoint and to comply with rate limits.

const { RateLimiter } = require('limiter'); const stripe = require('stripe')('your_stripe_secret_key'); //Allow one message to be sent every 600ms: const limiter = new RateLimiter({ tokensPerInterval: 1, interval: 600 // 60000ms / 100 = 600ms between each request }); const putMeterEvent = async (customerId, value) => { await limiter.removeTokens(1); try { const meterEvent = await stripe.billing.meterEvents.create({ event_name: 'llama_ai_tokens', payload:{ value, stripe_customer_id: customerId, } }); console.log('Meter event created:', meterEvent.id); return meterEvent; } catch (error) { console.error('Error creating meter event:', error); throw error; } };

The following diagram shows how the Request Spacing Pattern works to ensure each API request is spaced out into 600 ms windows:

Concurrency Control Pattern

When multiple processes are running concurrently, they can collectively exceed API rate limits. Managing concurrency without proper controls increases the likelihood of encountering 429 errors. The following example adds a concurrency setting using a node package called p-queue to limit the number of concurrent requests.

const { RateLimiter } = require('limiter'); const PQueue = require('p-queue'); const stripe = require('stripe')('your_stripe_secret_key'); const rateLimiter = new RateLimiter({ tokensPerInterval: 1, interval: 600 }); const queue = new PQueue({ concurrency: 5 }); // set a max concurrency of 5 requests const putMeterEvent = async (customerId, value) => { try { const meterEvent = await stripe.billing.meterEvents.create({ event_name: 'llama_ai_tokens', payload:{ value, stripe_customer_id: customerId, } }); console.log('Meter event created:', meterEvent.id); return meterEvent; } catch (error) { console.error('Error creating meter event:', error); throw error; } }; const putMeterEventWithLimits = async (customerId, value) => { await rateLimiter.removeTokens(1); return queue.add(() => putMeterEvent(customerId, value)); };

The concurrency setting limits the number of requests that can simultaneously start to five. After that, new requests will only start after one of the running requests completes and at least 600 ms has passed since the last request started. This helps prevent overwhelming the API even if multiple processes or a large batch job is running.

Token bucket pattern

This pattern is particularly useful for handling bursts of requests while maintaining a consistent overall rate. It controls the rate at which requests are processed by using a “bucket” that holds tokens. Each token represents the permission to make one request.

Tokens are generated at a steady rate and added to the bucket. The rate at which tokens are generated determines the average rate limit for requests. The bucket has a maximum capacity (often called the reservoir). If the bucket is full, any additional tokens are discarded. This capacity allows the system to accept bursts of requests. When a request is made, it must obtain a token from the bucket. If tokens are available, the request is processed. If the bucket is empty (no tokens available), the request is either delayed or rejected until tokens become available. Tokens are periodically added back to the bucket at the predefined rate, allowing the system to recover from bursts and resume processing requests at a consistent rate.

In the following example, a token bucket algorithm is used to manage request rate limits, with concurrency handled using p-queue. This approach allows for a burst of requests up to a certain limit and then enforces a steady rate of requests:

const { TokenBucket } = require('limiter'); const PQueue = require('p-queue'); const stripe = require('stripe')('your_stripe_secret_key'); // Token bucket: 100 tokens, refills 100 tokens every 60 seconds const tokenBucket = new TokenBucket({ bucketSize: 100, tokensPerInterval: 100, interval: 60 * 1000 }); // Concurrency queue: max 5 concurrent requests const queue = new PQueue({ concurrency: 5 }); const putMeterEvent = async (customerId, value) => { try { const meterEvent = await stripe.billing.meterEvents.create({ event_name: 'llama_ai_tokens', payload:{ value, stripe_customer_id: customerId, } }); console.log('Meter event created:', meterEvent.id); return meterEvent; } catch (error) { console.error('Error creating meter event:', error); throw error; } }; const putMeterEventWithLimits = async (customerId, value) => { // Check token bucket if (!(await tokenBucket.removeTokens(1))) { throw new Error('Rate limit exceeded (token bucket)'); } // Add to concurrency queue return queue.add(() => putMeterEvent(customerId, value)); }

The following image shows how a combination of rate limiting, max concurrency, and token bucket system work together to ensure a steady rate of requests.

The following chart taken from the Workbench overview demonstrates the results from before this pattern was implemented (in the red box), and after the pattern was implemented (in the green box). The results show that the updated implementation can process a high volume of API calls while staying within Stripe's rate limits. This approach minimizes errors, improves efficiency, and provides a more stable and predictable interaction with the Stripe API.

Conclusion

As your Stripe integration grows and evolves, regularly monitoring your API usage with Workbench and fine-tuning your rate limiting strategies is key to maintaining a robust and efficient payment processing integration.

As demonstrated in this post, the combination of Request Spacing, Concurrency Control, and Token Bucket patterns offers a comprehensive approach to managing API requests. By understanding how to implement these patterns, you're well-equipped to handle the complexities of high-volume payment processing, ensuring your Stripe integration remains robust and ready for enterprise-scale challenges.

/About the author

Ben Smith

Ben is a Staff Developer Advocate at Stripe, based in the UK. Previously, he was a Principal Developer Advocate at AWS, specializing in serverless architecture. With a background in web development, he is passionate about empowering developers through knowledge sharing and community engagement, making complex technologies accessible to all.

/Related Articles
[ Fig. 1 ]
10x
Simple error handling strategies with Stripe Workbench
With Workbench, developers now have a bird’s-eye view of their integration which shows many potential issues in one place. This makes it easier...
Workbench
Error Handling
[ Fig. 2 ]
10x
Bringing your Stripe objects to life with Workbench
This post shows how to use the Stripe Workbench Inspector to examine the lifecycle of a PaymentIntent object....
Workbench
Payment Intents