When letting agencies manage hundreds of properties across multiple platforms, manual synchronisation becomes impossible. Properties updated in LetAdmin need to flow automatically to Rightmove, accounting systems, tenant portals, and other connected services. Week 37 introduced production-ready webhook infrastructure to solve this challenge.
Building reliable webhooks is harder than it appears. HTTP requests fail, networks disconnect, recipient servers go down temporarily, and retry logic can easily create duplicate deliveries or infinite loops. This article explores how we built webhook infrastructure that handles these edge cases whilst keeping external systems reliably synchronised.
What Your Team Will Notice
From an agency perspective, webhooks are invisible when working correctly—and that's the point. Update a property's rent price or add photos, and within seconds those changes appear on Rightmove, your website, and anywhere else you've configured webhooks. No export buttons, no manual sync tasks, no wondering if external systems are up to date.
The webhooks management interface provides visibility when needed. Staff with appropriate permissions can see which integrations are active, view recent webhook deliveries, and diagnose why a particular update failed to reach an external system. Delivery logs show request and response details, making it straightforward to resolve integration issues without developer intervention.
For agencies using the OAuth API to build custom integrations, webhooks eliminate constant polling. Instead of checking every few minutes whether properties changed, external systems receive instant notifications when relevant events occur, reducing API calls by 95% whilst improving responsiveness.
Under the Bonnet: Enterprise Webhook Patterns
Our webhook implementation follows patterns established by GitHub, Stripe, and Twilio—services that deliver billions of webhooks reliably. The architecture comprises four key components working together.
Webhook Registration and Event Subscriptions
Agencies configure webhooks through the management interface, specifying a destination URL and subscribing to specific event types:
# Webhook model structure (simplified for clarity)
class Webhook
belongs_to :agency
# Core configuration
field :name # "Rightmove Sync"
field :url # "https://api.example.com/webhooks"
field :secret # Generated with cryptographically secure randomness
field :events # ["property.created", "property.updated", "property.deleted"]
field :active # true/false
field :description # Optional notes about purpose
end
The secret generation uses 256-bit entropy, creating unique secrets for each webhook that enable recipient verification without exposing credentials. Event subscriptions allow fine-grained control—an accounting integration might only care about rent changes, whilst Rightmove needs all property updates.
Delivery Tracking and Audit Trail
Every webhook delivery attempt creates a WebhookDelivery record, providing complete audit history:
class WebhookDelivery
belongs_to :webhook
# Delivery tracking
field :status # pending, succeeded, failed
field :http_status # 200, 404, 500, etc.
field :delivered_at # Timestamp of successful delivery
field :next_retry_at # For exponential backoff
field :retry_count # Number of attempts so far
# Debugging information
field :request_body # Truncated to 10KB
field :response_body # Truncated to 10KB
field :error_message # For failed deliveries
end
This model enables several critical capabilities: agencies can view delivery history to verify synchronisation; failed deliveries reschedule automatically with exponential backoff; long-term delivery statistics inform reliability monitoring; and comprehensive logs assist with debugging integration issues.
HMAC Signature Verification
Recipients need assurance that webhook requests genuinely originate from LetAdmin, not malicious actors. We implement HMAC-SHA256 signatures following GitHub's webhook security model:
# High-level signature approach
# Actual implementation includes additional security measures
def generate_signature(payload, secret)
# Creates HMAC-SHA256 hash of payload using webhook secret
# Includes in X-Hub-Signature-256 header with 'sha256=' prefix
# Recipients verify by computing same hash and comparing
end
Recipients validate webhooks by:
- Extracting the signature from request headers
- Computing their own HMAC using the shared secret
- Comparing signatures using constant-time comparison
- Rejecting requests if signatures don't match
This cryptographic verification prevents webhook spoofing attacks without requiring complex authentication flows.
Intelligent Retry Logic
Network failures and temporary service outages are inevitable. Our retry system distinguishes between transient failures worth retrying and permanent errors that won't resolve:
Retry these scenarios:
- 5xx server errors (recipient's service temporarily down)
- 408 Request Timeout (network latency spike)
- 429 Too Many Requests (recipient rate limiting)
- Connection errors (network disruption)
- Timeout errors (recipient overwhelmed)
Don't retry these:
- 401 Unauthorized (webhook misconfigured)
- 404 Not Found (wrong URL)
- 400 Bad Request (payload format issue)
- Other 4xx client errors (permanent problems)
Failed deliveries retry with exponential backoff: first retry after 1 minute, then 5 minutes, then 25 minutes, then 2 hours, then 10 hours. After five failed attempts, deliveries mark as permanently failed and alert appropriate staff.
Webhookable Concern: DRY Integration
Rather than implementing webhook triggering logic separately for properties, tenancies, landlords, and other models, we created a reusable concern:
module Webhookable
extend ActiveSupport::Concern
included do
after_commit :trigger_webhooks_if_changed, on: [:create, :update]
after_commit :trigger_webhooks_on_destroy, on: :destroy
end
def trigger_webhooks_if_changed
# Checks if any webhook-relevant attributes changed
# Determines event type (created vs updated)
# Queues webhook deliveries for subscribed webhooks
end
end
Models opt into webhook behaviour by including this concern and specifying which attributes trigger webhooks:
class Property < ApplicationRecord
include Webhookable
WEBHOOK_ATTRIBUTES = %w[
reference headline description price beds bathrooms
marketing_status availability_date property_type
].freeze
end
When a property's rent price changes, the concern automatically triggers property.updated webhooks. When photos are added, property_photo.created events fire. This declarative approach ensures consistent webhook behaviour across the entire application.
Testing Webhook Reliability
Comprehensive testing verifies webhook behaviour under various scenarios:
RSpec.describe WebhookDeliveryJob do
it "delivers webhooks with correct HMAC signatures" do
# Verifies signature generation and header inclusion
end
it "retries 5xx server errors with exponential backoff" do
# Confirms retry logic for transient failures
end
it "abandons 4xx client errors without retrying" do
# Ensures permanent failures don't retry indefinitely
end
it "truncates large payloads to prevent database bloat" do
# Validates 10KB truncation for audit logs
end
end
Integration tests verify real webhook deliveries using webhook.site, confirming end-to-end functionality including signature verification and payload formatting.
Performance Considerations
Webhook delivery happens asynchronously via background jobs, ensuring property updates remain fast even when delivering to dozens of webhooks. Job priority ensures critical webhooks (like Rightmove updates) process before lower-priority notifications.
The delivery tracking model includes indexes on webhook_id and status for efficient queries when displaying delivery history or finding failed deliveries needing retry. Request and response body truncation at 10KB prevents database bloat whilst retaining sufficient debugging information.
Multi-tenant context preservation ensures webhook deliveries execute with correct agency scope, even when processing hours later during retry attempts. This prevents cross-tenant data leaks in webhook payloads.
What's Next
With webhook infrastructure established, Week 37 built an extensible apps system on top of this foundation. Apps can register webhooks programmatically, subscribe to relevant events, and receive real-time updates—enabling sophisticated integrations like Rightmove's two-way synchronisation.
The webhook system also enables future capabilities like webhook event replay (resending historical events to new integrations), webhook forwarding (proxying events to multiple destinations), and webhook transformation (adapting payload formats for different recipients).
This infrastructure transforms LetAdmin from an isolated system into a platform that participates in the broader property technology ecosystem, automatically keeping all connected services synchronised with minimal latency and maximum reliability.
