Implement the Fan-In Pattern for Webhooks
The "fan-in" pattern is a powerful architectural approach for managing webhooks. It involves receiving webhooks from multiple, distinct third-party services and routing them all to a single, unified endpoint in your application. This simplifies your backend by creating one entry point for all incoming webhooks, regardless of their origin.
The Core Challenge: Data Normalization
A key challenge when implementing this pattern is that every service sends webhooks with its own unique data structure, or "payload." For example, an inbound SMS event from Twilio has a different JSON structure than one from Vonage.
This is where data normalization becomes critical. Normalization is the process of transforming these varied data structures into a single, consistent format that your application can easily understand and process.
Hookdeck's Transformation feature provides a solution to this challenge, allowing you to decouple your application's logic from the specific formats of third-party webhooks.
Prerequisites
- A Hookdeck account.
- Access to the third-party platforms you want to receive webhooks from.
- A backend application endpoint ready to receive a standardized JSON payload.
Core Architecture with Hookdeck for Fan-In
The fan-in architecture in Hookdeck is achieved by creating multiple Connections . Each Connection is composed of:
- A Source : When creating a connection, you also create a source, which is the entrypoint for a specific third-party service and provides a unique URL for that service to send webhooks to.
- A Destination : This represents the single, unified backend endpoint in your application that will receive all the normalized webhooks. For a fan-in pattern, all your connections will point to the same destination.
- A Transformation rule: This is a script that sits within the connection and normalizes the payload from the source before it's sent to the destination.
You will create a separate Connection for each third-party service you want to integrate.
Step 1: Define Your Common Internal Event Format
Before you can normalize data, you must define the target format. This "common internal event format" is the standardized structure your application will work with.
Key Fields to Consider:
source_platform
: An identifier for the origin (e.g., "twilio", "vonage").event_type_normalized
: A standardized event type you define (e.g., "inbound_message").event_id_original
: The unique ID from the source platform.occurred_at
: A standardized ISO 8601 timestamp.data
: The core payload, with consistent field names that you define.
Example Common Format:
{
"source_platform": "twilio",
"event_type_normalized": "inbound_message",
"event_id_original": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"occurred_at": "2025-06-16T10:30:00Z",
"data": {
"sender": "+15005550001",
"recipient": "+15005550006",
"body": "Hello from Twilio!",
"provider_message_id": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
Step 2: Configure Hookdeck and Write Transformations
For each third-party service, you will create a new Connection . During the creation process, you will:
- Create a new Source for the service (e.g., "Twilio Inbound SMS") and get its unique URL.
- Configure the third-party service to send webhooks to this new URL.
- Select your single, unified Destination .
- Write a Transformation script to normalize the incoming data.
The goal of the script is to convert the incoming payload from that specific platform into your common internal event format.
Concrete Example: Normalizing SMS Webhooks from Twilio and Vonage
Let's illustrate this with inbound SMS messages. Both Twilio and Vonage can send them, but their payloads differ.
Twilio Payload (Simplified): { "Body": "Hello", "From": "+1500...", "MessageSid": "SM..." }
Vonage Payload (Simplified): { "text": "Hello", "msisdn": "4477...", "messageId": "1400..." }
Transformation for Twilio:
This script maps Twilio's fields to your common format.
addHandler("transform", (request, context) => {
const twilioEvent = request.body;
const normalizedEvent = {
source_platform: "twilio",
event_type_normalized: "inbound_message",
event_id_original: twilioEvent.MessageSid,
occurred_at: new Date().toISOString(),
data: {
sender: twilioEvent.From,
recipient: twilioEvent.To,
body: twilioEvent.Body,
provider_message_id: twilioEvent.MessageSid,
}
};
request.body = normalizedEvent;
request.headers['Content-Type'] = 'application/json';
delete request.headers['content-length'];
return request;
});
Transformation for Vonage:
This script maps Vonage's different field names to the same common format.
addHandler("transform", (request, context) => {
const vonageEvent = request.body;
const normalizedEvent = {
source_platform: "vonage",
event_type_normalized: "inbound_message",
event_id_original: vonageEvent.messageId,
occurred_at: new Date(vonageEvent['message-timestamp']).toISOString(),
data: {
sender: vonageEvent.msisdn,
recipient: vonageEvent.to,
body: vonageEvent.text,
provider_message_id: vonageEvent.messageId,
}
};
request.body = normalizedEvent;
request.headers['Content-Type'] = 'application/json';
delete request.headers['content-length'];
return request;
});
Step 3: Implement Your Unified Backend Endpoint
Your single backend endpoint now only needs to handle one data structure: your common internal event format. The logic becomes much simpler.
Backend Logic Flow:
Your endpoint's logic can now be simplified to follow these steps:
- Receive the Request: Your single endpoint (e.g.,
POST /webhooks/unified-handler
) receives the request from Hookdeck. - Parse the Normalized Event: The request body will always contain the clean, consistent JSON object you defined in your common internal event format.
- Process the Unified Data: Use the
data
object from the normalized event to perform your core business logic. For our example, this would be processing the inbound message. - (Optional) Handle Source-Specific Cases: If you need to perform logic that is unique to the original service, you can use the
source_platform
field (e.g.,'twilio'
or'vonage'
) in aswitch
statement orif
condition. - Return a
200 OK
Response: Send a success status code to Hookdeck to acknowledge that the event was received successfully.
Benefits of the Fan-In Pattern with Hookdeck
- Simplified Application Backend: You only need to build, secure, and maintain one endpoint for all webhook ingestion, drastically reducing complexity.
- Decoupled Integrations: Your application is no longer tightly coupled to the specific data formats of third-party services. Adding a new service only requires new Hookdeck configuration, not changes to your core application code.
- Centralized Control & Observability: Manage authentication, retries, transformations, and monitoring for all incoming webhooks in one place.
- Standardized Event Handling: By normalizing data at the edge with Hookdeck, you ensure your application deals with a clean, consistent, and predictable event structure.
Conclusion
The fan-in pattern, powered by Hookdeck's transformation capabilities, is a strategic approach to building scalable and maintainable webhook integrations. By normalizing data from multiple sources into a common format, you can create a cleaner, more robust, and more flexible backend architecture.
Related Hookdeck Features & Documentation
Configuring Sources ->
Learn about Source authentication and setup.
Configuring Destinations ->
Details on setting up your backend endpoint.
Understanding Connections ->
The link between Sources and Destinations.
Powerful Payload Transformations ->
Deep dive into writing transformation scripts.
Inspecting Event Logs ->
Troubleshoot and verify your setup.