Files
attune/docs/architecture/webhook-system-architecture.md
2026-02-04 17:46:30 -06:00

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:

  1. Pack declares trigger (e.g., github.push, stripe.payment_succeeded)
  2. User enables webhooks via toggle in UI or API
  3. System generates unique webhook key (secure random token)
  4. Webhook URL is provided for external system configuration
  5. External system POSTs to webhook URL with payload
  6. Attune creates event from webhook payload
  7. 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:

  1. Direct Pass-Through (default): Entire webhook body becomes event payload
  2. JSONPath Extraction: Extract specific fields from webhook payload
  3. 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)

  1. Add webhook columns to attune.trigger table
  2. Create migration with indexes
  3. Add webhook key generation function
  4. Update trigger repository with webhook methods
  5. All integration tests passing (6/6)

Phase 2: API Endpoints (Complete)

  1. Webhook receiver endpoint: POST /api/v1/webhooks/:webhook_key
  2. Webhook management endpoints:
    • POST /api/v1/triggers/:ref/webhooks/enable
    • POST /api/v1/triggers/:ref/webhooks/disable
    • POST /api/v1/triggers/:ref/webhooks/regenerate
  3. Event creation logic with webhook metadata
  4. Error handling and validation
  5. OpenAPI documentation
  6. Integration tests created

Files Added/Modified:

  • crates/api/src/routes/webhooks.rs - Webhook routes implementation
  • crates/api/src/dto/webhook.rs - Webhook DTOs
  • crates/api/src/dto/trigger.rs - Added webhook fields to TriggerResponse
  • crates/api/src/openapi.rs - Added webhook endpoints to OpenAPI spec
  • crates/api/tests/webhook_api_tests.rs - Comprehensive integration tests

Phase 3: Advanced Security Features (Complete)

  1. HMAC signature verification (SHA256, SHA512, SHA1)
  2. Rate limiting per webhook key with configurable windows
  3. IP whitelist support with CIDR notation
  4. Payload size limits (configurable per trigger)
  5. Webhook event logging for audit and analytics
  6. Database functions for security configuration
  7. Repository methods for all Phase 3 features
  8. Enhanced webhook receiver with security checks
  9. Comprehensive error handling and logging

Database Schema Extensions:

  • webhook_hmac_enabled, webhook_hmac_secret, webhook_hmac_algorithm columns
  • webhook_rate_limit_enabled, webhook_rate_limit_requests, webhook_rate_limit_window_seconds columns
  • webhook_ip_whitelist_enabled, webhook_ip_whitelist columns
  • webhook_payload_size_limit_kb column
  • webhook_event_log table for audit trail
  • webhook_rate_limit table for rate limit tracking
  • webhook_stats_detailed view for analytics

Repository Methods Added:

  • enable_webhook_hmac() - Enable HMAC with secret generation
  • disable_webhook_hmac() - Disable HMAC verification
  • configure_webhook_rate_limit() - Configure rate limiting
  • configure_webhook_ip_whitelist() - Configure IP whitelist
  • check_webhook_rate_limit() - Check if request within limit
  • check_webhook_ip_whitelist() - Verify IP against whitelist
  • log_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-Signature or X-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 model
  • crates/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 type
  • crates/api/Cargo.toml - Added hmac, sha1, sha2, hex dependencies

📋 Phase 4: Web UI Integration (In Progress)

  1. Add webhook toggle to trigger detail page
  2. Display webhook URL and key
  3. Add copy-to-clipboard functionality
  4. Show webhook statistics from webhook_stats_detailed view
  5. Add regenerate key button with confirmation
  6. HMAC configuration UI (enable/disable, view secret)
  7. Rate limit configuration UI
  8. IP whitelist management UI
  9. Webhook event log viewer
  10. Real-time webhook testing tool

📋 Phase 5: Additional Features (TODO)

  1. Webhook retry on failure with exponential backoff
  2. Payload transformation/mapping with JSONPath
  3. Multiple webhook keys per trigger
  4. Webhook health monitoring and alerts
  5. Batch webhook processing
  6. Webhook response validation
  7. Custom header injection
  8. 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:

  1. Navigate to trigger github.push in UI
  2. Enable webhooks (toggle ON)
  3. Copy webhook URL
  4. 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
  5. GitHub sends webhook on push
  6. Attune creates event
  7. 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:

  1. Enable webhooks for stripe.payment_succeeded trigger
  2. Copy webhook URL
  3. Configure in Stripe dashboard
  4. Enable signature verification (recommended for Stripe)
  5. Set webhook secret in Attune
  6. Stripe sends webhook on successful payment
  7. Attune verifies signature and creates event
  8. 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:

  1. Enable webhooks for myapp.deployment_complete trigger
  2. Get webhook URL
  3. 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"
      }'
    
  4. Attune receives webhook and creates event
  5. 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:

  1. Create new webhook-enabled triggers for each webhook use case
  2. Enable webhooks for new triggers
  3. Provide mapping tool in UI to migrate old webhook URLs
  4. Run migration script to update external systems
  5. 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'));


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.