Does Ruby on Rails Work With Lemon Squeezy?
Ruby on Rails integrates seamlessly with Lemon Squeezy via webhooks and REST API, making it an excellent choice for selling digital products with minimal setup.
Quick Facts
How Ruby on Rails Works With Lemon Squeezy
Lemon Squeezy provides a REST API and webhook system that Rails handles naturally. You'll create webhook endpoints to receive purchase events (order.created, license.created, etc.), validate signatures using HMAC-SHA256, and update your database accordingly. Rails' built-in routing and ActiveRecord make this straightforward—typically a single controller action processes incoming webhooks. For customer-facing features, use Lemon Squeezy's checkout links or embedded checkout modal via JavaScript. The gem `lemonsqueezy-ruby` (community-maintained) provides a typed client, though the API is simple enough that many teams make direct HTTP calls with `Net::HTTP` or `httparty`. Rails' background job system (Sidekiq, Delayed Job) is useful for handling async webhook processing and syncing subscription states. Most developers implement a `Subscription` model that mirrors Lemon Squeezy data, creating a local source of truth for access control and license validation.
Best Use Cases
Webhook Receiver & Subscription Sync
bundle add httparty# app/controllers/webhooks_controller.rb
require 'openssl'
class WebhooksController < ApplicationController
skip_forgery_protection
def lemon_squeezy
payload = request.body.read
signature = request.headers['X-Signature']
# Validate webhook signature
secret = ENV['LEMON_SQUEEZY_WEBHOOK_SECRET']
expected_sig = OpenSSL::HMAC.hexdigest('sha256', secret, payload)
return head :unauthorized unless secure_compare(signature, expected_sig)
event = JSON.parse(payload)
handle_event(event['meta']['event_name'], event['data'])
head :ok
end
private
def handle_event(event_name, data)
case event_name
when 'order.created'
user = User.find_by(lemon_id: data['attributes']['customer_id'])
user&.update(subscription_active: true, license_key: data['attributes']['identifier'])
when 'subscription.expired'
Subscription.find_by(lemon_id: data['id'])&.update(active: false)
end
end
def secure_compare(a, b)
ActiveSupport::SecurityUtils.secure_compare(a, b)
end
endKnown Issues & Gotchas
Webhook signature validation skipped in development
Fix: Use ngrok or similar to test with real Lemon Squeezy webhooks, or mock the signature validation in test environment with a feature flag
Race condition between webhook and user refresh—user purchases, checks status before webhook arrives
Fix: Implement polling via Lemon Squeezy API on user page load, or use optimistic UI updates that verify server-side within seconds
Storing sensitive data like license keys—tempting but risky if your database is compromised
Fix: Only store license identifiers in Rails; fetch full license data from Lemon Squeezy API when needed, or encrypt sensitive fields with attr_encrypted
Lemon Squeezy customer portal doesn't integrate with Rails authentication
Fix: Build a Rails endpoint that validates the user session, then redirects to Lemon Squeezy's customer portal URL with proper context
Alternatives
- •Stripe + Rails—more payment methods, but requires separate tax handling; heavier integration
- •Gumroad + Rails—simpler webhooks, limited customization; better for one-off product sales
- •Paddle + Rails—similar to Lemon Squeezy with built-in tax, but less developer-friendly API
Resources
Related Compatibility Guides
Explore more compatibility guides