Skip to main content

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

FieldTypeRequiredDescription
platformstringYesMust be "premium" for premium routing
tierstringYesPlan tier identifier (e.g., "essential")
namestringNoDisplay name for emails
localestringNoDefault locale (e.g., "it", "en")

Stripe Dashboard Configuration

  1. Navigate to Products in Stripe Dashboard
  2. Select or create a product
  3. Scroll to Metadata section
  4. 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:

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours

After 5 attempts, the job is moved to the dead queue.

Common Errors

ErrorCauseResolution
Stripe::SignatureVerificationErrorInvalid webhook signatureCheck STRIPE_WEBHOOK_SECRET_V2
Stripe::InvalidRequestErrorInvalid API requestCheck API key and request parameters
Stripe::RateLimitErrorToo many API requestsImplement 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:

MetricDescription
Webhook success rate% of webhooks processed successfully
Webhook latencyTime to process each webhook
Queue depthNumber of pending webhook jobs
Error rate by typeErrors grouped by exception class

Stripe Dashboard

Monitor webhook delivery in Stripe Dashboard:

  1. Go to Developers > Webhooks
  2. Select your endpoint
  3. View Event deliveries for status and retries