Skip to content

Rails Integration

This guide shows a full Rails integration using push delivery with signature verification and background job processing.

app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def create
unless verify_signature
head :unauthorized
return
end
payload = JSON.parse(request.raw_post)
event_id = payload["event_id"]
# Idempotency check
if ProcessedWebhook.exists?(event_id: event_id)
head :ok
return
end
# Enqueue for async processing
ProcessWebhookJob.perform_later(payload)
head :ok
end
private
def verify_signature
timestamp = request.headers["X-Gateway-Timestamp"]
signature = request.headers["X-Gateway-Signature"]
body = request.raw_post
expected = OpenSSL::HMAC.hexdigest(
"SHA256",
ENV.fetch("GATEWAY_DELIVERY_SECRET"),
"#{timestamp}.#{body}"
)
ActiveSupport::SecurityUtils.secure_compare(signature.to_s, expected)
end
end
config/routes.rb
Rails.application.routes.draw do
post "/webhooks/transyt", to: "webhooks#create"
end
app/jobs/process_webhook_job.rb
class ProcessWebhookJob < ApplicationJob
queue_as :webhooks
retry_on StandardError, wait: :polynomially_longer, attempts: 5
def perform(payload)
event_id = payload["event_id"]
provider = payload["provider"]
event_type = payload["event_type"]
ActiveRecord::Base.transaction do
# Record that we've processed this event
return if ProcessedWebhook.exists?(event_id: event_id)
ProcessedWebhook.create!(event_id: event_id)
# Route to the appropriate handler
case provider
when "stripe"
handle_stripe(event_type, payload["payload"])
when "mailgun"
handle_mailgun(event_type, payload["payload"])
else
Rails.logger.warn("Unhandled provider: #{provider}")
end
end
end
private
def handle_stripe(event_type, data)
case event_type
when "charge.succeeded"
# Process successful charge
when "charge.failed"
# Handle failed charge
when "customer.subscription.deleted"
# Handle subscription cancellation
end
end
def handle_mailgun(event_type, data)
case event_type
when "delivered"
# Mark email as delivered
when "failed"
# Handle delivery failure
end
end
end
db/migrate/xxx_create_processed_webhooks.rb
class CreateProcessedWebhooks < ActiveRecord::Migration[7.0]
def change
create_table :processed_webhooks do |t|
t.string :event_id, null: false, index: { unique: true }
t.timestamps
end
end
end

If you prefer polling instead of push delivery, you can query the integration_events table directly:

app/jobs/webhook_pump_job.rb
class WebhookPumpJob < ApplicationJob
queue_as :default
def perform
# Query unprocessed events
events = IntegrationEvent
.where(status: "received")
.order(received_at: :asc)
.limit(100)
events.each do |event|
# Claim the event atomically
updated = IntegrationEvent
.where(id: event.id, status: "received")
.update_all(status: "enqueued")
next unless updated > 0
ProcessWebhookJob.perform_later(
"event_id" => event.id,
"provider" => event.provider,
"event_type" => event.event_type_raw,
"payload" => event.payload_raw
)
end
end
end

Schedule the pump job to run every 5 seconds using a recurring job framework (e.g., solid_queue, sidekiq-cron).