# HTTP Webhook Receiver
# Receive webhooks from external services, validate signatures, and forward to multiple destinations
#
# Source: https://docs.expanso.io/examples/http-webhook
#
# Usage:
#   curl -o config.yaml https://docs.expanso.io/examples/http-webhook.yaml
#   expanso-edge run -f config.yaml

input:
  http_server:
    address: 0.0.0.0:8080
    path: /webhooks/{source}
    allowed_verbs:
      - POST
    timeout: 30s
    sync_response:
      status: "${! json(\"status\").or(\"200\") }"
      headers:
        Content-Type: application/json

pipeline:
  processors:
    # Extract webhook source from path parameter
    - mapping: |
        meta source = @http_server_request_path.trim_prefix("/webhooks/")
        root = this

    # Parse JSON payload
    - mapping: |
        root = this.parse_json()
        root.webhook_source = @source
        root.received_at = now()

    # Validate signature (for sources that provide one)
    - branch:
        request_map: |
          root.payload = content()
          root.signature = @"X-Hub-Signature-256".or(@"X-Signature").or("")
          root.secret = env("WEBHOOK_SECRET").or("")
        processors:
          - mapping: |
              let expected = "sha256=" + this.payload.hash("hmac_sha256", this.secret).encode("hex")
              root.valid = if this.signature == "" {
                true  # No signature provided, skip validation
              } else {
                this.signature == expected
              }
        result_map: |
          root.signature_valid = this.valid

    # Drop invalid signatures and return 401
    - mapping: |
        root = this
        root.status = if this.signature_valid == false { "401" } else { "200" }
        root.error = if this.signature_valid == false { "Invalid signature" } else { deleted() }

    # Log the webhook
    - log:
        level: INFO
        message: "Received webhook"
        fields_mapping: |
          root.source = this.webhook_source
          root.event = this.event.or(this.type).or("unknown")

output:
  switch:
    cases:
      # Invalid signature - respond with error
      - check: this.status == "401"
        output:
          sync_response: {}

      # Route based on webhook source or event type
      - check: this.webhook_source == "github"
        output:
          broker:
            outputs:
              - sync_response: {}
              - http_client:
                  url: ${GITHUB_EVENTS_URL}
                  verb: POST
                  headers:
                    Content-Type: application/json

      - check: this.webhook_source == "stripe"
        output:
          broker:
            outputs:
              - sync_response: {}
              - http_client:
                  url: ${STRIPE_EVENTS_URL}
                  verb: POST
                  headers:
                    Content-Type: application/json

      # Default: acknowledge and log
      - output:
          broker:
            outputs:
              - sync_response: {}
              - stdout:
                  codec: lines
