Stripe Integration
The Premium system uses Stripe webhooks to manage subscription lifecycle events. This document covers webhook configuration, event routing, and subscription handling.
Architecture
Webhook Configuration
StripeEvent Engine
The application uses the stripe_event gem to handle webhooks. Configuration is in config/initializers/stripe_event.rb:
StripeEvent.signing_secret = ENV['STRIPE_WEBHOOK_SECRET_V2']
StripeEvent.configure do |events|
events.all do |event|
StripeWebhookJob.perform_later(event.as_json)
end
end
Route Configuration
The webhook endpoint is mounted in config/routes.rb:
mount StripeEvent::Engine, at: '/stripe-webhook'
Webhook URL: https://your-domain.com/stripe-webhook
Event Routing
The StripeWebhookJob intelligently routes events based on type and product metadata:
Routing Logic
class StripeWebhookJob < ApplicationJob
SUBSCRIPTION_EVENTS = %w[
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
].freeze
def perform(event_data)
event = Stripe::Event.construct_from(event_data)
if subscription_event?(event)
route_subscription_event(event)
else
Donations::ProcessStripeEventJob.perform_later(event_data)
end
end
private
def route_subscription_event(event)
subscription = event.data.object
product_id = subscription.plan&.product
metadata = fetch_product_metadata(product_id)
if metadata['platform'] == 'premium'
Premium::StripeEventJob.perform_later(event.as_json)
else
Donations::ProcessStripeEventJob.perform_later(event.as_json)
end
end
end
Subscription Events
subscription.created
Triggered when a new premium subscription is created.
subscription.deleted
Triggered when a subscription is cancelled or expires.
Stripe Service
The Premium::StripeService handles all subscription-related business logic.
Event Handlers
class Premium::StripeService
def handle_subscription_created(event)
subscription = event.data.object
customer = fetch_customer(subscription.customer)
plan = product_metadata(subscription.plan.product)
user_result = users_service.find_or_create!(
customer.email,
extract_first_name(customer.name),
extract_last_name(customer.name),
plan.locale
)
users_service.make_premium!(customer.email, plan: plan)
Premium::SendEmailJob.perform_later(
'welcome',
user_result.to_h,
plan.to_h
)
end
def handle_subscription_deleted(event)
subscription = event.data.object
customer = fetch_customer(subscription.customer)
plan = product_metadata(subscription.plan.product)
users_service.disable_premium!(customer.email)
Premium::SendEmailJob.perform_later(
'goodbye',
{ email: customer.email, display_name: customer.name },
plan.to_h
)
end
end
Plan Data Structure
Plan = Data.define(:id, :name, :tier, :platform, :locale)
# Example:
# Plan(
# id: "prod_ABC123",
# name: "Premium Essential",
# tier: "essential",
# platform: "premium",
# locale: "it"
# )
Product Metadata
Stripe products must include specific metadata for the premium system to work correctly.
Required Metadata Fields
| Field | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | Must be "premium" for premium routing |
tier | string | Yes | Plan tier identifier (e.g., "essential") |
name | string | No | Display name for emails |
locale | string | No | Default locale (e.g., "it", "en") |
Stripe Dashboard Configuration
- Navigate to Products in Stripe Dashboard
- Select or create a product
- Scroll to Metadata section
- Add the required metadata fields:
platform: premium
tier: essential
name: Premium Essential
locale: it
API Example
Stripe::Product.create(
name: 'Premium Essential',
metadata: {
platform: 'premium',
tier: 'essential',
name: 'Premium Essential',
locale: 'it'
}
)
Webhook Security
Signature Verification
All webhooks are verified using Stripe's signing secret:
# config/initializers/stripe_event.rb
StripeEvent.signing_secret = ENV['STRIPE_WEBHOOK_SECRET_V2']
The stripe_event gem automatically verifies signatures using the Stripe-Signature header.
Verification Flow
Error Handling
Retry Strategy
Failed webhook processing is retried by Sidekiq:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 attempts, the job is moved to the dead queue.
Common Errors
| Error | Cause | Resolution |
|---|---|---|
Stripe::SignatureVerificationError | Invalid webhook signature | Check STRIPE_WEBHOOK_SECRET_V2 |
Stripe::InvalidRequestError | Invalid API request | Check API key and request parameters |
Stripe::RateLimitError | Too many API requests | Implement backoff, reduce concurrency |
Idempotency
All handlers are designed to be idempotent:
def handle_subscription_created(event)
# Safe to retry - find_or_create! is idempotent
user_result = users_service.find_or_create!(...)
# Safe to retry - make_premium! updates to same state
users_service.make_premium!(email, plan: plan)
# Emails may be sent multiple times on retry
# Consider tracking sent emails by event ID
end
Testing Webhooks
Local Development
Use Stripe CLI to forward webhooks locally:
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login to Stripe
stripe login
# Forward webhooks to local server
stripe listen --forward-to localhost:3000/stripe-webhook
# Note the webhook signing secret from output
# Set it as STRIPE_WEBHOOK_SECRET_V2
Procfile.dev Integration
The development Procfile includes Stripe CLI:
web: bin/rails server
worker: bundle exec sidekiq
stripe: stripe listen --forward-to localhost:3000/stripe-webhook
Trigger Test Events
# Trigger a subscription created event
stripe trigger customer.subscription.created
# Trigger with specific product
stripe trigger customer.subscription.created \
--override subscription:plan:product=prod_ABC123
Monitoring
Logging
The service logs all webhook events:
logger.info("Processing subscription event",
event_type: event.type,
event_id: event.id,
customer_id: subscription.customer,
product_id: subscription.plan&.product
)
Key Metrics
Monitor these metrics in production:
| Metric | Description |
|---|---|
| Webhook success rate | % of webhooks processed successfully |
| Webhook latency | Time to process each webhook |
| Queue depth | Number of pending webhook jobs |
| Error rate by type | Errors grouped by exception class |
Stripe Dashboard
Monitor webhook delivery in Stripe Dashboard:
- Go to Developers > Webhooks
- Select your endpoint
- View Event deliveries for status and retries
Related Documentation
- Overview - System architecture overview
- Setup Guide - Stripe webhook configuration
- Email Notifications - Post-event email sending