Low commit activity in last 3 years
A webhook delivery client that signs payloads with HMAC-SHA256, retries failed deliveries with configurable backoff strategies, supports batch delivery, custom headers, and tracks delivery status including response codes, attempts, and duration.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-webhook_builder

Tests Gem Version Last updated

Webhook delivery client with HMAC signing, retry, and tracking

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-webhook_builder"

Or install directly:

gem install philiprehberger-webhook_builder

Usage

require "philiprehberger/webhook_builder"

client = Philiprehberger::WebhookBuilder.new(
  url: "https://example.com/webhooks",
  secret: "your-signing-secret"
)

delivery = client.deliver(event: "order.created", payload: { id: 123, total: 49.99 })
delivery.success?      # => true
delivery.response_code # => 200

Batch Delivery

require "philiprehberger/webhook_builder"

client = Philiprehberger::WebhookBuilder.new(
  url: "https://example.com/webhooks",
  secret: "your-signing-secret",
  concurrency: 8
)

events = [
  { event: "order.created", payload: { id: 1 } },
  { event: "order.updated", payload: { id: 2 } },
  { event: "order.deleted", payload: { id: 3 } }
]

results = client.deliver_batch(events)
results.each { |d| puts "#{d.response_code}: #{d.success?}" }

Backoff Strategies

require "philiprehberger/webhook_builder"

# Exponential backoff (default): 1s, 2s, 4s, 8s, ...
client = Philiprehberger::WebhookBuilder.new(
  url: "https://example.com/webhooks",
  secret: "secret",
  backoff: :exponential
)

# Linear backoff: 1s, 2s, 3s, 4s, ...
client = Philiprehberger::WebhookBuilder.new(
  url: "https://example.com/webhooks",
  secret: "secret",
  backoff: :linear
)

# Fixed backoff: 1s, 1s, 1s, ...
client = Philiprehberger::WebhookBuilder.new(
  url: "https://example.com/webhooks",
  secret: "secret",
  backoff: :fixed
)

# Decorrelated jitter (AWS-style): randomized within [base, min(cap, prev*3)]
client = Philiprehberger::WebhookBuilder.new(
  url: "https://example.com/webhooks",
  secret: "secret",
  backoff: :decorrelated
)

# Custom Proc backoff
client = Philiprehberger::WebhookBuilder.new(
  url: "https://example.com/webhooks",
  secret: "secret",
  backoff: ->(attempt) { attempt * 0.5 }
)

Header Customization

require "philiprehberger/webhook_builder"

# Default headers on all deliveries
client = Philiprehberger::WebhookBuilder.new(
  url: "https://example.com/webhooks",
  secret: "secret",
  default_headers: { "X-Tenant" => "acme" }
)

# Per-delivery headers (override defaults)
client.deliver(
  event: "order.created",
  payload: { id: 1 },
  headers: { "X-Priority" => "high" }
)

Verifying signatures

On the receiving side, use the same secret to verify the signature sent in the X-Webhook-Signature header. The comparison is constant-time and will never raise on malformed input.

require "philiprehberger/webhook_builder"

secret = "shared-signing-secret"
sender = Philiprehberger::WebhookBuilder.new(url: "https://example.com/webhooks", secret: secret)
receiver = Philiprehberger::WebhookBuilder.new(url: "https://example.com/webhooks", secret: secret)

body = '{"event":"order.created","payload":{"id":1}}'
signature = OpenSSL::HMAC.hexdigest("SHA256", secret, body)

receiver.verify_signature(body: body, signature: signature) # => true
receiver.verify_signature(body: body, signature: "tampered") # => false

You can also compute the signature the client would send for a body without performing a delivery — useful for preparing payloads offline or mirroring verify_signature:

require "philiprehberger/webhook_builder"

client = Philiprehberger::WebhookBuilder.new(
  url: "https://example.com/webhooks",
  secret: "shared-signing-secret"
)

body = '{"event":"order.created","payload":{"id":1}}'
signature = client.signature_for(body: body)

client.verify_signature(body: body, signature: signature) # => true

Delivery Tracking

require "philiprehberger/webhook_builder"

client = Philiprehberger::WebhookBuilder.new(
  url: "https://example.com/webhooks",
  secret: "secret"
)

delivery = client.deliver(event: "user.updated", payload: { id: 42 })

delivery.success?       # => true/false
delivery.response_code  # => 200
delivery.attempts       # => 1
delivery.duration       # => 0.342 (seconds)
delivery.response_body  # => '{"ok":true}'
delivery.error          # => nil or error message

API

Client

Method Description
.new(url:, secret:, timeout:, max_retries:, backoff:, concurrency:, default_headers:) Create a webhook client
#deliver(event:, payload:, headers:) Deliver a webhook event and return a Delivery
#deliver_batch(events) Deliver multiple events concurrently and return an array of Delivery results
#verify_signature(body:, signature:) Constant-time HMAC-SHA256 verification of an incoming signature; returns true/false and never raises
#signature_for(body:) Compute the HMAC-SHA256 signature for a body without sending

Delivery

Method Description
#success? Whether the delivery succeeded (2xx response)
#response_code The HTTP response code
#attempts Number of delivery attempts made
#duration Total duration in seconds across all attempts
#response_body The response body string
#error Error message if delivery failed

Backoff::Exponential

Method Description
.new(base:, max_delay:, jitter:) Create exponential strategy (defaults: base=1, max_delay=30, jitter=false)
#call(attempt) Calculate delay for given attempt

Backoff::Linear

Method Description
.new(base:, max_delay:) Create linear strategy (defaults: base=1, max_delay=30)
#call(attempt) Calculate delay for given attempt

Backoff::Fixed

Method Description
.new(delay:) Create fixed strategy (default: delay=1)
#call(attempt) Returns constant delay

Backoff::Decorrelated

Method Description
.new(base:, max_delay:) Create decorrelated jitter strategy (defaults: base=1, max_delay=30)
#call(attempt) Returns randomized delay in [base, min(max_delay, prev * 3)]

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT