Stripe logo

How Stripe scaled its backend

Money can't be 'probably correct' - reliability as the actual product

Payments infrastructure has a different tolerance for error than most software - a duplicate charge or a lost transaction isn't a minor bug, it's actual money either taken from or owed to a real person. The engineering challenge isn't primarily speed; it's absolute correctness under network conditions that are never fully reliable.

Stripe processes payments on behalf of a very large number of businesses across many countries, each with different currencies, banking rules, and card networks - meaning the same API call has to behave predictably and correctly regardless of which country, currency, or payment method is actually involved underneath.

Interactive

Stripe’s stack, mapped

Grouped by where each piece sits in the request path. Click through to see what each one is actually for.

Services & language

Data & messaging

Patterns & tooling

Backend language (early/core)

·

Ruby

Stripe's core API and much of its early backend were built in Ruby, chosen originally for developer productivity - Stripe has since layered significant internal tooling and typing discipline on top of it to manage correctness at scale.

Click a piece of Stripe’s stack to see what it’s for

The same stack, in plain language

Ruby

Backend language (early/core)

Stripe's core API and much of its early backend were built in Ruby, chosen originally for developer productivity - Stripe has since layered significant internal tooling and typing discipline on top of it to manage correctness at scale.

Sorbet

Type checker for Ruby

A gradual type checker Stripe built and open-sourced for Ruby, adding static type safety on top of a language that's normally dynamically typed - directly motivated by wanting more compile-time confidence in a codebase handling financial logic.

MongoDB / MySQL

Databases

Stripe has used both relational (MySQL) and document-oriented (MongoDB) storage across different parts of its systems, depending on the shape and consistency needs of the specific data involved.

Kafka

Event streaming

Used to reliably move events (like payment status changes) between internal services asynchronously, which matters when a single payment can trigger many downstream effects - fraud checks, notifications, accounting - that don't all need to happen synchronously.

Idempotency keys

API design pattern

Not a piece of software, but a core design pattern baked directly into Stripe's public API - a client-generated key that lets the exact same request be safely retried without ever double-charging a customer.

How it actually works

Idempotency keys, as a first-class API concept

Stripe's API popularized the idempotency key pattern for public APIs - a client-generated key attached to a request so that retrying the exact same request (after a timeout, a dropped connection) never accidentally double-charges a customer. Concretely: a client sends a payment request along with a unique key it generated itself; if the network fails and the client retries with that same key, Stripe recognizes it and returns the result of the original attempt instead of charging the card a second time. This is one of the most frequently cited real-world examples of idempotency in backend engineering, precisely because Stripe documented and productized the pattern so clearly.

Careful, deliberate API versioning

Stripe maintains long-term backward compatibility for its public API by versioning changes and giving every account a pinned API version, rather than forcing all clients to move in lockstep. In practice, this means a business that integrated with Stripe's API years ago can keep running that exact same integration indefinitely, even as Stripe ships new API versions for new customers - Stripe's servers internally translate between versions rather than breaking old clients. Publicly discussed as a deliberate tradeoff: added internal complexity, in exchange for external clients never getting an unannounced breaking change.

Adding static types to a dynamic language, on purpose

Stripe built and open-sourced Sorbet, a type checker for Ruby, specifically because a large, fast-growing codebase handling financial logic benefits enormously from catching a class of bugs at compile time rather than discovering them in production. This is a concrete example of choosing correctness tooling over developer convenience once the stakes of a mistake go up - Ruby without types is faster to write, but riskier at the scale and criticality Stripe operates at.

Treating the API contract itself as the product

Much of Stripe's public engineering and design writing focuses on the API surface as a designed product in its own right - consistent naming, predictable error shapes, extensive documentation - not just a thin wrapper around internal services. The recurring argument in their writing is that developer trust is itself a reliability property, not just a nice-to-have.

What “microservices” means here, concretely

Stripe's backend is organized into services responsible for distinct financial concerns (payments processing, fraud detection, billing, connected-account management), historically rooted in a large Ruby codebase with Sorbet-enforced typing, communicating internally through service boundaries and event streams like Kafka rather than one shared, untyped monolith.

Takeaway

Stripe's engineering culture, as far as it's publicly documented, treats correctness and API stability as the actual hard engineering problem - worth more deliberate investment than raw performance, because in payments, a fast wrong answer is worse than a slightly slower right one.

Based on

  • Stripe Engineering Blog - Idempotency keys
  • Stripe API documentation - versioning
  • Stripe Engineering Blog - Sorbet, a type checker for Ruby