# How to use Stripe API with Ruby

- Tool: Stripe
- Difficulty: Beginner
- Time required: 15 minutes
- Compatibility: Ruby 3.0+, Rails 7+, Stripe API 2024-12+
- Last updated: March 2026

## TL;DR

Install the Stripe Ruby gem, configure your secret key, and make API calls from a Rails application. This guide walks through setting up the gem, creating charges, and handling webhooks so you can accept payments in any Ruby or Rails project within minutes.

## Accepting Payments in Ruby on Rails with the Stripe Gem

The official Stripe Ruby gem gives you full access to the Stripe API from any Ruby application. In a Rails project you can create charges, manage customers, and listen for webhook events with just a few lines of code. This tutorial covers gem installation, API key configuration, creating a PaymentIntent, and verifying webhook signatures.

## Before you start

- A Stripe account with access to your secret key (sk_test_...)
- Ruby 3.0 or later installed locally
- A Rails 7+ application (or plain Ruby project)
- Bundler for dependency management

## Step-by-step guide

### 1. Install the Stripe gem

Add the Stripe gem to your Gemfile and run bundle install. This pulls in the official Stripe client library that wraps every Stripe API endpoint.

```
# Gemfile
gem 'stripe', '~> 10.0'

# Then run:
# bundle install
```

**Expected result:** The stripe gem is installed and available in your Rails application.

### 2. Configure your API key

Set your Stripe secret key in an initializer. Never hard-code the key — use an environment variable. The sk_test_ key is for test mode; switch to sk_live_ for production.

```
# config/initializers/stripe.rb
Stripe.api_key = ENV.fetch('STRIPE_SECRET_KEY')
Stripe.api_version = '2024-12-18'
```

**Expected result:** Stripe is configured and ready to make API calls when the Rails app boots.

### 3. Create a PaymentIntent from a controller

Add a controller action that creates a PaymentIntent. The amount is in cents — 2000 means $20.00. Return the client_secret to your frontend so Stripe.js can confirm the payment.

```
# app/controllers/payments_controller.rb
class PaymentsController < ApplicationController
  skip_before_action :verify_authenticity_token, only: [:create]

  def create
    intent = Stripe::PaymentIntent.create(
      amount: params[:amount].to_i,  # amount in cents
      currency: 'usd',
      automatic_payment_methods: { enabled: true }
    )
    render json: { client_secret: intent.client_secret }
  rescue Stripe::StripeError => e
    render json: { error: e.message }, status: 422
  end
end
```

**Expected result:** POST /payments returns a JSON object with a client_secret string that your frontend uses to confirm the payment.

### 4. Handle webhooks

Create a webhook endpoint to receive events like payment_intent.succeeded. Always verify the webhook signature to ensure the event came from Stripe.

```
# app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def stripe
    payload = request.body.read
    sig_header = request.env['HTTP_STRIPE_SIGNATURE']
    endpoint_secret = ENV.fetch('STRIPE_WEBHOOK_SECRET')

    event = Stripe::Webhook.construct_event(payload, sig_header, endpoint_secret)

    case event.type
    when 'payment_intent.succeeded'
      handle_successful_payment(event.data.object)
    end

    head :ok
  rescue Stripe::SignatureVerificationError
    head :bad_request
  end

  private

  def handle_successful_payment(payment_intent)
    Rails.logger.info("Payment succeeded: #{payment_intent.id}")
  end
end
```

**Expected result:** Stripe sends POST requests to your webhook URL and your app processes them securely.

### 5. Test with a test card

Use Stripe's test card number to simulate a successful payment. Make sure your Stripe Dashboard is in test mode and you are using sk_test_ keys.

```
# Test card details:
# Number: 4242 4242 4242 4242
# Expiry: any future date (e.g. 12/34)
# CVC: any 3 digits (e.g. 123)
# ZIP: any 5 digits (e.g. 10001)
```

**Expected result:** The payment succeeds in test mode, a PaymentIntent with status 'succeeded' appears in your Stripe Dashboard under Test Data.

## Complete code example

File: `app/controllers/payments_controller.rb`

```ruby
# app/controllers/payments_controller.rb
# Full PaymentIntent controller for Rails + Stripe
#
# Routes (add to config/routes.rb):
#   post '/payments', to: 'payments#create'
#   post '/webhooks/stripe', to: 'webhooks#stripe'

class PaymentsController < ApplicationController
  skip_before_action :verify_authenticity_token, only: [:create]

  # POST /payments
  # Body: { "amount": 2000, "currency": "usd" }
  def create
    intent = Stripe::PaymentIntent.create(
      amount: params[:amount].to_i,
      currency: params[:currency] || 'usd',
      automatic_payment_methods: { enabled: true },
      metadata: { order_id: params[:order_id] }
    )

    render json: {
      client_secret: intent.client_secret,
      payment_intent_id: intent.id
    }
  rescue Stripe::CardError => e
    render json: { error: e.message }, status: 402
  rescue Stripe::InvalidRequestError => e
    render json: { error: e.message }, status: 400
  rescue Stripe::StripeError => e
    render json: { error: 'Payment processing failed. Please try again.' }, status: 500
  end

  # GET /payments/:id/status
  def status
    intent = Stripe::PaymentIntent.retrieve(params[:id])
    render json: {
      status: intent.status,
      amount: intent.amount,
      currency: intent.currency
    }
  rescue Stripe::StripeError => e
    render json: { error: e.message }, status: 404
  end
end
```

## Common mistakes

- **Hard-coding the Stripe secret key in source code** — undefined Fix: Always use ENV.fetch('STRIPE_SECRET_KEY') and store the key in Rails credentials or a .env file.
- **Passing the amount in dollars instead of cents** — undefined Fix: Stripe expects amounts in the smallest currency unit. For USD, 2000 = $20.00.
- **Not verifying webhook signatures** — undefined Fix: Always use Stripe::Webhook.construct_event with your endpoint secret to prevent forged events.
- **Using live keys in development** — undefined Fix: Use sk_test_ and pk_test_ keys during development. Only switch to live keys in production.

## Best practices

- Pin the Stripe gem to a major version (e.g., '~> 10.0') to avoid breaking changes
- Set Stripe.api_version explicitly so your app is not affected by Stripe API updates
- Store all Stripe keys in environment variables, never in code or config files committed to Git
- Use idempotency keys for create operations to prevent duplicate charges during retries
- Log webhook event IDs to detect and skip duplicate deliveries
- Wrap Stripe calls in begin/rescue blocks and handle each error type separately
- Test with multiple test card numbers to cover success, decline, and authentication scenarios

## Frequently asked questions

### Which Ruby versions does the Stripe gem support?

The Stripe Ruby gem supports Ruby 2.7 and later. For the best experience and security patches, use Ruby 3.1 or later.

### Can I use the Stripe gem outside of Rails?

Yes. The stripe gem works in any Ruby project — Sinatra, plain Ruby scripts, or background jobs. Just require 'stripe' and set your API key.

### How do I handle Stripe errors in Ruby?

Rescue Stripe::StripeError for a catch-all, or rescue specific subclasses like Stripe::CardError, Stripe::RateLimitError, and Stripe::InvalidRequestError for more granular handling.

### Is it safe to expose the client_secret to the frontend?

Yes. The client_secret is designed to be passed to the frontend. It can only confirm the specific PaymentIntent it belongs to and cannot be used to create new charges.

### How do I test webhooks locally?

Use the Stripe CLI: run 'stripe listen --forward-to localhost:3000/webhooks/stripe' to forward events to your local Rails server.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-use-stripe-api-with-ruby
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-use-stripe-api-with-ruby
