{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/basecamp/bc3-api/schemas/basecamp/webhook-payload.json",
  "title": "Basecamp Webhook Payload",
  "description": "Represents the JSON payload delivered by Basecamp to a registered webhook endpoint when a subscribed event occurs within a project. Every payload includes a unique event ID, the event kind, a timestamp, the full recording object that triggered the event, the person who triggered it, and optional event-specific details.",
  "type": "object",
  "required": ["id", "kind", "created_at", "recording", "creator"],
  "properties": {
    "id": {
      "type": "integer",
      "description": "Unique identifier for this webhook event delivery"
    },
    "kind": {
      "type": "string",
      "description": "Event type descriptor identifying the resource type and action, formatted as {resource}_{action} (e.g., message_created, todo_completed, comment_updated)",
      "enum": [
        "message_created",
        "message_updated",
        "message_archived",
        "message_trashed",
        "message_subscribers_changed",
        "message_publicized",
        "todo_created",
        "todo_updated",
        "todo_completed",
        "todo_uncompleted",
        "todo_archived",
        "todo_trashed",
        "todo_subscribers_changed",
        "todolist_created",
        "todolist_updated",
        "todolist_archived",
        "todolist_trashed",
        "document_created",
        "document_updated",
        "document_archived",
        "document_trashed",
        "comment_created",
        "comment_updated",
        "comment_trashed",
        "card_created",
        "card_updated",
        "card_archived",
        "card_trashed",
        "schedule_entry_created",
        "schedule_entry_updated",
        "schedule_entry_archived",
        "schedule_entry_trashed",
        "upload_created",
        "upload_updated",
        "upload_archived",
        "upload_trashed",
        "question_answer_created",
        "question_answer_updated",
        "question_paused",
        "question_resumed",
        "inbox_forward_created",
        "client_forward_created",
        "client_correspondence_created"
      ]
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 UTC timestamp when the triggering event occurred in Basecamp"
    },
    "recording": {
      "$ref": "#/$defs/Recording",
      "description": "Full JSON representation of the Basecamp resource that triggered the event"
    },
    "creator": {
      "$ref": "#/$defs/Person",
      "description": "The Basecamp user whose action triggered the event"
    },
    "details": {
      "type": "object",
      "description": "Event-specific contextual metadata. For copy and move events includes a copy object with the duplicated item reference. Contents vary by event kind.",
      "additionalProperties": true,
      "properties": {
        "copy": {
          "$ref": "#/$defs/Recording",
          "description": "Reference to the original recording when this event was triggered by a copy or move operation"
        }
      }
    }
  },
  "$defs": {
    "Person": {
      "type": "object",
      "description": "A Basecamp user profile reference included in webhook payloads",
      "required": ["id", "name"],
      "properties": {
        "id": {
          "type": "integer",
          "description": "Unique identifier for the person"
        },
        "attachable_sgid": {
          "type": "string",
          "description": "Signed global ID used to reference this person as an attachment"
        },
        "name": {
          "type": "string",
          "description": "Full display name of the person"
        },
        "email_address": {
          "type": "string",
          "format": "email",
          "description": "Primary email address of the person"
        },
        "personable_type": {
          "type": "string",
          "description": "Personable type indicating user role",
          "enum": ["User", "Client"]
        },
        "title": {
          "type": "string",
          "description": "Job title of the person"
        },
        "bio": {
          "type": "string",
          "description": "Short biography text"
        },
        "location": {
          "type": "string",
          "description": "Location string"
        },
        "avatar_url": {
          "type": "string",
          "format": "uri",
          "description": "URL to the person's avatar image"
        },
        "created_at": {
          "type": "string",
          "format": "date-time",
          "description": "Timestamp when the person's account was created"
        },
        "updated_at": {
          "type": "string",
          "format": "date-time",
          "description": "Timestamp when the person's profile was last updated"
        },
        "admin": {
          "type": "boolean",
          "description": "Whether the person is an account administrator"
        },
        "owner": {
          "type": "boolean",
          "description": "Whether the person is the account owner"
        },
        "client": {
          "type": "boolean",
          "description": "Whether the person is a client user"
        },
        "employee": {
          "type": "boolean",
          "description": "Whether the person is an internal employee"
        },
        "time_zone": {
          "type": "string",
          "description": "IANA time zone name for the person's local time zone"
        }
      }
    },
    "Bucket": {
      "type": "object",
      "description": "Reference to the Basecamp project containing the recording",
      "required": ["id", "name", "type"],
      "properties": {
        "id": {
          "type": "integer",
          "description": "Project ID"
        },
        "name": {
          "type": "string",
          "description": "Project name"
        },
        "type": {
          "type": "string",
          "description": "Always 'Project' for project buckets",
          "const": "Project"
        }
      }
    },
    "Recording": {
      "type": "object",
      "description": "A Basecamp content resource such as a message, to-do, document, comment, or card. The exact fields present depend on the resource type indicated by the type field.",
      "required": ["id", "type"],
      "properties": {
        "id": {
          "type": "integer",
          "description": "Unique identifier for this recording"
        },
        "status": {
          "type": "string",
          "description": "Lifecycle status of the recording",
          "enum": ["active", "archived", "trashed"]
        },
        "visible_to_clients": {
          "type": "boolean",
          "description": "Whether the recording is visible to client users"
        },
        "created_at": {
          "type": "string",
          "format": "date-time",
          "description": "Timestamp when the recording was created"
        },
        "updated_at": {
          "type": "string",
          "format": "date-time",
          "description": "Timestamp when the recording was last updated"
        },
        "title": {
          "type": "string",
          "description": "Title or summary of the recording"
        },
        "inherits_status": {
          "type": "boolean",
          "description": "Whether this recording inherits its status from a parent recording"
        },
        "type": {
          "type": "string",
          "description": "Recording type identifying the kind of Basecamp content",
          "enum": [
            "Message",
            "Todo",
            "Todolist",
            "Document",
            "Comment",
            "Kanban::Card",
            "Schedule::Entry",
            "Upload",
            "Vault",
            "Question::Answer",
            "Client::Forward",
            "Client::Correspondence",
            "Inbox::Forward"
          ]
        },
        "url": {
          "type": "string",
          "format": "uri",
          "description": "API URL to retrieve this recording"
        },
        "app_url": {
          "type": "string",
          "format": "uri",
          "description": "Web URL to open this recording in the Basecamp application"
        },
        "bookmark_url": {
          "type": "string",
          "format": "uri",
          "description": "API URL to bookmark this recording"
        },
        "bucket": {
          "$ref": "#/$defs/Bucket"
        },
        "creator": {
          "$ref": "#/$defs/Person"
        },
        "content": {
          "type": "string",
          "description": "HTML-formatted body content for messages, documents, and comments"
        },
        "subject": {
          "type": "string",
          "description": "Subject line for message recordings"
        },
        "completed": {
          "type": "boolean",
          "description": "Whether the to-do has been completed (Todo type only)"
        },
        "due_on": {
          "type": ["string", "null"],
          "format": "date",
          "description": "Due date in ISO 8601 date format (Todo type only)"
        },
        "starts_on": {
          "type": ["string", "null"],
          "format": "date",
          "description": "Start date in ISO 8601 date format (Todo type only)"
        },
        "assignees": {
          "type": "array",
          "description": "People assigned to this to-do (Todo type only)",
          "items": {
            "$ref": "#/$defs/Person"
          }
        },
        "starts_at": {
          "type": "string",
          "format": "date-time",
          "description": "Start time for schedule entries (Schedule::Entry type only)"
        },
        "ends_at": {
          "type": "string",
          "format": "date-time",
          "description": "End time for schedule entries (Schedule::Entry type only)"
        },
        "all_day": {
          "type": "boolean",
          "description": "Whether the schedule entry spans the entire day (Schedule::Entry type only)"
        },
        "filename": {
          "type": "string",
          "description": "Original filename of the uploaded file (Upload type only)"
        },
        "content_type": {
          "type": "string",
          "description": "MIME type of the uploaded file (Upload type only)"
        },
        "byte_size": {
          "type": "integer",
          "description": "File size in bytes (Upload type only)"
        },
        "download_url": {
          "type": "string",
          "format": "uri",
          "description": "Direct download URL for the uploaded file (Upload type only)"
        },
        "comments_count": {
          "type": "integer",
          "description": "Number of comments on this recording"
        },
        "boosts_count": {
          "type": "integer",
          "description": "Number of boosts (reactions) on this recording"
        },
        "position": {
          "type": "integer",
          "description": "Display order position within the parent container"
        },
        "parent": {
          "$ref": "#/$defs/Recording",
          "description": "Reference to the parent recording (e.g., the message board containing a message, or the to-do list containing a to-do)"
        }
      }
    }
  }
}
