Module: Synthra::WebhookSimulator

Defined in:
lib/synthra/webhook_simulator.rb

Overview

Webhook Simulation

Simulate webhook deliveries for testing integrations with payment providers, notification services, and third-party APIs.

Examples:

Configure and send webhook

Synthra::WebhookSimulator.configure do |config|
  config.endpoint = "http://localhost:3000/webhooks/stripe"
  config.secret = "whsec_test123"
end

Synthra::WebhookSimulator.send(
  "payment_intent.succeeded",
  data: { amount: 2000, currency: "usd" }
)

Defined Under Namespace

Modules: GitHub, Stripe Classes: Configuration, ConfigurationError, WebhookResult

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.configurationObject

Returns the value of attribute configuration.



26
27
28
# File 'lib/synthra/webhook_simulator.rb', line 26

def configuration
  @configuration
end

Class Method Details

.build_payload(event_type, data, config) ⇒ Object (private)



161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/synthra/webhook_simulator.rb', line 161

def build_payload(event_type, data, config)
  {
    id: "evt_#{SecureRandom.hex(12)}",
    type: event_type,
    created: Time.now.to_i,
    data: {
      object: data || {}
    },
    livemode: false,
    api_version: config.api_version
  }
end

.compute_signature(timestamp, payload, secret) ⇒ Object (private)



216
217
218
219
# File 'lib/synthra/webhook_simulator.rb', line 216

def compute_signature(timestamp, payload, secret)
  signed_payload = "#{timestamp}.#{payload}"
  OpenSSL::HMAC.hexdigest("SHA256", secret, signed_payload)
end

.configure {|configuration| ... } ⇒ Object

Yields:



28
29
30
31
32
# File 'lib/synthra/webhook_simulator.rb', line 28

def configure
  self.configuration ||= Configuration.new
  yield(configuration) if block_given?
  configuration
end

.deliver(endpoint, payload, secret, extra_headers) ⇒ Object (private)



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/synthra/webhook_simulator.rb', line 174

def deliver(endpoint, payload, secret, extra_headers)
  uri = URI.parse(endpoint)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == "https"
  http.open_timeout = 10
  http.read_timeout = 30

  body = JSON.generate(payload)

  request = Net::HTTP::Post.new(uri.request_uri)
  request["Content-Type"] = "application/json"
  request["User-Agent"] = "Synthra-Webhook/1.0"

  # Add signature if secret provided
  if secret
    timestamp = Time.now.to_i
    signature = compute_signature(timestamp, body, secret)
    request["Stripe-Signature"] = "t=#{timestamp},v1=#{signature}"
  end

  # Add extra headers
  extra_headers&.each { |k, v| request[k] = v }

  request.body = body

  response = http.request(request)

  WebhookResult.new(
    success: response.code.to_i < 400,
    status: response.code.to_i,
    body: response.body,
    payload: payload
  )
rescue StandardError => e
  WebhookResult.new(
    success: false,
    status: 0,
    error: e.message,
    payload: payload
  )
end

.from_template(template_name, **overrides) ⇒ WebhookResult

Send webhook using template

Parameters:

  • template_name (Symbol)

    registered template name

  • overrides (Hash)

    data overrides

Returns:

Raises:

  • (ArgumentError)


143
144
145
146
147
148
149
150
151
152
# File 'lib/synthra/webhook_simulator.rb', line 143

def from_template(template_name, **overrides)
  template = templates[template_name]
  raise ArgumentError, "Unknown template: #{template_name}" unless template

  send(
    template[:event_type],
    schema: template[:schema],
    **template[:options].merge(overrides)
  )
end

.register_template(name, event_type:, schema: nil, **options) ⇒ Object

Register a webhook template

Parameters:

  • name (Symbol)

    template name

  • event_type (String)

    event type

  • schema (String) (defaults to: nil)

    schema for data

  • options (Hash)

    default options



129
130
131
132
133
134
135
# File 'lib/synthra/webhook_simulator.rb', line 129

def register_template(name, event_type:, schema: nil, **options)
  templates[name] = {
    event_type: event_type,
    schema: schema,
    options: options
  }
end

.send(event_type, data: nil, **options) ⇒ WebhookResult

Send a webhook

Parameters:

  • event_type (String)

    event type (e.g., "payment_intent.succeeded")

  • data (Hash) (defaults to: nil)

    event data

  • options (Hash)

    delivery options

Options Hash (**options):

  • :endpoint (String)

    override default endpoint

  • :secret (String)

    override default secret

  • :delay (Integer)

    delay in seconds before sending

  • :schema (String)

    schema to generate data from

  • :headers (Hash)

    additional headers

Returns:

Raises:



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/synthra/webhook_simulator.rb', line 46

def send(event_type, data: nil, **options)
  config = configuration || Configuration.new
  endpoint = options[:endpoint] || config.endpoint
  secret = options[:secret] || config.secret

  raise ConfigurationError, "Endpoint not configured" unless endpoint

  # Generate data from schema if specified
  if options[:schema] && data.nil?
    schema = Synthra.registry.schema(options[:schema].to_s)
    data = schema.generate
  end

  # Build webhook payload
  payload = build_payload(event_type, data, config)

  # Handle delay
  if options[:delay]
    Thread.new do
      sleep(options[:delay])
      deliver(endpoint, payload, secret, options[:headers])
    end
    return WebhookResult.new(
      success: true,
      status: :scheduled,
      message: "Webhook scheduled for delivery in #{options[:delay]}s"
    )
  end

  # Deliver immediately
  deliver(endpoint, payload, secret, options[:headers])
end

.send_async(event_type, **options) ⇒ Thread

Send webhook asynchronously

Parameters:

  • event_type (String)

    event type

  • options (Hash)

    options

Returns:

  • (Thread)

    delivery thread



85
86
87
# File 'lib/synthra/webhook_simulator.rb', line 85

def send_async(event_type, **options)
  Thread.new { send(event_type, **options) }
end

.sequence(events) {|result| ... } ⇒ Array<WebhookResult>

Simulate a sequence of webhooks

Examples:

Simulate payment flow

WebhookSimulator.sequence([
  { type: "payment_intent.created", delay: 0 },
  { type: "payment_intent.processing", delay: 1 },
  { type: "payment_intent.succeeded", delay: 2 }
])

Parameters:

  • events (Array<Hash>)

    list of events with delays

Yields:

  • (result)

    for each delivered webhook

Returns:



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/synthra/webhook_simulator.rb', line 102

def sequence(events, &block)
  results = []

  events.each do |event|
    sleep(event[:delay] || 0) if event[:delay]

    result = send(
      event[:type],
      data: event[:data],
      schema: event[:schema],
      endpoint: event[:endpoint]
    )

    results << result
    yield result if block_given?
  end

  results
end

.templatesObject

Templates registry



155
156
157
# File 'lib/synthra/webhook_simulator.rb', line 155

def templates
  @templates ||= {}
end