24 KiB
Webhook System Architecture
Last Updated: 2026-01-20
Status: Phase 3 Complete - Advanced Security Features Implemented
Overview
Attune provides built-in webhook support as a first-class feature of the trigger system. Any trigger can be webhook-enabled, allowing external systems to fire events by posting to a unique webhook URL. This eliminates the need for generic webhook triggers and provides better security and traceability.
Core Concepts
Webhook-Enabled Triggers
Any trigger in Attune can have webhooks enabled:
- Pack declares trigger (e.g.,
github.push,stripe.payment_succeeded) - User enables webhooks via toggle in UI or API
- System generates unique webhook key (secure random token)
- Webhook URL is provided for external system configuration
- External system POSTs to webhook URL with payload
- Attune creates event from webhook payload
- Rules evaluate normally against the event
Key Benefits
- Per-Trigger Security: Each trigger has its own unique webhook key
- No Generic Triggers: Webhooks are a feature, not a trigger type
- Better Traceability: Clear association between webhook and trigger
- Flexible Payloads: Each trigger defines its own payload schema
- Multi-Tenancy Ready: Webhook keys can be scoped to identities/organizations
Architecture
┌─────────────────────────────────────────────────────────────┐
│ External Systems │
├─────────────────────────────────────────────────────────────┤
│ GitHub │ Stripe │ Slack │ Custom Apps │ etc. │
└────┬──────────┬──────────┬──────────┬───────────────────────┘
│ │ │ │
│ POST │ POST │ POST │ POST
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Attune API - Webhook Receiver Endpoint │
│ POST /api/v1/webhooks/:webhook_key │
├─────────────────────────────────────────────────────────────┤
│ 1. Validate webhook key │
│ 2. Look up associated trigger │
│ 3. Parse and validate payload │
│ 4. Create event in database │
│ 5. Return 200 OK │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL Database │
│ ┌────────┐ ┌───────┐ ┌───────┐ │
│ │Trigger │───▶│ Event │───▶│ Rule │ │
│ │webhook │ │ │ │ │ │
│ │enabled │ │ │ │ │ │
│ │webhook │ │ │ │ │ │
│ │ key │ │ │ │ │ │
│ └────────┘ └───────┘ └───────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Rule Evaluation │
│ Execution Scheduling │
└─────────────────────────────────────────────────────────────┘
Database Schema
Existing attune.trigger Table Extensions
Add webhook-related columns to the trigger table:
ALTER TABLE attune.trigger ADD COLUMN IF NOT EXISTS
webhook_enabled BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE attune.trigger ADD COLUMN IF NOT EXISTS
webhook_key VARCHAR(64) UNIQUE;
ALTER TABLE attune.trigger ADD COLUMN IF NOT EXISTS
webhook_secret VARCHAR(128); -- For HMAC signature verification (optional)
-- Index for fast webhook key lookup
CREATE INDEX IF NOT EXISTS idx_trigger_webhook_key
ON attune.trigger(webhook_key)
WHERE webhook_key IS NOT NULL;
Webhook Event Metadata
Events created from webhooks include additional metadata:
{
"source": "webhook",
"webhook_key": "wh_abc123...",
"webhook_metadata": {
"received_at": "2024-01-20T12:00:00Z",
"source_ip": "192.168.1.100",
"user_agent": "GitHub-Hookshot/abc123",
"headers": {
"X-GitHub-Event": "push",
"X-GitHub-Delivery": "12345-67890"
}
},
"payload": {
// Original webhook payload from external system
}
}
API Endpoints
Webhook Receiver
Receive Webhook Event
POST /api/v1/webhooks/:webhook_key
Content-Type: application/json
{
"ref": "refs/heads/main",
"commits": [...],
"repository": {...}
}
Response (Success)
HTTP/1.1 200 OK
Content-Type: application/json
{
"success": true,
"event_id": 12345,
"trigger_ref": "github.push",
"message": "Event created successfully"
}
Response (Invalid Key)
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"success": false,
"error": "Invalid webhook key"
}
Response (Disabled)
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"success": false,
"error": "Webhooks are disabled for this trigger"
}
Webhook Management
Enable Webhooks for Trigger
POST /api/v1/triggers/:id/webhook/enable
Authorization: Bearer <token>
Response
{
"data": {
"id": 123,
"ref": "github.push",
"webhook_enabled": true,
"webhook_key": "wh_abc123xyz789...",
"webhook_url": "https://attune.example.com/api/v1/webhooks/wh_abc123xyz789..."
}
}
Disable Webhooks for Trigger
POST /api/v1/triggers/:id/webhook/disable
Authorization: Bearer <token>
Regenerate Webhook Key
POST /api/v1/triggers/:id/webhook/regenerate
Authorization: Bearer <token>
Response
{
"data": {
"webhook_key": "wh_new_key_here...",
"webhook_url": "https://attune.example.com/api/v1/webhooks/wh_new_key_here...",
"previous_key_revoked": true
}
}
Get Webhook Info
GET /api/v1/triggers/:id/webhook
Authorization: Bearer <token>
Response
{
"data": {
"enabled": true,
"webhook_key": "wh_abc123xyz789...",
"webhook_url": "https://attune.example.com/api/v1/webhooks/wh_abc123xyz789...",
"created_at": "2024-01-20T10:00:00Z",
"last_used_at": "2024-01-20T12:30:00Z",
"total_events": 145
}
}
Webhook Key Format
Webhook keys use a recognizable prefix and secure random suffix:
wh_[32 random alphanumeric characters]
Example: wh_k7j2n9p4m8q1r5w3x6z0a2b5c8d1e4f7
Generation (Rust):
use rand::Rng;
use rand::distributions::Alphanumeric;
fn generate_webhook_key() -> String {
let random_part: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(32)
.map(char::from)
.collect();
format!("wh_{}", random_part)
}
Security Considerations
1. Webhook Key as Bearer Token
The webhook key acts as a bearer token - anyone with the key can post events. Therefore:
- Keys must be long and random (32+ characters)
- Keys must be stored securely
- Keys should be transmitted over HTTPS only
- Keys can be regenerated if compromised
2. Optional Signature Verification
For enhanced security, triggers can require HMAC signature verification:
POST /api/v1/webhooks/:webhook_key
X-Webhook-Signature: sha256=abc123...
Content-Type: application/json
{...}
The signature is computed as:
HMAC-SHA256(webhook_secret, request_body)
This prevents replay attacks and ensures payload integrity.
3. IP Whitelisting (Future)
Triggers can optionally restrict webhooks to specific IP ranges:
{
"webhook_enabled": true,
"webhook_ip_whitelist": [
"192.30.252.0/22", // GitHub
"185.199.108.0/22" // GitHub
]
}
4. Rate Limiting
Apply rate limits to prevent abuse:
- Per webhook key: 100 requests per minute
- Per IP address: 1000 requests per minute
- Global: 10,000 requests per minute
5. Payload Size Limits
Limit webhook payload sizes:
- Maximum payload size: 1 MB
- Reject larger payloads with 413 Payload Too Large
Event Creation from Webhooks
Event Structure
INSERT INTO attune.event (
trigger,
trigger_ref,
payload,
metadata,
source
) VALUES (
<trigger_id>,
<trigger_ref>,
<webhook_payload>,
jsonb_build_object(
'source', 'webhook',
'webhook_key', <webhook_key>,
'received_at', NOW(),
'source_ip', <client_ip>,
'user_agent', <user_agent>,
'headers', <selected_headers>
),
'webhook'
);
Payload Transformation
Webhooks can optionally transform payloads before creating events:
- Direct Pass-Through (default): Entire webhook body becomes event payload
- JSONPath Extraction: Extract specific fields from webhook payload
- Template Transformation: Use templates to reshape payload
Example (JSONPath):
{
"webhook_payload_mapping": {
"commit_sha": "$.head_commit.id",
"branch": "$.ref",
"author": "$.head_commit.author.name"
}
}
Web UI Integration
Trigger Detail Page
Display webhook status for each trigger:
┌──────────────────────────────────────────────────────────┐
│ Trigger: github.push │
├──────────────────────────────────────────────────────────┤
│ │
│ Webhooks [Toggle: ● ON ] │
│ │
│ Webhook URL: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ https://attune.example.com/api/v1/webhooks/wh_k7j... │ │
│ └──────────────────────────────────────────────────────┘ │
│ [Copy URL] [Show Key] [Regenerate] │
│ │
│ Stats: │
│ • Events received: 145 │
│ • Last event: 2 minutes ago │
│ • Created: 2024-01-15 10:30:00 │
│ │
│ Configuration: │
│ □ Require signature verification │
│ □ Enable IP whitelisting │
│ │
└──────────────────────────────────────────────────────────┘
Webhook Key Display
Show webhook key with copy button and security warning:
┌──────────────────────────────────────────────────────────┐
│ Webhook Key │
├──────────────────────────────────────────────────────────┤
│ │
│ wh_k7j2n9p4m8q1r5w3x6z0a2b5c8d1e4f7g9h2 │
│ │
│ [Copy Key] [Hide] │
│ │
│ ⚠️ Keep this key secret. Anyone with this key can │
│ trigger events. If compromised, regenerate │
│ immediately. │
│ │
└──────────────────────────────────────────────────────────┘
Implementation Status
✅ Phase 1: Database & Core (Complete)
- ✅ Add webhook columns to
attune.triggertable - ✅ Create migration with indexes
- ✅ Add webhook key generation function
- ✅ Update trigger repository with webhook methods
- ✅ All integration tests passing (6/6)
✅ Phase 2: API Endpoints (Complete)
- ✅ Webhook receiver endpoint:
POST /api/v1/webhooks/:webhook_key - ✅ Webhook management endpoints:
POST /api/v1/triggers/:ref/webhooks/enablePOST /api/v1/triggers/:ref/webhooks/disablePOST /api/v1/triggers/:ref/webhooks/regenerate
- ✅ Event creation logic with webhook metadata
- ✅ Error handling and validation
- ✅ OpenAPI documentation
- ✅ Integration tests created
Files Added/Modified:
crates/api/src/routes/webhooks.rs- Webhook routes implementationcrates/api/src/dto/webhook.rs- Webhook DTOscrates/api/src/dto/trigger.rs- Added webhook fields to TriggerResponsecrates/api/src/openapi.rs- Added webhook endpoints to OpenAPI speccrates/api/tests/webhook_api_tests.rs- Comprehensive integration tests
✅ Phase 3: Advanced Security Features (Complete)
- ✅ HMAC signature verification (SHA256, SHA512, SHA1)
- ✅ Rate limiting per webhook key with configurable windows
- ✅ IP whitelist support with CIDR notation
- ✅ Payload size limits (configurable per trigger)
- ✅ Webhook event logging for audit and analytics
- ✅ Database functions for security configuration
- ✅ Repository methods for all Phase 3 features
- ✅ Enhanced webhook receiver with security checks
- ✅ Comprehensive error handling and logging
Database Schema Extensions:
webhook_hmac_enabled,webhook_hmac_secret,webhook_hmac_algorithmcolumnswebhook_rate_limit_enabled,webhook_rate_limit_requests,webhook_rate_limit_window_secondscolumnswebhook_ip_whitelist_enabled,webhook_ip_whitelistcolumnswebhook_payload_size_limit_kbcolumnwebhook_event_logtable for audit trailwebhook_rate_limittable for rate limit trackingwebhook_stats_detailedview for analytics
Repository Methods Added:
enable_webhook_hmac()- Enable HMAC with secret generationdisable_webhook_hmac()- Disable HMAC verificationconfigure_webhook_rate_limit()- Configure rate limitingconfigure_webhook_ip_whitelist()- Configure IP whitelistcheck_webhook_rate_limit()- Check if request within limitcheck_webhook_ip_whitelist()- Verify IP against whitelistlog_webhook_event()- Log webhook requests for analytics
Security Module:
- HMAC signature verification for SHA256, SHA512, SHA1
- Constant-time comparison for signatures
- CIDR notation support for IP whitelists (IPv4 and IPv6)
- Signature format:
sha256=<hex>or just<hex> - Headers:
X-Webhook-SignatureorX-Hub-Signature-256
Webhook Receiver Enhancements:
- Payload size limit enforcement (returns 413 if exceeded)
- IP whitelist validation (returns 403 if not allowed)
- Rate limit enforcement (returns 429 if exceeded)
- HMAC signature verification (returns 401 if invalid)
- Comprehensive event logging for all requests (success and failure)
- Processing time tracking
- Detailed error messages with proper HTTP status codes
Files Added/Modified:
attune/migrations/20260120000002_webhook_advanced_features.sql(362 lines)crates/common/src/models.rs- Added Phase 3 fields and WebhookEventLog modelcrates/common/src/repositories/trigger.rs- Added Phase 3 methods (215 lines)crates/api/src/webhook_security.rs- HMAC and IP validation (274 lines)crates/api/src/routes/webhooks.rs- Enhanced receiver with security (350+ lines)crates/api/src/middleware/error.rs- Added TooManyRequests error typecrates/api/Cargo.toml- Added hmac, sha1, sha2, hex dependencies
📋 Phase 4: Web UI Integration (In Progress)
- ✅ Add webhook toggle to trigger detail page
- ✅ Display webhook URL and key
- ✅ Add copy-to-clipboard functionality
- Show webhook statistics from
webhook_stats_detailedview - Add regenerate key button with confirmation
- HMAC configuration UI (enable/disable, view secret)
- Rate limit configuration UI
- IP whitelist management UI
- Webhook event log viewer
- Real-time webhook testing tool
📋 Phase 5: Additional Features (TODO)
- Webhook retry on failure with exponential backoff
- Payload transformation/mapping with JSONPath
- Multiple webhook keys per trigger
- Webhook health monitoring and alerts
- Batch webhook processing
- Webhook response validation
- Custom header injection
- Webhook forwarding/proxying
Example Use Cases
1. GitHub Push Events
Pack Definition:
# packs/github/triggers/push.yaml
name: push
ref: github.push
description: "Triggered when code is pushed to a repository"
type: webhook
payload_schema:
type: object
properties:
ref:
type: string
description: "Git reference (branch/tag)"
commits:
type: array
description: "Array of commits"
repository:
type: object
description: "Repository information"
User Workflow:
- Navigate to trigger
github.pushin UI - Enable webhooks (toggle ON)
- Copy webhook URL
- Configure in GitHub repository settings:
- Payload URL:
https://attune.example.com/api/v1/webhooks/wh_abc123... - Content type:
application/json - Events: Just the push event
- Payload URL:
- GitHub sends webhook on push
- Attune creates event
- Rules evaluate and trigger actions
2. Stripe Payment Events
Pack Definition:
# packs/stripe/triggers/payment_succeeded.yaml
name: payment_succeeded
ref: stripe.payment_succeeded
description: "Triggered when a payment succeeds"
type: webhook
payload_schema:
type: object
properties:
id:
type: string
amount:
type: integer
currency:
type: string
customer:
type: string
User Workflow:
- Enable webhooks for
stripe.payment_succeededtrigger - Copy webhook URL
- Configure in Stripe dashboard
- Enable signature verification (recommended for Stripe)
- Set webhook secret in Attune
- Stripe sends webhook on successful payment
- Attune verifies signature and creates event
- Rules trigger actions (send receipt, update CRM, etc.)
3. Custom Application Events
Pack Definition:
# packs/myapp/triggers/deployment_complete.yaml
name: deployment_complete
ref: myapp.deployment_complete
description: "Triggered when application deployment completes"
type: webhook
payload_schema:
type: object
properties:
environment:
type: string
enum: [dev, staging, production]
version:
type: string
deployed_by:
type: string
status:
type: string
enum: [success, failure]
User Workflow:
- Enable webhooks for
myapp.deployment_completetrigger - Get webhook URL
- Add to CI/CD pipeline:
curl -X POST https://attune.example.com/api/v1/webhooks/wh_xyz789... \ -H "Content-Type: application/json" \ -d '{ "environment": "production", "version": "v2.1.0", "deployed_by": "jenkins", "status": "success" }' - Attune receives webhook and creates event
- Rules trigger notifications, health checks, etc.
Testing
Manual Testing
# Enable webhooks for a trigger
curl -X POST http://localhost:8080/api/v1/triggers/123/webhook/enable \
-H "Authorization: Bearer $TOKEN"
# Get webhook info
curl http://localhost:8080/api/v1/triggers/123/webhook \
-H "Authorization: Bearer $TOKEN"
# Send test webhook
WEBHOOK_KEY="wh_k7j2n9p4m8q1r5w3x6z0a2b5c8d1e4f7"
curl -X POST http://localhost:8080/api/v1/webhooks/$WEBHOOK_KEY \
-H "Content-Type: application/json" \
-d '{"test": "payload", "value": 123}'
# Verify event was created
curl http://localhost:8080/api/v1/events?limit=1 \
-H "Authorization: Bearer $TOKEN"
Integration Tests
#[tokio::test]
async fn test_webhook_enable_disable() {
// Create trigger
// Enable webhooks
// Verify webhook key generated
// Disable webhooks
// Verify key removed
}
#[tokio::test]
async fn test_webhook_event_creation() {
// Enable webhooks for trigger
// POST to webhook endpoint
// Verify event created in database
// Verify event has correct payload and metadata
}
#[tokio::test]
async fn test_webhook_key_regeneration() {
// Enable webhooks
// Save original key
// Regenerate key
// Verify new key is different
// Verify old key no longer works
// Verify new key works
}
#[tokio::test]
async fn test_webhook_invalid_key() {
// POST to webhook endpoint with invalid key
// Verify 404 response
// Verify no event created
}
#[tokio::test]
async fn test_webhook_rate_limiting() {
// Send 101 requests in 1 minute
// Verify rate limit exceeded error
}
Migration from Generic Webhooks
If Attune previously had generic webhook triggers, migration steps:
- Create new webhook-enabled triggers for each webhook use case
- Enable webhooks for new triggers
- Provide mapping tool in UI to migrate old webhook URLs
- Run migration script to update external systems
- Deprecate generic webhook triggers
Performance Considerations
Webhook Endpoint Optimization
- Async processing: Return 200 OK immediately, process event async
- Connection pooling: Reuse database connections
- Caching: Cache webhook key lookups (with TTL)
- Bulk event creation: Batch multiple webhook events
Database Indexes
-- Fast webhook key lookup
CREATE INDEX idx_trigger_webhook_key ON attune.trigger(webhook_key);
-- Webhook event queries
CREATE INDEX idx_event_source ON attune.event(source) WHERE source = 'webhook';
CREATE INDEX idx_event_webhook_key ON attune.event((metadata->>'webhook_key'));
Related Documentation
Conclusion
Built-in webhook support as a trigger feature provides:
- ✅ Better security with per-trigger webhook keys
- ✅ Clear association between webhooks and triggers
- ✅ Flexible payload handling per trigger type
- ✅ Easy external system integration
- ✅ Full audit trail and traceability
This design eliminates the need for generic webhook triggers while providing a more robust and maintainable webhook system.