{
  "feature": "webhook-ingestion",
  "version": "1.0.0",
  "description": "Receive and process incoming webhooks from external services with signature verification (HMAC/RSA), replay protection, idempotent deduplication, and async handler routing",
  "category": "integration",
  "tags": [
    "webhooks",
    "ingestion",
    "signature-verification",
    "replay-protection",
    "events"
  ],
  "fields": [
    {
      "name": "source",
      "type": "text",
      "required": true,
      "label": "Webhook source identifier (e.g., provider name or integration ID)"
    },
    {
      "name": "event_type",
      "type": "text",
      "required": true,
      "label": "Event type from the external service (e.g., payment.completed)"
    },
    {
      "name": "payload",
      "type": "json",
      "required": true,
      "label": "Raw webhook payload body"
    },
    {
      "name": "signature",
      "type": "text",
      "required": true,
      "label": "Cryptographic signature from the webhook header"
    },
    {
      "name": "signature_algorithm",
      "type": "select",
      "required": false,
      "label": "Signature verification algorithm",
      "options": [
        {
          "value": "hmac_sha256",
          "label": "HMAC-SHA256"
        },
        {
          "value": "hmac_sha512",
          "label": "HMAC-SHA512"
        },
        {
          "value": "rsa_sha256",
          "label": "RSA-SHA256"
        }
      ]
    },
    {
      "name": "timestamp",
      "type": "datetime",
      "required": true,
      "label": "Webhook timestamp from the provider header"
    },
    {
      "name": "event_id",
      "type": "text",
      "required": true,
      "label": "Unique event identifier for idempotency deduplication"
    },
    {
      "name": "processing_status",
      "type": "select",
      "required": false,
      "label": "Current processing status",
      "options": [
        {
          "value": "received",
          "label": "Received"
        },
        {
          "value": "verified",
          "label": "Verified"
        },
        {
          "value": "processing",
          "label": "Processing"
        },
        {
          "value": "completed",
          "label": "Completed"
        },
        {
          "value": "failed",
          "label": "Failed"
        }
      ]
    },
    {
      "name": "handler_id",
      "type": "text",
      "required": false,
      "label": "Registered handler that processes this event type"
    },
    {
      "name": "retry_count",
      "type": "number",
      "required": false,
      "label": "Number of handler retry attempts"
    },
    {
      "name": "webhook_secret",
      "type": "hidden",
      "required": true,
      "label": "Shared secret or public key for signature verification"
    }
  ],
  "rules": {
    "signature_verification": [
      "Every incoming webhook must include a cryptographic signature in the request header",
      "HMAC-SHA256: compute HMAC of raw body using shared secret; compare with provided signature",
      "RSA-SHA256: verify signature using provider's public key against raw body",
      "Reject webhook immediately if signature does not match (do not process payload)",
      "Use constant-time comparison to prevent timing attacks on signature verification"
    ],
    "replay_protection": [
      "Reject webhooks with timestamp older than 5 minutes from current server time",
      "Clock skew tolerance of 30 seconds to account for network delays",
      "Store processed event_ids for deduplication window (minimum 24 hours)"
    ],
    "idempotency": [
      "Deduplicate by event_id; if event_id already processed, return 200 without re-processing",
      "Deduplication window is 24 hours; events older than 24 hours may be reprocessed"
    ],
    "processing": [
      "Store raw payload before processing (audit trail and replay capability)",
      "Route events to registered handlers based on source + event_type combination",
      "Process handlers asynchronously; return 200 immediately to webhook sender",
      "Failed handlers retry 3 times with exponential backoff (1s, 4s, 16s)"
    ],
    "response": [
      "Always return 200 status to webhook sender after signature verification",
      "Return 401 for invalid signatures; return 400 for malformed payloads",
      "Provider will retry on non-2xx responses; avoid unnecessary retries by responding quickly"
    ]
  },
  "outcomes": {
    "webhook_received_and_verified": {
      "priority": 1,
      "given": [
        {
          "field": "signature",
          "source": "request",
          "operator": "exists"
        },
        {
          "field": "timestamp",
          "source": "request",
          "operator": "exists"
        },
        {
          "field": "event_id",
          "source": "request",
          "operator": "exists"
        },
        "Signature verification passes against raw payload",
        "Timestamp is within 5-minute replay window"
      ],
      "then": [
        {
          "action": "create_record",
          "type": "webhook_event",
          "fields": [
            "source",
            "event_type",
            "payload",
            "event_id",
            "timestamp"
          ]
        },
        {
          "action": "set_field",
          "target": "processing_status",
          "value": "verified"
        },
        {
          "action": "emit_event",
          "event": "webhook.received",
          "payload": [
            "source",
            "event_type",
            "event_id"
          ]
        },
        {
          "action": "emit_event",
          "event": "webhook.verified",
          "payload": [
            "source",
            "event_type",
            "event_id"
          ]
        }
      ],
      "result": "Webhook stored and verified; 200 returned to sender; async processing initiated"
    },
    "webhook_processed_successfully": {
      "priority": 2,
      "given": [
        {
          "field": "processing_status",
          "source": "db",
          "operator": "eq",
          "value": "verified"
        },
        "Handler registered for source + event_type combination",
        "Handler executes without error"
      ],
      "then": [
        {
          "action": "set_field",
          "target": "processing_status",
          "value": "completed"
        },
        {
          "action": "emit_event",
          "event": "webhook.processed",
          "payload": [
            "source",
            "event_type",
            "event_id",
            "handler_id"
          ]
        }
      ],
      "result": "Webhook event processed by registered handler; status marked completed"
    },
    "webhook_handler_failed_with_retry": {
      "priority": 3,
      "given": [
        {
          "field": "processing_status",
          "source": "db",
          "operator": "in",
          "value": [
            "verified",
            "processing"
          ]
        },
        "Handler throws an error during processing",
        {
          "field": "retry_count",
          "source": "db",
          "operator": "lt",
          "value": 3
        }
      ],
      "then": [
        {
          "action": "set_field",
          "target": "retry_count",
          "value": "retry_count + 1"
        },
        {
          "action": "set_field",
          "target": "processing_status",
          "value": "processing"
        }
      ],
      "result": "Handler retried with exponential backoff; retry count incremented"
    },
    "webhook_handler_failed_permanently": {
      "priority": 4,
      "error": "WEBHOOK_HANDLER_FAILED",
      "given": [
        {
          "field": "retry_count",
          "source": "db",
          "operator": "gte",
          "value": 3
        }
      ],
      "then": [
        {
          "action": "set_field",
          "target": "processing_status",
          "value": "failed"
        },
        {
          "action": "emit_event",
          "event": "webhook.failed",
          "payload": [
            "source",
            "event_type",
            "event_id",
            "handler_id",
            "error_message"
          ]
        }
      ],
      "result": "Handler exhausted all retries; webhook marked as failed for manual investigation"
    },
    "webhook_signature_invalid": {
      "priority": 5,
      "error": "WEBHOOK_SIGNATURE_INVALID",
      "given": [
        "Computed signature does not match provided signature"
      ],
      "then": [
        {
          "action": "emit_event",
          "event": "webhook.failed",
          "payload": [
            "source",
            "event_type",
            "error_reason"
          ]
        }
      ],
      "result": "401 returned; webhook rejected without storing or processing payload"
    },
    "webhook_replay_detected": {
      "priority": 6,
      "error": "WEBHOOK_REPLAY_DETECTED",
      "given": [
        "Timestamp is older than 5 minutes from current server time"
      ],
      "then": [
        {
          "action": "emit_event",
          "event": "webhook.failed",
          "payload": [
            "source",
            "event_id",
            "timestamp",
            "error_reason"
          ]
        }
      ],
      "result": "400 returned; stale webhook rejected as potential replay attack"
    },
    "webhook_duplicate_event": {
      "priority": 7,
      "given": [
        "event_id already exists in processed events store"
      ],
      "then": [],
      "result": "200 returned without re-processing; duplicate event silently ignored"
    },
    "webhook_no_handler": {
      "priority": 8,
      "error": "WEBHOOK_NO_HANDLER",
      "given": [
        "No handler registered for the source + event_type combination"
      ],
      "then": [
        {
          "action": "set_field",
          "target": "processing_status",
          "value": "completed"
        },
        {
          "action": "emit_event",
          "event": "webhook.received",
          "payload": [
            "source",
            "event_type",
            "event_id"
          ]
        }
      ],
      "result": "Webhook stored for audit but no processing performed; 200 returned to sender"
    }
  },
  "errors": [
    {
      "code": "WEBHOOK_SIGNATURE_INVALID",
      "status": 401,
      "message": "Webhook signature verification failed. Payload may have been tampered with."
    },
    {
      "code": "WEBHOOK_REPLAY_DETECTED",
      "status": 400,
      "message": "Webhook timestamp is too old. Possible replay attack detected."
    },
    {
      "code": "WEBHOOK_HANDLER_FAILED",
      "status": 500,
      "message": "Webhook handler failed after all retry attempts. Manual investigation required."
    },
    {
      "code": "WEBHOOK_NO_HANDLER",
      "status": 400,
      "message": "No handler registered for this event type. Webhook stored but not processed."
    },
    {
      "code": "WEBHOOK_PAYLOAD_MALFORMED",
      "status": 400,
      "message": "Webhook payload could not be parsed. Expected valid JSON body."
    }
  ],
  "events": [
    {
      "name": "webhook.received",
      "payload": [
        "source",
        "event_type",
        "event_id",
        "timestamp"
      ]
    },
    {
      "name": "webhook.verified",
      "payload": [
        "source",
        "event_type",
        "event_id"
      ]
    },
    {
      "name": "webhook.processed",
      "payload": [
        "source",
        "event_type",
        "event_id",
        "handler_id"
      ]
    },
    {
      "name": "webhook.failed",
      "payload": [
        "source",
        "event_type",
        "event_id",
        "error_code",
        "error_message"
      ]
    }
  ],
  "related": [
    {
      "feature": "payment-gateway",
      "type": "recommended",
      "reason": "Receive payment status updates via provider webhooks"
    },
    {
      "feature": "email-service",
      "type": "optional",
      "reason": "Receive email delivery status notifications via webhooks"
    },
    {
      "feature": "message-queue",
      "type": "recommended",
      "reason": "Queue verified webhooks for async handler processing"
    },
    {
      "feature": "api-gateway",
      "type": "optional",
      "reason": "Route incoming webhooks through gateway for rate limiting"
    }
  ]
}