Webhook Simulation
FakeDataDSL's Webhook Simulator lets you send realistic webhook payloads to your application, simulating events from external services like Stripe, GitHub, Twilio, and more.
Quick Start
# Configure the simulator
FakeDataDSL::WebhookSimulator.configure do |config|
config.endpoint = "http://localhost:3000/webhooks"
config.secret = "whsec_your_webhook_secret"
end
# Send a webhook
FakeDataDSL::WebhookSimulator.send(
"stripe.payment_intent.succeeded",
schema: "StripePaymentIntent"
)
Basic Usage
Simple Webhook
# Send webhook with generated data
FakeDataDSL::WebhookSimulator.send(
"user.created",
schema: "User"
)
# With custom payload overrides
FakeDataDSL::WebhookSimulator.send(
"order.completed",
schema: "Order",
data: { status: "completed", total: 99.99 }
)
With Delay
# Simulate network latency
FakeDataDSL::WebhookSimulator.send(
"payment.failed",
schema: "PaymentEvent",
delay: 2.seconds
)
Batch Webhooks
# Send multiple webhooks in sequence
FakeDataDSL::WebhookSimulator.send_batch([
{ event: "order.created", schema: "Order" },
{ event: "payment.processing", schema: "Payment", delay: 1.second },
{ event: "payment.succeeded", schema: "Payment", delay: 2.seconds },
{ event: "order.completed", schema: "Order", delay: 3.seconds }
])
Configuration
Global Configuration
FakeDataDSL::WebhookSimulator.configure do |config|
# Target endpoint
config.endpoint = "http://localhost:3000/webhooks"
# Webhook secret for signatures
config.secret = ENV["WEBHOOK_SECRET"]
# Default headers
config.headers = {
"Content-Type" => "application/json",
"User-Agent" => "FakeDataDSL-WebhookSimulator/1.0"
}
# Timeout settings
config.timeout = 30
config.open_timeout = 5
# Retry settings
config.max_retries = 3
config.retry_delay = 1.second
# Logging
config.logger = Rails.logger
config.log_level = :info
end
Per-Request Configuration
FakeDataDSL::WebhookSimulator.send(
"event.name",
schema: "Schema",
config: {
endpoint: "http://different-endpoint.com/hooks",
secret: "different_secret",
timeout: 60
}
)
Webhook Signatures
Automatic Signing
Webhooks are automatically signed based on the provider type:
# Stripe-style signature (HMAC-SHA256)
FakeDataDSL::WebhookSimulator.send(
"payment_intent.succeeded",
schema: "StripePayment",
signature_type: :stripe
)
# Adds: Stripe-Signature: t=1234567890,v1=abc123...
# GitHub-style signature
FakeDataDSL::WebhookSimulator.send(
"push",
schema: "GitHubPush",
signature_type: :github
)
# Adds: X-Hub-Signature-256: sha256=abc123...
# Custom signature
FakeDataDSL::WebhookSimulator.send(
"custom.event",
schema: "Custom",
signature: ->(payload, secret) {
OpenSSL::HMAC.hexdigest("SHA512", secret, payload)
},
signature_header: "X-Custom-Signature"
)
Supported Signature Types
| Provider | Type | Header |
|---|---|---|
| Stripe | :stripe |
Stripe-Signature |
| GitHub | :github |
X-Hub-Signature-256 |
| Twilio | :twilio |
X-Twilio-Signature |
| Shopify | :shopify |
X-Shopify-Hmac-SHA256 |
| Custom | :custom |
Configurable |
Provider Presets
Stripe
FakeDataDSL::WebhookSimulator.stripe(
"payment_intent.succeeded",
data: {
amount: 2000,
currency: "usd",
customer: "cus_123"
}
)
# Uses built-in Stripe event schemas
# Automatically formats as Stripe webhook structure:
# {
# "id": "evt_...",
# "object": "event",
# "type": "payment_intent.succeeded",
# "data": { "object": { ... } }
# }
GitHub
FakeDataDSL::WebhookSimulator.github(
"push",
repository: "owner/repo",
data: {
ref: "refs/heads/main",
commits: [{ message: "Update README" }]
}
)
Twilio
FakeDataDSL::WebhookSimulator.twilio(
"message.received",
data: {
from: "+15551234567",
body: "Hello, world!"
}
)
Custom Provider
FakeDataDSL::WebhookSimulator.register_provider(:my_service) do |config|
config.base_url = "https://my-service.com"
config.signature_type = :hmac_sha256
config.signature_header = "X-MyService-Signature"
config.event_wrapper = ->(event, data) {
{
event_type: event,
timestamp: Time.current.iso8601,
payload: data
}
}
end
# Use registered provider
FakeDataDSL::WebhookSimulator.my_service(
"user.updated",
schema: "User"
)
Event Sequences
Define Sequences
# Define a sequence of related events
FakeDataDSL::WebhookSimulator.define_sequence(:checkout_flow) do
# Step 1: Cart created
step "cart.created", schema: "Cart"
# Step 2: Payment initiated (after 1 second)
step "payment.initiated", schema: "Payment", delay: 1.second
# Step 3: Payment succeeded (after 2 more seconds)
step "payment.succeeded", schema: "Payment", delay: 2.seconds
# Step 4: Order created
step "order.created", schema: "Order", delay: 500.milliseconds
# Step 5: Email sent
step "notification.sent", schema: "Notification", delay: 1.second
end
Run Sequences
# Run the sequence
FakeDataDSL::WebhookSimulator.run_sequence(:checkout_flow)
# Run with shared context (data passed between steps)
FakeDataDSL::WebhookSimulator.run_sequence(:checkout_flow,
context: { user_id: "user_123", amount: 99.99 }
)
# Run with callbacks
FakeDataDSL::WebhookSimulator.run_sequence(:checkout_flow) do |step, response|
puts "Step #{step.name}: #{response.status}"
end
Conditional Steps
FakeDataDSL::WebhookSimulator.define_sequence(:payment_flow) do
step "payment.initiated", schema: "Payment"
# Branch based on previous response
branch do |context|
if context[:payment_method] == "card"
step "payment.card.processing", schema: "CardPayment"
else
step "payment.bank.processing", schema: "BankPayment"
end
end
step "payment.completed", schema: "Payment"
end
Testing Integration
RSpec Helpers
# spec/rails_helper.rb
require 'fake_data_dsl/webhook_simulator/rspec'
RSpec.configure do |config|
config.include FakeDataDSL::WebhookSimulator::RSpecHelpers
end
# spec/webhooks/stripe_spec.rb
RSpec.describe "Stripe Webhooks", type: :request do
it "handles payment_intent.succeeded" do
simulate_webhook "stripe.payment_intent.succeeded",
schema: "StripePayment",
data: { amount: 5000 }
expect(response).to have_http_status(:ok)
expect(Payment.last.amount).to eq(50.00)
end
it "handles the full checkout sequence" do
simulate_webhook_sequence :checkout_flow
expect(Order.count).to eq(1)
expect(Order.last.status).to eq("completed")
end
end
Minitest Helpers
# test/test_helper.rb
require 'fake_data_dsl/webhook_simulator/minitest'
class ActiveSupport::TestCase
include FakeDataDSL::WebhookSimulator::MinitestHelpers
end
# test/integration/webhooks_test.rb
class WebhooksTest < ActionDispatch::IntegrationTest
test "handles stripe webhook" do
simulate_webhook "stripe.payment_intent.succeeded",
schema: "StripePayment"
assert_response :success
end
end
Error Simulation
Simulate Failures
# Simulate webhook delivery failure
FakeDataDSL::WebhookSimulator.send(
"event.name",
schema: "Schema",
simulate_failure: true # Returns 500 error simulation
)
# Simulate timeout
FakeDataDSL::WebhookSimulator.send(
"event.name",
schema: "Schema",
simulate_timeout: true
)
# Simulate partial failure (intermittent)
FakeDataDSL::WebhookSimulator.send(
"event.name",
schema: "Schema",
failure_rate: 0.3 # 30% chance of failure
)
Retry Testing
# Test retry behavior
FakeDataDSL::WebhookSimulator.send(
"event.name",
schema: "Schema",
fail_first_n: 2 # Fail first 2 attempts, succeed on 3rd
)
Recording and Replay
Record Webhooks
# Record all webhooks sent
FakeDataDSL::WebhookSimulator.start_recording
# ... send webhooks ...
# Stop and save
recordings = FakeDataDSL::WebhookSimulator.stop_recording
recordings.save_to("spec/fixtures/webhooks/checkout_flow.json")
Replay Recordings
# Replay recorded webhooks
FakeDataDSL::WebhookSimulator.replay("spec/fixtures/webhooks/checkout_flow.json")
# With timing
FakeDataDSL::WebhookSimulator.replay("recordings.json",
preserve_timing: true # Replay with original delays
)
CLI Commands
# Send single webhook
fake_data_dsl webhook send user.created \
--schema User \
--endpoint http://localhost:3000/webhooks \
--secret whsec_xxx
# Send with data override
fake_data_dsl webhook send payment.succeeded \
--schema Payment \
--data '{"amount": 9999}' \
--endpoint http://localhost:3000/webhooks
# Run sequence
fake_data_dsl webhook sequence checkout_flow \
--endpoint http://localhost:3000/webhooks
# Record mode
fake_data_dsl webhook record \
--output recordings/session.json \
--duration 60 # Record for 60 seconds
# Replay recordings
fake_data_dsl webhook replay recordings/session.json \
--endpoint http://localhost:3000/webhooks
API Reference
WebhookSimulator.send
FakeDataDSL::WebhookSimulator.send(event_name, = {})
Parameters:
event_name- Event type string (e.g., "user.created")options[:schema]- Schema name for payload generationoptions[:data]- Override dataoptions[:delay]- Delay before sendingoptions[:endpoint]- Override endpointoptions[:secret]- Override secretoptions[:signature_type]- Signature typeoptions[:headers]- Additional headersoptions[:config]- Full config override
Returns: WebhookResponse object
WebhookSimulator.send_batch
FakeDataDSL::WebhookSimulator.send_batch(webhooks)
Parameters:
webhooks- Array of webhook configurations
Returns: Array of WebhookResponse objects
WebhookSimulator.define_sequence
FakeDataDSL::WebhookSimulator.define_sequence(name, &block)
Parameters:
name- Sequence nameblock- Sequence definition block
WebhookSimulator.run_sequence
FakeDataDSL::WebhookSimulator.run_sequence(name, = {}, &callback)
Parameters:
name- Sequence nameoptions[:context]- Shared context datacallback- Optional callback for each step
Returns: Array of WebhookResponse objects
Best Practices
1. Use Consistent Secrets
# config/environments/test.rb
ENV["WEBHOOK_SECRET"] = "test_webhook_secret"
# spec/rails_helper.rb
FakeDataDSL::WebhookSimulator.configure do |config|
config.secret = ENV["WEBHOOK_SECRET"]
end
2. Test Error Handling
it "handles webhook failures gracefully" do
simulate_webhook "payment.failed",
schema: "FailedPayment",
data: { error_code: "card_declined" }
expect(response).to have_http_status(:ok)
expect(Payment.last.status).to eq("failed")
expect(User.last).to have_received_email(:payment_failed)
end
3. Use Sequences for Complex Flows
# Better than individual webhook tests
simulate_webhook_sequence :full_order_lifecycle
# Instead of:
simulate_webhook "order.created"
simulate_webhook "payment.succeeded"
simulate_webhook "order.shipped"
simulate_webhook "order.delivered"
4. Record Production Webhooks for Testing
# In development, record real webhooks
# Then replay in tests for realistic scenarios
Troubleshooting
Signature Verification Failing
# Make sure secrets match
FakeDataDSL::WebhookSimulator.configure do |config|
config.secret = ENV["WEBHOOK_SECRET"] # Same as your app uses
end
# Debug signature
response = FakeDataDSL::WebhookSimulator.send(...)
puts response.request_headers["Stripe-Signature"]
Timeout Issues
# Increase timeout
FakeDataDSL::WebhookSimulator.configure do |config|
config.timeout = 60
config.open_timeout = 10
end
Endpoint Not Reachable
# Make sure your server is running
# In tests, use request specs not system specs
RSpec.describe "Webhooks", type: :request do
# This runs against the test server
end