Skip to Content
Course content

177: Practice Exercise: Building a Simple Webhook Delivery System

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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_timeout and read_timeout both 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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.