-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
177: Practice Exercise: Building a Simple Webhook Delivery System
Wait, what's the cleanest way to actually push the data to the external URL?
When you're building a delivery system, you're essentially acting as the client. While there are plenty of gems like Faraday or HTTParty, I always suggest starting with Net::HTTP from the standard library so you actually understand what's happening under the hood. You'll want to send a POST request with a JSON payload.
Here is a stripped-down version of how I'd handle a "Subscription Renewed" event. Notice how we set the Content-Type; if you forget this, many receiving servers will either reject the request or fail to parse your body as JSON.
require 'net/http'
require 'uri'
require 'json'
def deliver_webhook(target_url, payload)
uri = URI.parse(target_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
request = Net::HTTP::Post.new(uri.path.empty? ? '/' : uri.path, {'Content-Type' => 'application/json'})
request.body = payload.to_json
response = http.request(request)
puts "Delivery status: #{response.code}"
end
# Example usage
event_data = { event: 'subscription.renewed', customer_id: 456, amount: 29.99 }
deliver_webhook('https://client-api.com/webhooks', event_data)
I'm worried about my app hanging if the other server is slow. How do I stop that?
This is where most junior devs get tripped up. If the server you're sending the webhook to is lagging or completely dead, your Ruby process will just sit there waiting for a response. In a real production environment, you'd never do this in the main request thread—you'd push it to a background worker like Sidekiq. But even then, you need strict timeouts.
I always set an open_timeout (how long to wait to establish the connection) and a read_timeout (how long to wait for the data). Trust me, 30 seconds is way too long for a webhook; 5 seconds is usually more than enough.
http = Net::HTTP.new(uri.host, uri.port)
http.open_timeout = 5
http.read_timeout = 5
begin
response = http.request(request)
rescue Net::OpenTimeout, Net::ReadTimeout
puts "The receiver was too slow. We should probably queue this for a retry."
rescue StandardError => e
puts "Something else went wrong: #{e.message}"
end
How do I prevent someone from just spoofing a request to the receiver?
If I know your client's webhook URL, I could send them a fake "payment_received" event and trick their system into giving me a product for free. To stop this, we use a shared secret and an HMAC (Hash-based Message Authentication Code) signature.
Basically, you and the client agree on a secret string. You hash the payload using that secret and send the hash in the header. The client does the same thing on their end; if the hashes match, they know the message actually came from you and wasn't tampered with.
require 'openssl'
def sign_payload(payload_json, secret)
OpenSSL::HMAC.hexdigest('sha256', secret, payload_json)
end
secret = 'super_secret_token_123'
payload_json = event_data.to_json
signature = sign_payload(payload_json, secret)
# Now add it to your request headers
request['X-Webhook-Signature'] = signature
📋 Practical Task
Exercise: Building a Secure Event Dispatcher
Your task is to build a class called WebhookDispatcher that integrates everything we just discussed. The dispatcher should be able to handle a list of multiple destination URLs and a shared secret.
- Create a method
dispatch(event_name, data)that takes an event name (e.g., "user.created") and a hash of data. - The dispatcher should iterate through an array of
destinations(URLs). - For each destination, it must:
- Convert the payload to JSON.
- Generate an HMAC-SHA256 signature using a shared secret.
- Send the POST request with
open_timeoutandread_timeoutboth set to 5 seconds. - Capture the response code and return a summary report (e.g.,
{ "https://api.com": "200", "https://test.com": "Timeout" }).
Test your dispatcher by mocking a failure (using a non-existent URL) to ensure your timeout logic catches the error instead of crashing the program.
There are no comments for now.