async-stripe
Strongly-typed async Rust bindings for the Stripe API, code-generated from Stripe's OpenAPI spec and kept current with weekly regeneration.
Repository Health
Technical Analysis
async-stripe is a Rust client for the Stripe HTTP API, covering the entire API surface by generating code directly from Stripe’s official OpenAPI specification. A weekly CI job pulls the latest spec, regenerates the crates, and opens a pull request, so the library rarely lags behind new Stripe features. Rather than shipping one monolithic crate, the API is split by resource area (stripe-core, stripe-billing, stripe-connect, and more) so consumers only compile the parts of the API surface they actually use.
Requests are built through a fluent, chained-setter API that mirrors Stripe’s own documentation and ends in a .send(&client) call, with pluggable async runtimes (tokio/hyper or async-std/surf) and a blocking mode for non-async codebases. Serialization uses serde for outgoing request parameters and the lighter miniserde for deserializing responses, a deliberate split chosen to keep compile times and binary size down across a very large generated type surface. Webhook signature verification and typed event matching live in a separate async-stripe-webhook crate, and list pagination is exposed as either an eager get-all call or a lazy futures stream.
What You Get
- Full Stripe API coverage split across modular per-resource crates (stripe-core, stripe-billing, stripe-connect, stripe-issuing, and more) so you only compile what you use
- A fluent, builder-style request API that mirrors Stripe’s own documentation, ending in
.send(&client)or.send_blocking(&client) - Async support for tokio/hyper and async-std/surf, plus a blocking client built on tokio internally
- Typed webhook signature verification and event matching via the async-stripe-webhook crate
- Cursor-based pagination exposed as a lazy
futuresStream (.paginate().stream(client)) or an eagerget_all()helper
Common Use Cases
- Creating and managing Customers, Charges, and Payment Intents from a Rust backend
- Processing Stripe webhook events (checkout completed, subscription updated, invoice paid) with compile-time-checked event matching
- Building recurring subscription billing flows against Stripe’s Billing and Invoicing objects
- Paginating through large Stripe object lists (customers, charges, transactions) without hand-rolling cursor logic
Under The Hood
Architecture The workspace is layered around a small set of core crates that every generated crate builds on: async-stripe-client-core defines the StripeClient/StripeBlockingClient traits, the RequestBuilder/CustomizableStripeRequest types, and the ListPaginator pagination abstraction, while async-stripe-types supplies shared List/SearchList/Object/AsCursorOpt types. The concrete transport backends (hyper for tokio, surf for async-std) live behind cfg-gated modules inside the top-level async-stripe crate, implementing StripeClient/StripeBlockingClient by unpacking a CustomizedStripeRequest and shipping bytes over HTTP; error.rs centralizes transport error conversion (hyper::Error, hyper_util errors, http_types::Error) into one thiserror-derived StripeError enum. Each generated resource crate (stripe-core, stripe-billing, stripe-connect, and roughly a dozen more) implements StripeRequest structs whose build() produces a RequestBuilder, which any compiled-in transport can execute; pagination is layered on top via the PaginableList trait, which treats List<T> and SearchList<T> uniformly and turns Stripe’s has_more/cursor fields into either a Vec via get_all() or a lazy Stream. Because every generated crate and both transport backends depend on the same StripeRequest/StripeClient trait boundary in client-core, that seam is effectively the workspace’s central abstraction.
Tech Stack
The workspace is a Cargo multi-crate project (resolver “2”, edition 2024, minimum Rust 1.88) built around serde (>=1.0.79) for serializing request parameters, miniserde 0.1.34 for deserializing responses, serde_json and serde_qs for query/form encoding, and smol_str for compact string types. Transport is hyper + hyper-util with a choice of native-tls or rustls (webpki-roots or native cert store) for the default tokio path, or surf + http_types for async-std; thiserror backs the StripeError enum and futures_util drives the pagination Stream. Code generation itself lives in a separate openapi crate (excluded from the main workspace) that parses Stripe’s published OpenAPI spec to emit the generated/* crates, run weekly by a dedicated GitHub Actions workflow that opens a PR with any API changes; a companion Next.js-based documentation site lives under site/.
Code Quality
A dedicated tests crate (its own Cargo.toml, under tests/tests/it/) holds integration tests covering pagination utilities, enum deserialization, generated-type round-trips, and both async and blocking client calls per resource. CI runs cargo fmt --all -- --check and clippy with -D warnings pinned to the crate’s minimum supported Rust version, with a separate job that verifies the codegen crate itself so generated and hand-written code are both linted. Error handling is fully typed through the thiserror-derived StripeError enum (distinct variants for Stripe API errors, deserialization failures, client/config errors, and timeouts) rather than stringly-typed errors, and the crate root sets #![forbid(unsafe_code)], #![deny(missing_docs, missing_debug_implementations)], and warns on clippy::missing_errors_doc/missing_panics_doc, enforcing documentation and safety discipline across the generated surface.
API Design
The request API favors chained setters that read close to Stripe’s own API docs, and pagination is exposed idiomatically as a futures::Stream rather than requiring manual cursor loops. The serde-for-serialize/miniserde-for-deserialize split is an unusual, deliberate tradeoff made specifically to control compile times and binary size across a huge generated type surface, and per-request overrides (CustomizableStripeRequest::request_strategy/account_id/timeout) let callers tune idempotency, retries, and multi-account behavior without threading configuration through every function signature. Splitting the API into a dozen-plus per-resource crates costs some setup ergonomics (multiple Cargo.toml dependency lines) in exchange for meaningfully smaller compiles than a single monolithic client crate would produce.