Library / SDK
stripe/stripe-ruby avatar
stripe/stripe-ruby

Inside stripe-ruby: self-building resources, per-request keys and a departing Makefile

Ruby library for the Stripe API.

2,174 stars696 forksRubyMIT

At a glance

What is it?
stripe-ruby wraps the Stripe API for Ruby applications with resource classes that initialize themselves from each response, per-request keys for Connect platforms, and a client you can retune at runtime. It is generated from an OpenAPI description and checked by Sorbet, and its own build files disagree about which Ruby versions still matter.
Who is it for?
Adopt stripe-ruby when a Ruby service talks to Stripe with a server-side secret key and needs per-request accounts for Connect, since self-building resources and `last_response` save you the hand-written HTTP layer. Leave it out for a Ruby 2.6 codebase, which the stated runtime floor no longer covers, and for any team whose code still sits on the legacy pattern once new endpoints land on `StripeClient`.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Resources that build themselves from each API response

stripe-ruby does not hand you a class per endpoint with a fixed field list. Every resource class initializes itself from the API response that comes back, and that single design decision is what lets one gem version sit against a wide range of Stripe API versions instead of forcing a code generation run into your deploy pipeline.

Two files at the repository root explain where that shape comes from. `CODEGEN_VERSION` and `OPENAPI_VERSION` sit beside `lib/`, while `rbi/` and `sorbet/` hold type definitions that Sorbet checks. The surface is generated from an OpenAPI description and typed ahead of time, and yet the object you touch at runtime still describes itself from the payload.

For a caller this has one consequence worth planning around. A field your account's pinned API version returns is reachable the moment it arrives, and a field that version no longer returns is not there to read at all.

gem install stripe, then your first customer call

Installation is one command, and the documentation is explicit that you do not need the source unless you want to modify the gem.

sh
gem install stripe

The first call needs a secret key from the Stripe Dashboard, and everything travels through a `StripeClient` instance.

ruby
require 'stripe'

client = Stripe::StripeClient.new("sk_test_...")

# list customers
customers = client.v1.customers.list()

# retrieve single customer
customer = client.v1.customers.retrieve('cus_123456789')

Under Bundler the source line matters as much as the gem line, because anything fetched over http could be compromised in transit.

ruby
source 'https://rubygems.org'

gem 'rails'
gem 'stripe'

Building the gem yourself is `gem build stripe.gemspec`, and the runtime floor is Ruby 2.7, with support for 2.7 already deprecated and due for removal in upcoming major versions. The gem is MIT licensed, which permits use and modification. One gap sits here: the newest versions published under this name are v19.7.0-alpha.5 on 2026-09-23, v19.7.0-alpha.4 on 2026-09-16 and v19.7.0-alpha.3 on 2026-09-09, all on one alpha line, and the documented install command carries no prerelease flag, so the README does not tell you how to reach that line at all.

One client, many keys: per-request accounts for Connect

A single client object can carry many keys, which is the situation for an application running Stripe Connect across several accounts.

ruby
require "stripe"

client = Stripe::StripeClient.new("sk_test_...")

client.v1.customers.list(
  {},
  {
    api_key: 'sk_test_...',
    stripe_account: 'acct_...',
    stripe_version: '2018-02-28',
  }
)

That trailing hash is the per-request layer. `api_key`, `stripe_account` and `stripe_version` go out with the call, so no global state changes and no second client gets constructed.

The alternative is inside the same gem rather than another repository. `StripeClient` arrived in v13, and the legacy pattern it replaced still works today, though it is about to be marked deprecated, and once that happens new API endpoints will exist only on `StripeClient`. There are no current plans to drop the legacy pattern for endpoints that already exist, and the README stops short of promising that will hold. No competing Stripe client library is named anywhere in it either, so a cross-language comparison has to be sourced elsewhere.

The indexer returns nil where the accessor raises

Every resource answers to two accessors, and they disagree on failure.

ruby
customer = client.v1.customers.retrieve('cus_123456789')
puts customer['id']
puts customer.id

The indexer is forgiving, the accessor is not.

ruby
customer = client.v1.customers.retrieve('cus_123456789')
puts customer['unknown'] # nil
puts customer.unknown # raises NoMethodError

An undefined property raises an exception through the accessor and returns nil through the indexer, so feature detection written against a field your gem predates fails loudly as `NoMethodError` in production instead of quietly taking the other branch. The documentation also steers you to the bracket form for custom hashes you supply yourself, such as `Customer.metadata`, where neither path behaves like a typed attribute.

Response metadata rides on the same object. `customer.last_response.http_status` and `customer.last_response.http_headers` expose the status code and the headers without paying for a second request.

Retries, idempotency keys and the 409 Conflict that resends

Retries stay off until you ask for them, and the count you can set is small.

ruby
Stripe.max_network_retries = 2

Connection errors and timeouts are the obvious triggers, yet the trigger list also covers API responses, with HTTP status `409 Conflict` named as one of them. A conflict is a server answer rather than a transport fault, so with this setting on, a request that already had an effect on Stripe's side can go out a second time. That is safe because idempotency keys are attached to requests to guarantee that retries are safe, and the guarantee lives on the server, not in your process.

One check before shipping: know whether your handler assumes one call equals one charge attempt. A retried conflict still shows two attempts in your logs even though the server treats them as a single operation, and log-based reconciliation code that counts attempts will not agree with your ledger.

Timeouts, a proxy and the CA bundle you can swap

Three network knobs are writable at runtime, and one of them carries a caveat that has aged badly.

ruby
Stripe.open_timeout = 30 # in seconds
Stripe.read_timeout = 80
Stripe.write_timeout = 30 # only supported on Ruby 2.6+

The library's own runtime floor is Ruby 2.7, so the Ruby 2.6 condition on the write timeout describes an interpreter this gem no longer supports and the caveat guards nothing today. That same 2.7 threshold appears twice more. The justfile runs Sorbet only when the runtime reports 2.7 or newer, and the Makefile's ci-test recipe repeats the comparison in shell and prints the same fallback message in both places, so on any supported runtime neither fallback branch can execute.

Certificate handling has two separate shapes. By default the library uses its own internal bundle of known CA certificates.

ruby
Stripe.ca_bundle_path = 'path/to/ca/bundle'

Assigning the path swaps your bundle in wholesale, whereas the contributor-side recipe update-certs runs `bundle exec rake update_certs` to refresh the copy shipped inside the gem. Proxies take a URL with credentials embedded.

ruby
Stripe.proxy = 'https://user:[email protected]:1234'

Notices the SDK prints and cannot always suppress

Output matters more here than in most client libraries, because this one writes to standard output in situations you do not switch on.

sh
$ export STRIPE_LOG=info

Setting `STRIPE_LOG` to `debug` or `info` raises verbosity, and `Stripe.log_level` can be set in code with `Stripe::LEVEL_INFO`.

Then there is the notice channel, and its switch covers less ground than the name promises. Notices arrive in the `Stripe-Notice` header. They print always when the SDK runs in an agent environment, and for calls against test accounts or sandboxes they also print when the SDK runs outside one. Setting `STRIPE_SUPPRESS_NOTICES` to `true` before the integration starts suppresses them outside an agent environment and nowhere else. A wrapper that parses this process's output has to accept a line it cannot configure away, which bites anything treating stdout as structured data. Two settings that look global, `STRIPE_LOG` and `STRIPE_SUPPRESS_NOTICES`, are read from the environment before your code runs, so a service that scrubs its environment breaks both at once.

A Makefile marked for deletion and the justfile replacing it

This repository carries two build systems and names the departing one. The first line of the Makefile states that the file is deprecated and slated for deletion, pointing at the equivalent `just` commands, and the justfile holds the surviving paths: `prepare` runs lint, typecheck and test in that order, `lint` runs RuboCop with `--autocorrect`, and `format` adds `--display-only-fail-level-offenses` because the corrected-offenses report runs to roughly 1.5MB. Version bumps are duplicated as well, with both files writing the version into `VERSION` and rewriting `lib/stripe/version.rb`.

One recipe is useful even to an application author.

bash
print-version-info:
    #!/usr/bin/env bash
    set -euo pipefail
    echo "pinned-api-version: $(rg -N --color never -m1 -o '[0-9]{4}-[0-9]{2}-[0-9]{2}[.\w-]*' lib/stripe/api_version.rb)"
    echo "minimum-runtime-version: $(rg -N --color never -o 'required_ruby_version = ">= ([^"]+)"' --replace '$1' stripe.gemspec)"

It greps `lib/stripe/api_version.rb` for the API date the checkout assumes and pulls `required_ruby_version` out of `stripe.gemspec`, which is the fastest way to see what you actually resolved. Two smaller details: the justfile opens with `set quiet` plus an optional import from `../sdk-codegen/utils.just`, so recipes can resolve differently inside Stripe's own codegen checkout than in a plain clone, and lint configuration is split between `.rubocop.yml` and `.rubocop_todo.yml`, meaning the todo file carries accepted debt that a style run will not flag.

The last push on this repository landed on 2026-09-28.

Editorial conclusion

Adopt stripe-ruby when a Ruby service talks to Stripe with a server-side secret key and needs per-request accounts for Connect, since self-building resources and `last_response` save you the hand-written HTTP layer. Leave it out for a Ruby 2.6 codebase, which the stated runtime floor no longer covers, and for any team whose code still sits on the legacy pattern once new endpoints land on `StripeClient`. Confirm the API date and minimum runtime printed by `print-version-info` before the first charge.

Frequently asked questions

Which Ruby versions does stripe-ruby support?

The library states it currently supports Ruby 2.7+ under Stripe's Language Version Support Policy, and says support for Ruby 2.7 is deprecated and will be removed in upcoming major versions. Its own typecheck step runs Sorbet only when the runtime reports 2.7 or newer.

How do I send a request with a different Stripe account or API key?

Pass a second hash to the resource call carrying `api_key`, `stripe_account` and `stripe_version`. That per-request layer is the documented approach for apps needing multiple keys over the lifetime of one process, such as one using Stripe Connect.

What happens if I read a resource property that does not exist?

The accessor raises an exception, `NoMethodError`, while the indexer returns nil. For custom hashes you provide yourself, such as `Customer.metadata`, the documentation directs you to the bracket accessor.

Can I turn off the notices that stripe-ruby prints?

Setting `STRIPE_SUPPRESS_NOTICES` to `true` suppresses them only when the SDK runs outside an agent environment. Notices are printed without exception in an agent environment, and for test account or sandbox calls they print outside one as well.

What alternative to stripe-ruby does the project document?

Its own legacy pattern, which predates v13 and still works for existing endpoints. It is about to be marked deprecated, and new API endpoints will be reachable only through `StripeClient` once that happens. No competing Stripe client library is named in the README.

How do I find out which Stripe API version my checkout pins?

Requests default to the API version pinned to the account making them, and `Stripe.api_version` overrides that globally while `stripe_version` overrides it per request. In the repository, the justfile recipe print-version-info reads the pinned date straight out of `lib/stripe/api_version.rb`.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. stripe/stripe-ruby on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/stripe-stripe-ruby.svg)](https://hysenlabs.com/projects/stripe-stripe-ruby)

Was this useful?