Disclaimer. This article is based entirely on Stripe's public communications — their engineering blog, conference talks, open source repositories, and external case studies. It is not an official Stripe architecture document. We model what we know publicly to illustrate how a complex stack can be made legible with the C4 model. Where details are inferred rather than stated by Stripe, we say so.
Anatomy of a Charge: Modeling Stripe in C4 with Archyl
A POST request hits Stripe at 3 am Pacific during Black Friday. Twenty seconds later the merchant has been credited, the cardholder's issuing bank has authorized the charge, the funds are queued for settlement, the merchant's server has received a signed webhook, and Stripe's risk engine has scored the transaction in under 100 milliseconds.
That single request, repeated 27 395 times per second at peak in BFCM 2024, traverses fourteen Stripe systems and at least four external networks before frame zero ever hits the merchant's dashboard.
In 2025, Stripe processed $1.9 trillion through this stack, sustaining 99.9999% uptime during Black Friday — six nines, equivalent to 32 seconds of downtime in a year. They did this on roughly fifteen million lines of Ruby, type-checked by their own bespoke type system.
How do you understand a stack that runs the credit-card payments of half the internet? Like Netflix, you don't — not all at once. That's exactly the problem the C4 model was invented to solve.
In this post we follow one user action — a single stripe.PaymentIntents.create() call — and watch it traverse Stripe's architecture across the four C4 levels. We won't cover every product. We'll trace one charge, write the ADRs that explain the choices we hit along the way, and end with a map of the teams who own each box.
Level 1 — System Context: fifteen products, one trillion dollars

At the System Context level, Stripe is not "a payments API". It's fifteen distinct product systems sharing a foundation:
- Payments — the historical core: charges, payment intents, refunds, payouts
- Connect — multi-party payments, marketplaces, platforms
- Billing — subscriptions, invoices, metered billing
- Atlas — Delaware C-Corp/LLC incorporation
- Capital — merchant lending
- Issuing — virtual and physical card creation
- Treasury — banking-as-a-service (Goldman Sachs partnership)
- Identity — KYC/KYB verification
- Tax — sales tax, VAT, GST
- Climate — carbon offsets per transaction
- Radar — fraud detection ML (sub-100ms scoring)
- Sigma — SQL analytics on Stripe data
- Terminal — POS hardware
- Financial Connections — bank account linking (their Plaid)
- Apps Marketplace — third-party apps in the Dashboard
Around them: merchants, cardholders, the four card networks (Visa, Mastercard, Amex, Discover) plus regional ones (JCB, UnionPay), 100+ alternative payment methods (Apple Pay, Klarna, ACH, SEPA, iDEAL...), acquiring and issuing banks, banking partners (Goldman Sachs, Evolve, Cross River), tax authorities, identity providers, and AWS as the underlying mono-cloud.
Fifteen systems, eight categories of external actors. Everything else is detail.
This is the gift of Level 1: at System Context, you don't need to know that Payments is twenty microservices. You need to know it exists, that it talks to card networks, that Connect coordinates with Treasury, and that AWS underpins everything. The diagram is a conversation starter, not an inventory.
ADR-001 · Idempotency keys baked into the API from day 1
Status · Accepted (2011, still active in 2026)
Context · Networks are unreliable. A merchant retrying a failed POST /charges could double-charge a customer. The industry's response in 2011 was "the merchant should handle that" — pushing distributed-systems complexity to every API consumer.
Decision · Require Idempotency-Key on every mutating API call. Store the key, the request hash, and the response. On retry, replay the stored response if the key matches. Ship it as a first-class part of the API, not an opt-in feature.
Consequences · Stripe's idempotency model became the de facto industry standard. The IETF's Idempotency-Key draft is directly inspired by it. Every Stripe API user, knowingly or not, benefits from a contract that turns "POST /charges" into a safely retryable operation. We'll zoom into how it works at Level 3.
This is the single architectural choice that most shapes Stripe's API surface. Without it, the C4 model would have to expose retry logic on every mutating boundary — leaking distributed-systems complexity to every consumer.
In Archyl, that's how an ADR earns its place: it explains why the boundary looks the way it does.
Level 2 — Container: zoom into Payments core

The merchant's stripe.PaymentIntents.create() lands at the edge of Payments. Let's open the box.
Inside Payments, public sources reveal at least these containers:
- Apiori — the API gateway. Originally Ruby + Rails, with hot-path code progressively rewritten in Go for sub-150 µs latency on the auth and routing layer.
- Idempotency layer — the cross-cutting concern that sits in front of every mutating endpoint. Backed by PostgreSQL with row-level locking.
- PaymentIntent service — orchestrates the state machine:
requires_payment_method→requires_confirmation→requires_action(3DS challenge) →processing→succeeded(orrequires_capture). - Card Data Vault — physically isolated PCI environment, AES-256 at rest, no main service can decrypt a PAN. All card data flows through tokenization.
- Radar — fraud scoring in under 100 ms p99. Pure DNN since 2022, ResNeXt-inspired architecture.
- Network connectors — adapters for Visa, Mastercard, Amex, etc. They speak ISO 8583 and proprietary protocols on the wire.
- Webhook delivery service — at-least-once delivery, 16 retries over 3 days with exponential backoff, HMAC-SHA256 signing.
- Ledger — immutable event log, ~5 billion events per day, ~100 ledger entries per payment. Source of truth for reconciliation, audit, and accounting.
- DocDB — Stripe's custom database-as-a-service, built on top of MongoDB. 5 million queries per second, 5 000+ collections, 2 000+ shards, petabytes of financial data.
The technology stack at this level: Ruby with Sorbet types as the dominant language (15M lines), Go on hot paths, PostgreSQL for relational concerns (idempotency, accounts), DocDB for high-volume document workloads, Apache Kafka for events, Apache Pinot for real-time analytics, Apache Flink for stream processing.
A typical charge touches Apiori → Idempotency layer → PaymentIntent service → (Vault for tokens) → (Radar for risk in parallel) → Network connector → Ledger → Webhook fanout. All of that, with retries, instrumented end-to-end via Veneur and routed safely through Smokescreen for any external egress.
ADR-002 · DocDB — build on top of MongoDB rather than rewrite
Status · Accepted (~2018, ongoing investment)
Context · By 2018, Stripe's data volume on MongoDB was straining the off-the-shelf product: schema migrations on petabyte collections were dangerous, sharding was operational toil, and 99.999% uptime requirements left no room for downtime windows. The industry would have said "rewrite to a relational store".
Decision · Don't migrate the data layer to a different engine. Instead, build a custom Database-as-a-Service on top of MongoDB: a Database Proxy, a Chunk Metadata Service, a Data Movement Platform that performs the dual-write/backfill/dual-read/cleanup migration pattern as a managed primitive, and a CDC service for outbound events.
Consequences · Stripe gets the best of MongoDB's flexible document model plus the operational guarantees of a managed platform: 5 M QPS, 99.999% steady-state uptime, zero-downtime migrations as a routine operation. The "Mongo → DynamoDB" migration that internet rumor occasionally claims happened? Never happened. They doubled down instead.
This ADR is a great example of path-dependent architecture: the right answer in 2018 was to extend, not replace.
Level 3 — Component: inside the Idempotency layer

Of all the components in Stripe's stack, the Idempotency layer is the most publicly documented — Brandur Leach's 2017 post on it remains a canonical reference for distributed systems engineers.
A single POST /charges with an idempotency key traverses these components inside the layer:
- Request hasher — computes a deterministic hash of the request payload. If the same idempotency key arrives with a different payload, the API returns 422 (the client made a programming mistake).
- Idempotency key store — a PostgreSQL table keyed on
(account_id, idempotency_key). Includesrequest_hash,response_code,response_body,recovery_point,last_run_at,locked_at. Thelocked_atcolumn implements row-level locking for concurrent retries. - Phase executor — splits the operation into atomic phases separated by foreign state mutations. Each phase is either purely local (Postgres-only, transactional with the idempotency row) or one external side-effect (Vault tokenize, network charge, send webhook).
- Recovery point tracker — persists the current phase:
started→ran_charge→wrote_ledger→enqueued_webhook→finished. On retry, the executor resumes at the recovery point. - Job enqueuer — for asynchronous side-effects (emails, webhooks), enqueues a durable job in the same Postgres transaction as the recovery-point update. Atomic by construction.
- Background runner — drains the job queue with its own retry semantics, exponential backoff, and dead-letter store.
The pattern is brutally simple once you see it: every local mutation lives in the same Postgres transaction as the idempotency-key row update; every external mutation lives between two recovery points. This shape eliminates an entire class of double-write bugs that plague distributed systems built without this primitive.
This is what Component-level C4 looks like: not "here's some code", but "here's the chain of business-meaningful primitives, each owned, each replaceable, each measurable".
ADR-003 · Sorbet — invest in a type checker, don't rewrite Ruby
Status · Accepted (~2017, open-sourced 2019, still default)
Context · By 2017, Stripe's Ruby + Rails monolith had grown past 10 million lines. The dominant industry advice for a fintech of that size was to rewrite in a typed language — Java, Go, or Scala. The cost of such a rewrite was estimated in years and hundreds of engineers. The Ruby developer experience, meanwhile, was Stripe's competitive advantage in shipping speed.
Decision · Don't rewrite. Build a gradual type checker for Ruby. Take 18 months and a small team to ship a multithreaded, IDE-grade type system that scales to millions of lines. Open-source it.
Consequences · Sorbet now type-checks 15 million lines of Stripe Ruby with sub-second incremental latency. Stripe never paid the rewrite tax. They paid the invent-a-type-checker tax — once. Sorbet has become a meaningful open-source project used by Coinbase, Shopify, GitHub, and others.
In an Archyl model, ADRs like this one travel with the architecture. When you click into the Apiori container in 2026 and see "Ruby + Sorbet", you also see the 2017 decision that explains why it's not Java.

Three decisions. Three cards in Archyl, each linked to the C4 elements they shape — Idempotency keys to every mutating endpoint, DocDB to the data tier, Sorbet to every Ruby container. The diagram is the present tense; the ADRs are the why.
Ownership: turning a model into accountability

A C4 model is a static artifact until you map teams to it.
Stripe communicates publicly about its engineering structure: a strong Foundations group (Infrastructure, Security, Data Platform, Developer Experience), product teams aligned to each major system (Payment Methods, Connect, Capital, Identity, Issuing, Treasury, Climate, Radar), and cross-cutting groups for ML and observability.
Drop these onto the C4 model:
- Foundations owns Apiori, Sorbet, Veneur, Smokescreen, the Kubernetes platform, DocDB, Card Data Vault — the substrate every product builds on
- Payment Methods owns the Network connectors, the PaymentIntent state machine, the per-method services (cards, ACH, SEPA, wallets)
- Connect owns the Account service, capability gating, multi-party funds flow, payouts
- Capital owns the lending decision pipeline and the integration with the merchant's Stripe Payments history
- Identity owns KYC/KYB workflows and the compliance gating for Connect onboarding
- Radar (ML team) owns the fraud DNN, model serving, training pipelines
- Issuing & Treasury own the bank-partner integrations and card lifecycle
- Climate owns the carbon offsetting marketplace integration
This mapping isn't decoration. It's the substrate for everything that comes next.
Once a system, container, or component has a team owner, drift detection becomes accountable: when a new service appears in commits and isn't on the diagram, a specific team gets asked. When a conformance rule is violated (a non-Foundations service trying to read from Card Data Vault directly, say), there's a name in an inbox.
In Archyl, the Ownership Map is the moment a documentation tool becomes a governance tool.
Drift, conformance, and the weekly digest
A model this large will drift. New products land — Climate in 2022, Treasury, Tax expansions, Apps Marketplace. Stacks shift — Apiori paths migrate to Go, Pinot replaces older analytics, Sorbet typecheck strictness ratchets up.
Archyl computes a drift score weekly: the gap between the documented C4 model and what's currently in the codebase. Conformance rules add the policy layer — "every container needs an owner team", "only Foundations services can read Card Data Vault", "every public API change must reference an API-versioning ADR".
For Stripe, that's drift detection at the scale of fifteen million lines and 27 000 requests per second. The rules are the same as for ten services.
And the Architecture Team Digest we shipped recently would, in a Stripe-like setup, mean:
- Foundations' Monday digest covers Apiori, the Vault, DocDB, the K8s platform
- Payment Methods' digest covers Network connectors, the PaymentIntent service, every per-method integration
- Radar's digest covers the fraud DNN, training pipelines, model rollouts
- Each digest scoped to its team's owned perimeter
Same surface. Different scopes. That's the symmetry C4 + ownership unlock.
You don't need to process a trillion dollars
You're not Stripe. Most engineering organizations aren't.
But the lesson scales down. The discipline of separating Context from Container from Component, of writing the ADR that explains a path-dependent decision (we built on top of Mongo, we type-checked Ruby instead of rewriting it), of attaching ownership to every box — that discipline is what keeps a stack of fifty services from feeling like fifteen million lines.
C4 + ADRs + Ownership + Drift + Conformance is what Archyl gives you out of the box. The Stripe example is just the largest plausible stress-test of the model in the financial-systems domain.
Open up your own architecture. Sketch fifteen products (or three, if you have three). Pick the one with the most surprising past-tense decisions and zoom into its containers. Write three ADRs explaining what would baffle a new hire. Map a team to every container.
You'll be ahead of where most engineering organizations get to in a year.
Want to model your own architecture in C4? Start with Archyl. Read more on why ADRs and C4 work better together or how Architecture Change Requests bring pull-request rigor to your C4 model. The previous case study modeled Netflix in C4 — Anatomy of a Play.