{
  "asyncapi": "3.0.0",
  "info": {
    "title": "CleanedWeb signed property webhooks",
    "version": "1.0.0",
    "description": "At-least-once signed delivery of finalized maintained property events to a customer HTTPS endpoint."
  },
  "defaultContentType": "application/json",
  "channels": {
    "customerWebhook": {
      "address": "{customerWebhookUrl}",
      "description": "Customer-controlled HTTPS destination configured and verified in the CleanedWeb workspace.",
      "parameters": {
        "customerWebhookUrl": {
          "description": "Verified public HTTPS endpoint. Private, loopback, link-local, and unsafe resolved destinations are rejected."
        }
      },
      "messages": {
        "propertyEvent": {"$ref": "#/components/messages/PropertyEvent"},
        "testEvent": {"$ref": "#/components/messages/TestEvent"}
      }
    }
  },
  "operations": {
    "sendPropertyEvent": {
      "action": "send",
      "summary": "Deliver one finalized property event.",
      "description": "Delivery is at least once. Verify the raw body and deduplicate webhook-id before processing.",
      "channel": {"$ref": "#/channels/customerWebhook"},
      "messages": [{"$ref": "#/channels/customerWebhook/messages/propertyEvent"}],
      "x-cleanedweb-delivery": {
        "ordering": "not-guaranteed-across-properties-or-retries",
        "maximum_attempts": 10,
        "retry_delays_seconds": [5, 300, 1800, 7200, 18000, 36000, 50400, 72000, 86400],
        "terminal_statuses": [410]
      }
    },
    "sendTestEvent": {
      "action": "send",
      "summary": "Verify a destination with a zero-record, zero-Unit event.",
      "channel": {"$ref": "#/channels/customerWebhook"},
      "messages": [{"$ref": "#/channels/customerWebhook/messages/testEvent"}]
    }
  },
  "components": {
    "messages": {
      "PropertyEvent": {
        "name": "property-event-v1",
        "title": "Finalized property event",
        "headers": {"$ref": "#/components/schemas/StandardWebhookHeaders"},
        "payload": {
          "schemaFormat": "application/schema+json;version=draft-2020-12",
          "schema": {"$ref": "https://cleanedweb.com/schemas/property-event-v1.json"}
        },
        "examples": [
          {
            "name": "priceChanged",
            "summary": "A finalized observed price change.",
            "payload": {"$ref": "https://cleanedweb.com/examples/webhook-event.json"}
          }
        ]
      },
      "TestEvent": {
        "name": "webhook.test",
        "title": "Destination verification event",
        "headers": {"$ref": "#/components/schemas/StandardWebhookHeaders"},
        "payload": {
          "type": "object",
          "additionalProperties": false,
          "required": ["id", "type", "api_version", "timestamp", "data"],
          "properties": {
            "id": {"type": "string"},
            "type": {"const": "webhook.test"},
            "api_version": {"type": "string"},
            "timestamp": {"type": "string", "format": "date-time"},
            "data": {
              "type": "object",
              "required": ["workspace_id", "saved_search_id", "record_count", "units"],
              "properties": {
                "workspace_id": {"type": "string"},
                "saved_search_id": {"type": "string"},
                "record_count": {"const": 0},
                "units": {"const": 0}
              }
            }
          }
        }
      }
    },
    "schemas": {
      "StandardWebhookHeaders": {
        "type": "object",
        "additionalProperties": true,
        "required": ["webhook-id", "webhook-timestamp", "webhook-signature"],
        "properties": {
          "webhook-id": {"type": "string", "minLength": 1},
          "webhook-timestamp": {"type": "string", "pattern": "^[0-9]+$"},
          "webhook-signature": {"type": "string", "minLength": 1}
        }
      }
    }
  }
}
