WhatsApp Webhook Forwarding Feature - Complete Documentation
Table of Contents
- Overview
- Architecture & Flow
- Key Components
- Configuration Guide
- N8N Format Structure
- Event Types
- Implementation Details
- Security Features
- Troubleshooting
- Code References
Overview
The WhatsApp Webhook Forwarding feature allows your application to automatically forward incoming WhatsApp webhook events to external endpoints (primarily N8N automation workflows) in a standardized, structured format.
What It Does
- ✅ Receives WhatsApp webhook events (messages, status updates, etc.)
- ✅ Transforms raw WhatsApp payloads into N8N-compatible structured format
- ✅ Forwards selected events to configured external webhook URLs
- ✅ Includes signature verification for security
- ✅ Supports multi-tenancy (tenant-specific configurations)
- ✅ Filters events by field types (messages, statuses, templates, etc.)
Use Cases
- Integrate with N8N for automation workflows
- Send WhatsApp events to third-party systems
- Build custom integrations without modifying core code
- Create multi-step automation based on WhatsApp events
- Analytics and monitoring of WhatsApp interactions
Architecture & Flow
┌─────────────────┐
│ WhatsApp API │ (Meta/Facebook)
│ (Webhooks) │
└────────┬────────┘
│ 1. Webhook Event
▼
┌─────────────────────────────────────────┐
│ WhatsAppWebhookController │
│ (/webhook/whatsapp) │
├─────────────────────────────────────────┤
│ • Receives raw WhatsApp payload │
│ • Verifies webhook authenticity │
│ • Processes messages/statuses │
│ • Checks duplicate messages │
└────────┬────────────────────────────────┘
│ 2. Process Webhook
▼
┌─────────────────────────────────────────┐
│ forwardWebhookData() │
├─────────────────────────────────────────┤
│ • Check if forwarding enabled │
│ • Filter by enabled event fields │
│ • Check webhook URL validity │
└────────┬────────────────────────────────┘
│ 3. Transform Data
▼
┌─────────────────────────────────────────┐
│ WhatsAppWebhookN8NTransformer │
├─────────────────────────────────────────┤
│ • Detect event type │
│ • Extract resource data │
│ • Structure payload for N8N │
│ • Add tenant context │
│ • Include original payload │
└────────┬────────────────────────────────┘
│ 4. N8N Formatted Payload
▼
┌─────────────────────────────────────────┐
│ HTTP Request to External Endpoint │
├─────────────────────────────────────────┤
│ • Add signature header (HMAC-SHA256) │
│ • Add event type header │
│ • Add timestamp header │
│ • POST/GET/Custom method │
└────────┬────────────────────────────────┘
│ 5. Forward to N8N
▼
┌─────────────────┐
│ N8N Workflow │ (or any external endpoint)
│ (Automation) │
└─────────────────┘Key Components
1. WhatsAppWebhookController
File: app/Http/Controllers/Whatsapp/WhatsAppWebhookController.php
Main Method: forwardWebhookData(string $feedData, array $payload)
Responsibilities:
- Retrieve tenant-specific webhook settings
- Filter events based on configured field types
- Call transformer for payload conversion
- Send HTTP request to external endpoint
- Log forwarding success/failure
Settings Used:
enable_webhook_resend- Enable/disable forwardingwhatsapp_data_resend_to- Target webhook URLwebhook_resend_method- HTTP method (POST/GET)webhook_selected_fields- Event types to forward
2. WhatsAppWebhookN8NTransformer
File: app/Transformers/WhatsAppWebhookN8NTransformer.php
Main Method: transform(array $whatsappPayload, int $tenantId): array
Responsibilities:
- Detect WhatsApp event type from payload structure
- Extract message/status/account data
- Structure data in N8N-compatible format
- Add tenant information
- Include metadata for tracing
Key Methods:
detectEventType()- Identifies the webhook event typeextractResourceData()- Extracts relevant data based on eventgetResourceType()- Determines resource type (message, status, etc.)getTenantData()- Fetches tenant contextgenerateEventId()- Creates unique event identifier
3. Webhook Configuration UI
File: resources/views/livewire/tenant/settings/whats-mark/web-hooks-settings.blade.php
Features:
- Toggle to enable/disable webhook forwarding
- URL input for webhook endpoint
- HTTP method selector (GET/POST)
- Event field selector with:
- Multi-select dropdown
- Search functionality
- Select all/Clear all options
- Visual tag display for selected events
Configuration Guide
Step 1: Enable Webhook Forwarding
Navigate to Settings
- Go to: Tenant Settings → WhatsApp → Webhooks
Enable Webhook Resend
- Toggle "Enable Webhooks Resend" to ON
Configure Webhook URL
- Enter your N8N webhook URL or external endpoint
- Example:
https://your-n8n-instance.com/webhook/whatsapp-events - Must be a valid HTTPS URL (recommended for production)
Select HTTP Method
- POST (Recommended) - Sends data in request body
- GET - Sends data as query parameters
- Other methods available for custom integrations
Select Event Fields
- Choose which WhatsApp event types to forward:
messages- Incoming messagesmessage_echoes- Sent messagesmessage_status- Status updates (sent, delivered, read, failed)account_alerts- Account alertsaccount_review_update- Account review updatesbusiness_capability_update- Business capability changesmessage_template_status_update- Template status changesmessage_template_quality_update- Template quality updatesphone_number_quality_update- Phone quality updates- And more...
- Choose which WhatsApp event types to forward:
Save Configuration
Step 2: Set Up N8N Webhook (If using N8N)
Create N8N Workflow
- Add a Webhook node as the trigger
Configure Webhook Node
- HTTP Method:
POST - Path:
/webhook/whatsapp-events(or custom path) - Authentication: None (or configure based on your needs)
- Response: Return immediately
- HTTP Method:
Copy Webhook URL
- Get the production webhook URL from N8N
- Paste it in your WhatsApp webhook settings
Activate Workflow
- Ensure the workflow is active to receive events
N8N Format Structure
Complete Payload Example
Based on the sample JSON you provided, here's the standardized structure:
{
"event": {
"id": "evt_1767263928_7KGQvXqbFF",
"type": "whatsapp.message.received",
"timestamp": "2026-01-01T10:38:48+00:00",
"version": "1.0"
},
"tenant": {
"id": 3,
"name": "vatsal"
},
"data": {
"resource": {
"type": "message",
"id": "wamid.HBgMOTE5OTI1MTE5Mjg0FQIAEhgWM0VCMDE1NDRCMTFBMDQ1RTlFM0NFMwA=",
"attributes": {
"message_id": "wamid.HBgMOTE5OTI1MTE5Mjg0FQIAEhgWM0VCMDE1NDRCMTFBMDQ1RTlFM0NFMwA=",
"from": "919925119284",
"timestamp": "1767263926",
"type": "text",
"text": "Hello",
"context": null
}
},
"relationships": {
"contact": {
"wa_id": "919925119284",
"name": "Team Corbital Technologies"
},
"metadata": {
"phone_number_id": "339141689277134",
"display_phone_number": "919428735270"
}
}
},
"whatsapp": {
"original_payload": {
"object": "whatsapp_business_account",
"entry": [
{
"id": "315064115026195",
"changes": [
{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "919428735270",
"phone_number_id": "339141689277134"
},
"contacts": [
{
"profile": {
"name": "Team Corbital Technologies"
},
"wa_id": "919925119284"
}
],
"messages": [
{
"from": "919925119284",
"id": "wamid.HBgMOTE5OTI1MTE5Mjg0FQIAEhgWM0VCMDE1NDRCMTFBMDQ1RTlFM0NFMwA=",
"timestamp": "1767263926",
"text": {
"body": "Hello"
},
"type": "text"
}
]
},
"field": "messages"
}
]
}
]
}
},
"metadata": {
"source": "whatsapp_webhook_forward",
"environment": "production",
"request_id": "595bbcf8-93bf-418b-8209-3343c76ad852"
}
}Payload Structure Breakdown
1. Event Object
Contains event metadata and identification:
{
"id": "evt_1767263928_7KGQvXqbFF", // Unique event ID
"type": "whatsapp.message.received", // Event type
"timestamp": "2026-01-01T10:38:48+00:00", // ISO 8601 timestamp
"version": "1.0" // Format version
}2. Tenant Object
Multi-tenancy context:
{
"id": 3, // Tenant ID
"name": "vatsal" // Tenant name/company name
}3. Data Object
Contains the actual event data:
Resource
The main entity (message, status, etc.):
{
"type": "message", // Resource type
"id": "wamid...", // Resource ID (message ID)
"attributes": {
// Resource-specific data
"message_id": "...",
"from": "919925119284",
"timestamp": "1767263926",
"type": "text",
"text": "Hello",
"context": null
}
}Relationships
Related entities:
{
"contact": {
"wa_id": "919925119284",
"name": "Team Corbital Technologies"
},
"metadata": {
"phone_number_id": "339141689277134",
"display_phone_number": "919428735270"
}
}4. WhatsApp Object
Original webhook payload from Meta:
{
"original_payload": {
// Complete raw WhatsApp webhook data
}
}5. Metadata Object
Tracking and debugging information:
{
"source": "whatsapp_webhook_forward",
"environment": "production",
"request_id": "595bbcf8-93bf-418b-8209-3343c76ad852"
}Event Types
Supported Event Types
The transformer automatically detects and categorizes events:
Message Events
whatsapp.message.received- Incoming message from userwhatsapp.message.sent- Message sent by business (echo)whatsapp.message.delivered- Message delivered to recipientwhatsapp.message.read- Message read by recipientwhatsapp.message.failed- Message delivery failed
Status Events
whatsapp.status.updated- Generic status update
Account Events
whatsapp.account.alert- Account alert notificationwhatsapp.account.review_updated- Account review status changedwhatsapp.account.updated- Account information updated
Business Events
whatsapp.business.capability_updated- Business capabilities changedwhatsapp.business.status_updated- Business status changed
Template Events
whatsapp.template.status_updated- Template approval statuswhatsapp.template.quality_updated- Template quality score changed
Phone Events
whatsapp.phone.quality_updated- Phone number quality changedwhatsapp.phone.name_updated- Display name updated
Other Events
whatsapp.call.received- Incoming WhatsApp callwhatsapp.flow.event- Flow-related event
Event Detection Logic
The transformer uses the following logic to detect event types:
// 1. Check the 'field' from webhook changes
$field = $payload['entry'][0]['changes'][0]['field'];
// 2. Based on field and data structure:
if ($field === 'messages') {
// Check if it's a received message
return 'whatsapp.message.received';
}
if ($field === 'message_echoes') {
return 'whatsapp.message.sent';
}
// 3. Check for status updates
if (isset($payload['entry'][0]['changes'][0]['value']['statuses'])) {
$status = $statuses[0]['status'];
// Map to specific event type
return 'whatsapp.message.delivered|read|sent|failed';
}
// 4. Map other fields directly
return match($field) {
'account_alerts' => 'whatsapp.account.alert',
'calls' => 'whatsapp.call.received',
// ... etc
};Implementation Details
Core Logic Flow
1. Webhook Reception (WhatsAppWebhookController)
protected function processWebhookPayload()
{
$feedData = file_get_contents('php://input');
$payload = json_decode($feedData, true);
// Extract business & phone number IDs
$business_id = $payload['entry'][0]['id'];
$phoneNumberId = $payload['entry'][0]['changes'][0]['value']['metadata']['phone_number_id'];
// Get tenant ID from WhatsApp details
$this->tenant_id = getTenantIdFromWhatsappDetails($business_id, $phoneNumberId);
// Process webhook...
// Forward webhook data if enabled
$this->forwardWebhookData($feedData, $payload);
}2. Forward Decision Making
protected function forwardWebhookData(string $feedData, array $payload)
{
// 1. Get tenant settings
$enabled = get_tenant_setting_by_tenant_id('whats-mark', 'enable_webhook_resend', '', $this->tenant_id);
$url = get_tenant_setting_by_tenant_id('whats-mark', 'whatsapp_data_resend_to', '', $this->tenant_id);
$method = get_tenant_setting_by_tenant_id('whats-mark', 'webhook_resend_method', 'POST', $this->tenant_id);
// 2. Extract fields from webhook
$fields = collect($payload['entry'] ?? [])
->flatMap(fn($entry) => collect($entry['changes'] ?? []))
->pluck('field')
->filter()
->unique();
// 3. Get enabled fields from settings
$enabled_fields = collect(get_tenant_setting_by_tenant_id('whats-mark', 'webhook_selected_fields', [], $this->tenant_id));
// 4. Check if field is allowed
$isFieldAllowed = $fields->intersect($enabled_fields)->isNotEmpty();
if (!$isFieldAllowed) {
whatsapp_log('Webhook Forward Skipped', 'info', [
'reason' => 'No enabled fields found for resend',
'fields' => $fields,
]);
return;
}
// 5. Proceed with forwarding...
}3. Data Transformation
// Transform to N8N format
$n8nPayload = \App\Transformers\WhatsAppWebhookN8NTransformer::transform(
$payload,
$this->tenant_id
);4. Signature Generation
// Generate HMAC-SHA256 signature
$secret = config('ApiWebhookManager.signing_secret', config('app.key'));
$signature = hash_hmac('sha256', json_encode($n8nPayload), $secret);5. HTTP Request
$headers = [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'User-Agent' => 'whatsmark-Webhook/1.0',
'X-Webhook-Signature' => $signature,
'X-Webhook-Event' => $n8nPayload['event']['type'] ?? 'unknown',
'X-Webhook-Timestamp' => now()->toIso8601String(),
'X-Webhook-Format' => 'n8n',
];
switch ($method) {
case 'POST':
$response = \Http::withHeaders($headers)->post($url, $n8nPayload);
break;
case 'GET':
$response = \Http::withHeaders($headers)->get($url, $n8nPayload);
break;
default:
$response = \Http::withHeaders($headers)
->withBody(json_encode($n8nPayload), 'application/json')
->send($method, $url);
break;
}6. Logging
whatsapp_log('Webhook Forwarded (N8N Format)', 'info', [
'url' => $url,
'method' => $method,
'event_type' => $n8nPayload['event']['type'] ?? 'unknown',
'status_code' => $response->status(),
'success' => $response->successful(),
]);Security Features
1. Signature Verification
Every forwarded webhook includes a signature for verification:
// Generation (in application)
$secret = config('ApiWebhookManager.signing_secret', config('app.key'));
$signature = hash_hmac('sha256', json_encode($payload), $secret);Verification Example (in N8N or receiving endpoint):
// Node.js example
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler
const signature = req.headers['x-webhook-signature'];
const isValid = verifySignature(
req.body,
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(403).send('Invalid signature');
}2. Custom Headers
Every request includes identifying headers:
X-Webhook-Signature: <hmac-sha256-signature>
X-Webhook-Event: whatsapp.message.received
X-Webhook-Timestamp: 2026-01-01T10:38:48+00:00
X-Webhook-Format: n8n
User-Agent: whatsmark-Webhook/1.03. URL Validation
if ($enabled && filter_var($url, FILTER_VALIDATE_URL)) {
// Only proceed if URL is valid
}4. Duplicate Prevention
The controller checks for duplicate message processing:
$message_id = $payload['entry'][0]['changes'][0]['value']['messages'][0]['id'] ?? '';
if (!empty($message_id)) {
$found = $this->checkMessageProcessed($message_id);
if ($found) {
whatsapp_log('Duplicate Message Detected', 'warning', ['message_id' => $message_id]);
return;
}
}5. Tenant Isolation
All settings and data are tenant-specific:
$enabled = get_tenant_setting_by_tenant_id('whats-mark', 'enable_webhook_resend', '', $this->tenant_id);Troubleshooting
Webhooks Not Being Forwarded
Check 1: Verify Settings
// Check if forwarding is enabled
get_tenant_setting_by_tenant_id('whats-mark', 'enable_webhook_resend', '', $tenant_id);
// Verify URL is configured
get_tenant_setting_by_tenant_id('whats-mark', 'whatsapp_data_resend_to', '', $tenant_id);Check 2: Field Filtering
- Ensure the event field is in the selected fields list
- Check logs: "Webhook Forward Skipped - No enabled fields found"
Check 3: URL Validity
- Must be a valid URL format
- HTTPS recommended for production
- Check for typos in configuration
N8N Not Receiving Data
Check 1: N8N Workflow Status
- Ensure workflow is activated
- Check webhook node configuration
- Verify path matches configuration
Check 2: Firewall/Network
- Check if N8N endpoint is accessible
- Test with curl:
curl -X POST https://your-n8n.com/webhook/test \
-H "Content-Type: application/json" \
-d '{"test": "data"}'Check 3: Response Status
- Check application logs for response status
- Look for "Webhook Forwarded" log entries
Signature Verification Failing
Issue: Signature mismatch in receiving endpoint
Solution:
- Ensure secret matches on both sides
- Verify you're computing signature on the same payload
- Check that payload hasn't been modified in transit
- Use
json_encode($payload)exactly as sent
Logs to Check
# Check webhook forward logs
php artisan tinker
>>> \DB::table('whatsapp_logs')->where('title', 'like', '%Webhook Forward%')->latest()->get();
# Check for skipped webhooks
>>> \DB::table('whatsapp_logs')->where('title', 'Webhook Forward Skipped')->latest()->get();
# Check for errors
>>> \DB::table('whatsapp_logs')->where('title', 'Webhook Forward Error')->latest()->get();Code References
Primary Files
Controller Logic
- File:
app/Http/Controllers/Whatsapp/WhatsAppWebhookController.php - Method:
forwardWebhookData()(lines 820-900) - Handles: Settings retrieval, field filtering, HTTP forwarding
- File:
Transformer Logic
- File:
app/Transformers/WhatsAppWebhookN8NTransformer.php - Method:
transform()(lines 1-315) - Handles: Event detection, data extraction, N8N formatting
- File:
Configuration UI
- File:
resources/views/livewire/tenant/settings/whats-mark/web-hooks-settings.blade.php - Handles: User interface for webhook configuration
- File:
Admin Configuration
- File:
resources/views/livewire/admin/whats-app/webhook-configuration.blade.php - Handles: Admin-level webhook settings
- File:
Related Files
Modules/ApiWebhookManager/Config/config.php- Webhook configModules/ApiWebhookManager/Enums/WebhookEventType.php- Event type definitionsModules/ApiWebhookManager/Services/WebhookService.php- Webhook service utilities
Configuration Keys
// Settings keys used:
'whats-mark.enable_webhook_resend' // Boolean: Enable forwarding
'whats-mark.whatsapp_data_resend_to' // String: Webhook URL
'whats-mark.webhook_resend_method' // String: HTTP method (POST/GET)
'whats-mark.webhook_selected_fields' // Array: Enabled event fieldsDatabase Tables
-- Settings stored in:
settings (tenant-specific settings)
-- Logs stored in:
whatsapp_logs (webhook forward logs)Example N8N Workflow
Basic Workflow Structure
┌─────────────────┐
│ Webhook Node │ (Trigger)
│ (POST) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Function Node │ (Verify Signature)
│ │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Switch Node │ (Route by event.type)
│ │
└────────┬────────┘
│
├─────► whatsapp.message.received
│ └─► Save to DB / Send Notification
│
├─────► whatsapp.message.delivered
│ └─► Update message status
│
└─────► Other event types...Signature Verification (Function Node)
// items[0].json contains the webhook payload
const payload = items[0].json;
const signature = $node['Webhook'].context['headers']['x-webhook-signature'];
const secret = 'your-secret-key-here'; // Or from environment
const crypto = require('crypto');
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
if (signature !== expectedSignature) {
throw new Error('Invalid signature');
}
return items;Event Routing (Switch Node)
// Route based on event type
return [
payload.event.type === 'whatsapp.message.received' ? 0 : -1,
payload.event.type === 'whatsapp.message.delivered' ? 0 : -1,
payload.event.type === 'whatsapp.message.read' ? 0 : -1,
// ... other event types
];Best Practices
1. Selective Event Forwarding
- Only forward events you actually need
- Reduces bandwidth and processing overhead
- Easier to debug and maintain
2. Error Handling
- Implement retry logic in N8N for failed webhooks
- Monitor webhook forward logs regularly
- Set up alerts for high error rates
3. Security
- Always use HTTPS endpoints
- Implement signature verification
- Rotate secrets periodically
- Restrict webhook endpoint access
4. Performance
- Use asynchronous processing in N8N when possible
- Avoid long-running operations in webhook handlers
- Consider queuing for heavy processing
5. Testing
- Test webhook forwarding in development first
- Use N8N's test webhook feature
- Verify all event types you need
- Check signature verification works
6. Monitoring
- Log all webhook forwards
- Track success/failure rates
- Monitor response times
- Set up alerts for issues
Migration Notes
From Previous Version (If Applicable)
If migrating from an older webhook system:
- Settings Migration: Old settings may need to be converted
- Event Field Mapping: Ensure field names match new structure
- Signature Method: Update signature verification if changed
- Testing: Thoroughly test all event types after migration
Support & References
Additional Documentation
- WHATSAPP_N8N_WEBHOOK_GUIDE.md - Comprehensive N8N integration guide
- WHATSAPP_N8N_WEBHOOK_REFERENCE.md - Quick reference for event types and payload structure
Commits Reference
- Main implementation:
74a9907c- "feat: Implement WhatsApp Webhook Transformer for N8N integration" - Enhancements:
984b8440- "Enhance multi-tenancy support and improve webhook processing"
Related Features
- ApiWebhookManager Module - Generic webhook management
- Multi-tenancy - Tenant-specific webhook configurations
- WhatsApp Integration - Core WhatsApp Business API integration
Changelog
Version 1.6.0 (Current)
- ✅ N8N format transformer implemented
- ✅ Multi-tenant webhook forwarding
- ✅ Signature verification
- ✅ Selective event field filtering
- ✅ Comprehensive logging
- ✅ Admin and tenant configuration UI
Future Enhancements (Planned)
- [ ] Webhook retry mechanism
- [ ] Webhook forward queue for reliability
- [ ] Webhook analytics dashboard
- [ ] Custom transformation templates
- [ ] Webhook event batching
- [ ] Webhook rate limiting
Summary
The WhatsApp Webhook Forwarding feature provides a robust, secure, and flexible way to integrate WhatsApp events with external automation systems like N8N. With its standardized format, comprehensive event coverage, and tenant-specific configuration, it enables powerful automation workflows while maintaining security and multi-tenancy support.
Key Benefits:
- 🚀 Easy integration with N8N and other webhook consumers
- 🔒 Secure with signature verification
- 🎯 Selective event forwarding for efficiency
- 🏢 Multi-tenant support
- 📊 Comprehensive logging and debugging
- 🔧 Flexible configuration per tenant
For questions or issues, refer to the application logs or contact the development team.