Building a Webhooks Backend

Once you have a UI for your users to create webhooks, and a well-architected event schema, you need a system to reliably deliver webhook events. A simple webhook is just a POST request that is triggered by an event, so it might look something like this:

class User
  after_create do |user|
    Webhook.each do |webhook|
      HTTPClient.post(webhook.url, user.to_json, {
        "X-MyApp-Event": "user.created"
      })
    end
  end
end

Of course, the above code has a number of issues:

  • The webhooks are delivered synchronously (it will take a long time)
  • There is no error handling—what happens if there is a DNS issue or timeout?
  • There is no retry logic—what happens when a webhook URL is temporarily down?

A better approach would be to use a background scheduling system or job queue to send each webhook:

class User
  after_create do |user|
    Webhook.each do |webhook|
      WebhookJob.perform_later(webhook, "user.created", user)
    end
  end
end

class WebhookJob
  # Retry exceptions up to 100 times with exponential backoff
  retry: 100

  def perform(webhook, event_name, event_object)
    HTTPClient.post(webhook.url, event_object.to_json, {
      "X-MyApp-Event": event_name
    })
  end
end

Here are some queue systems that are a good fit for this architecture:

Since you're connecting to many different 3rd party servers (even malicious ones), you may need some extra handling for things like slow servers, timeouts, and security issues like SSRF.

Of course, this is where Hook Relay can save you time—we'll handle the backend for you and give you visiblity into your webhook deliveries. Sign up here »

© 2020-2021 Honeybadger Industries, LLC