ActionWebhook ๐ช
ActionWebhook is a Rails-friendly framework for delivering structured webhooks with the elegance and familiarity of ActionMailer. Built for modern Rails applications, it provides a clean, testable, and reliable way to send webhooks to external services.
โจ Features
- ๐ฏ ActionMailer-inspired API - Familiar patterns for Rails developers
- ๐ ERB Template Support - Dynamic JSON payloads with embedded Ruby
- ๐ Smart Retry Logic - Only retries failed URLs, not successful ones
- โก ActiveJob Integration - Queue webhooks using your existing job infrastructure
- ๐ฃ Flexible Callbacks - Hook into delivery lifecycle events
- ๐ก๏ธ Error Handling - Comprehensive error handling and logging
- ๐งช Test-Friendly - Built-in testing utilities and helpers
- ๐ Multiple Endpoints - Send to multiple URLs simultaneously with selective retry
- ๐ง Highly Configurable - Fine-tune behavior per webhook class
- ๐ Comprehensive Logging - Detailed logging for debugging and monitoring
๐ฆ Installation
Add this line to your application's Gemfile:
gem 'action_webhook'
And then execute:
$ bundle install
๐ Quick Start
1. Generate a Webhook Class
# app/webhooks/user_webhook.rb
class UserWebhook < ActionWebhook::Base
def user_created
@user = params[:user]
@timestamp = Time.current
endpoints = [
{
url: 'https://api.example.com/webhooks',
headers: {
'Authorization' => 'Bearer your-token',
'Content-Type' => 'application/json'
}
}
]
deliver(endpoints)
end
end
2. Create a Payload Template
<!-- app/webhooks/user_webhook/user_created.json.erb -->
{
"event": "user.created",
"timestamp": "<%= @timestamp.iso8601 %>",
"data": {
"user": {
"id": <%= @user.id %>,
"email": "<%= @user.email %>",
"name": "<%= @user.name %>",
"created_at": "<%= @user.created_at.iso8601 %>"
}
}
}
3. Trigger the Webhook
# Immediate delivery
UserWebhook.user_created(user: @user).deliver_now
# Background delivery (recommended)
UserWebhook.user_created(user: @user).deliver_later
# With custom queue
UserWebhook.user_created(user: @user).deliver_later(queue: 'webhooks')
๐ง Advanced Configuration
Retry Configuration
ActionWebhook intelligently retries only the URLs that fail, not all URLs in a batch:
class PaymentWebhook < ActionWebhook::Base
# Configure retry behavior
self.max_retries = 5
self.retry_delay = 30.seconds
self.retry_backoff = :exponential # :exponential, :linear, or :fixed
self.retry_jitter = 5.seconds # Adds randomness to prevent thundering herd
def payment_completed
@payment = params[:payment]
endpoints = [
{ url: 'https://accounting.example.com/webhooks' },
{ url: 'https://analytics.example.com/webhooks' },
{ url: 'https://notifications.example.com/webhooks' }
]
deliver(endpoints)
# If only analytics.example.com fails, only that URL will be retried
end
end
Smart Callbacks
Get notified about successful deliveries immediately and permanent failures after retries are exhausted:
class OrderWebhook < ActionWebhook::Base
# Called immediately when any URLs succeed
after_deliver :handle_successful_deliveries
# Called when retries are exhausted for failed URLs
after_retries_exhausted :handle_permanent_failures
def order_created
@order = params[:order]
deliver(webhook_endpoints)
end
private
def handle_successful_deliveries(successful_responses)
successful_responses.each do |response|
Rails.logger.info "Webhook delivered to #{response[:url]} (#{response[:status]})"
end
end
def handle_permanent_failures(failed_responses)
failed_responses.each do |response|
Rails.logger.error "Webhook permanently failed for #{response[:url]} after #{response[:attempt]} attempts"
AdminMailer.webhook_failure(@order.id, response).deliver_later
end
end
end
Multiple Endpoints with Selective Retry
Send to multiple endpoints efficiently with intelligent retry logic:
class NotificationWebhook < ActionWebhook::Base
def user_registered
@user = params[:user]
endpoints = [
{
url: 'https://analytics.example.com/events',
headers: { 'X-API-Key' => Rails.application.credentials.analytics_key }
},
{
url: 'https://crm.example.com/webhooks',
headers: { 'Authorization' => "Bearer #{Rails.application.credentials.crm_token}" }
},
{
url: 'https://marketing.example.com/api/events',
headers: { 'X-Service-Token' => Rails.application.credentials.marketing_token }
}
]
deliver(endpoints)
# If marketing.example.com fails but others succeed,
# only marketing.example.com will be retried
end
end
Real-world Example: E-commerce Order Processing
class OrderWebhook < ActionWebhook::Base
self.max_retries = 3
self.retry_delay = 30.seconds
self.retry_backoff = :exponential
after_deliver :log_successful_integrations
after_retries_exhausted :handle_integration_failures
def order_placed
@order = params[:order]
endpoints = [
{ url: 'https://inventory.company.com/webhooks' }, # Update inventory
{ url: 'https://shipping.company.com/api/orders' }, # Create shipping label
{ url: 'https://analytics.company.com/events' }, # Track conversion
{ url: 'https://email.company.com/order-confirmation' }, # Send confirmation
{ url: 'https://accounting.company.com/webhooks' } # Update books
]
deliver(endpoints)
end
private
def log_successful_integrations(responses)
responses.each do |response|
OrderIntegration.create!(
order: @order,
service_url: response[:url],
status: 'success',
http_status: response[:status],
attempt: response[:attempt]
)
end
end
def handle_integration_failures(responses)
responses.each do |response|
OrderIntegration.create!(
order: @order,
service_url: response[:url],
status: 'failed',
error_message: response[:error],
final_attempt: response[:attempt]
)
# Alert based on criticality
case response[:url]
when /inventory|accounting/
CriticalAlert.webhook_failure(@order, response)
else
StandardAlert.webhook_failure(@order, response)
end
end
end
end
๐งช Testing
ActionWebhook provides testing utilities to make webhook testing straightforward:
# In your test files
require 'action_webhook/test_helper'
class UserWebhookTest < ActiveSupport::TestCase
include ActionWebhook::TestHelper
test "user_created webhook sends correct payload" do
user = users(:john)
# Test immediate delivery
webhook = UserWebhook.user_created(user: user)
assert_enqueued_webhook_deliveries 1 do
webhook.deliver_later
end
# Test payload content
perform_enqueued_webhook_deliveries do
webhook.deliver_later
end
# Verify webhook was sent
assert_webhook_delivered UserWebhook, :user_created
end
end
๐ Documentation
Comprehensive documentation is available in the docs directory:
- Installation Guide - Detailed setup instructions
- Quick Start Tutorial - Step-by-step getting started
- Configuration Reference - All configuration options
- Basic Usage - Core concepts and patterns
- Template System - Working with ERB templates
- Queue Management - ActiveJob integration
- Retry Logic - Error handling and retries
- Callbacks - Lifecycle hooks and callbacks
- Error Handling - Comprehensive error strategies
- Testing Guide - Testing webhooks effectively
- API Reference - Complete API documentation
- Examples - Real-world usage examples
๐ค Contributing
We love contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
๐ Requirements
- Ruby >= 3.1.0
- ActiveJob >= 6.0 (included in Rails 6.0+)
๐ License
This gem is available as open source under the terms of the MIT License.
๐โโ๏ธ Support
- Documentation: docs
- Issues: GitHub Issues
- Discussions: GitHub Discussions
๐ฏ Roadmap
- GraphQL webhook support
- Webhook signature verification
- Built-in webhook endpoint discovery
- Metrics and monitoring integration
- Advanced filtering and conditional delivery
๐ Documentation Notes
This documentation was generated with assistance from GitHub Copilot to ensure comprehensive coverage and clarity. If you find any errors, inconsistencies, or areas that need improvement, please open an issue so we can make corrections and keep the documentation accurate and helpful for everyone.
ActionWebhook - Making webhook delivery as elegant as sending emails in Rails. ๐