philiprehberger-webhook_builder
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
)
# 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
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 |
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 |
Development
bundle install
bundle exec rspec
bundle exec rubocop
Support
If you find this project useful: