Skip to content

[Feature]: Define rich, typed webhook payloads for all existing events #260

Description

@PascaleBeier

Summary

Replace the current inconsistent webhook data maps with documented, event-specific payloads for all 17 existing webhook events. In particular, make goal.converted actionable by including the triggering custom-event properties or a privacy-bounded pageview snapshot.

This is a versioned contract cleanup: the envelope remains stable, while deliveries created under the release containing this change use the new nested data shape without legacy flat aliases.

Problem

Webhook payloads currently expose uneven, producer-specific fields. For example, goal.converted identifies the matched goal but deliberately omits the metadata from the custom event or pageview that triggered the conversion. Receivers therefore need extra API calls—or cannot implement the workflow at all—to route conversions, synchronize lifecycle state, or act on changes.

The payload fields are also represented in OpenAPI as an unrestricted object, so integrations cannot discover or validate the event-specific contract.

Who Benefits

  • Self-hosted operators
  • Managed Cloud operators
  • Developers integrating HitKeep with CRMs, automation systems, alerting, and data pipelines
  • API consumers that need a stable, machine-readable webhook contract

Proposed Solution

Keep the existing envelope fields:

  • api_version
  • id
  • delivery_id
  • type
  • created_at
  • data

Make data.actor required but nullable. Human actions use { "id": "<uuid>" }; system and background events use null.

Define reusable snapshots:

  • Site: id, team_id, domain, data_retention_days, created_at
  • Goal: id, name, type, value, created_at
  • Team: id, name, logo_url, created_at
  • User/member/actor: IDs only—no email, display name, or membership-role snapshot
  • Import: id, provider, status, rows_imported, created_at, optional started_at, finished_at, and the existing user-visible error
  • Changes: field-specific { "from": ..., "to": ... } entries; no arbitrary change keys

Event payloads

Event Required data
site.created site, actor
site.updated latest site, changes for domain or data_retention_days, actor
site.deleted final pre-delete site, actor
goal.created site reference, goal, actor
goal.updated site reference, latest goal, changes for name, type, or value, actor
goal.deleted site reference, final pre-delete goal, actor
goal.converted site reference, goal, triggering source, actor: null
import.completed site reference, completed import, actor: null
import.failed site reference, failed import including its user-visible error, actor: null
webhook.test webhook id, name, scope; nullable site reference; actor
system.user.updated user ID, changes.instance_role, actor
system.user.deleted user ID, actor
team.created team, actor
team.updated latest team, changes for name or logo_url, actor
team.archived final team, actor
team.member.added team ID, member user ID, cause invite_accepted or sso_provisioned, actor
team.member.removed team ID, member user ID, cause removed or left, actor

Goal conversion source

Model goal.converted.data.source as a discriminated union:

  • Custom event: type: "event", id, session_id, occurred_at, name, and properties (always an object)
  • Pageview: type: "pageview", id, session_id, occurred_at, path, optional hostname, referrer, and optional UTM fields

Explicitly exclude user-agent, device/screen dimensions, language, geo/network fields, QR identifiers, and internal tracker fields. Set the conversion envelope's created_at from the triggering source timestamp.

User Workflow

  1. An operator subscribes an instance or site webhook to the relevant event types.
  2. HitKeep delivers a typed payload whose data schema is selected by api_version and type.
  3. The receiver validates the documented schema and acts without additional lookups for the included product data.
  4. Conversion receivers can route on custom-event properties or approved pageview attribution without receiving the complete raw hit.

Implementation Notes

  • Introduce typed payload structs/builders in the webhook package and replace producer-owned map[string]any construction.
  • Validate each event type against its expected payload before persistence.
  • Add occurrence time to the internal webhook event interface and propagate it through batch enqueueing.
  • Capture snapshots before destructive operations and before/after values for updates.
  • Preserve transactional final delivery for site deletion.
  • Pass the approved source fields directly from in-memory ingest records while preserving subscriber short-circuiting, batch emission, stable conversion IDs, retry safety, and exact stored delivery bytes.
  • Model WebhookEventPayload in runtime OpenAPI as a oneOf over all event-specific schemas using constant event types.
  • Synchronize the adjacent docs OpenAPI and webhook/goal guides.

Success Criteria

  • All 17 existing event types have typed, documented payload schemas.
  • data.actor is always present and is either an ID-only object or null.
  • Update events include the latest snapshot and typed from/to changes.
  • Delete events include the final pre-delete snapshot.
  • goal.converted includes custom properties or the approved pageview context.
  • Disallowed tracking, identity, secret, destination, and internal-error fields remain absent.
  • Conversion created_at uses the source occurrence time.
  • Existing signing, event/delivery IDs, deduplication, batching, retries, and site-deletion outbox behavior remain unchanged.
  • Existing queued deliveries retry with their original api_version and exact stored body; no migration rewrites payload JSON.
  • Runtime and docs OpenAPI describe every catalog event with an event-specific schema.
  • The webhook and goal-conversion guides document examples, privacy boundaries, and receiver migration.

Test Plan

  • Table-driven serialization coverage for all 17 schemas, including required/null behavior.
  • Producer tests for actor propagation, snapshot/diff behavior, membership causes, import errors, and ID-only people.
  • Ingest tests proving approved conversion metadata is present and excluded fields are absent.
  • Delivery tests for signing, identifiers, deduplication, retry immutability, batching, and site-deletion outbox behavior.
  • OpenAPI coverage ensuring every catalog event has a corresponding schema.
  • Run change-aware QA while iterating, PR-parity before review, and the exhaustive profile before release because this changes a public integration contract.

Constraints

  • No new event types, dashboard UI, MCP surface, takeout payload exposure, or subscription behavior.
  • No email, display name, raw user profile, membership-role snapshot, secret, destination URL, raw internal error, or unrestricted hit export.
  • Self-hosted and managed-cloud behavior must remain identical.
  • Do not add compatibility aliases inside the new data objects; receivers distinguish the contract through the release-line api_version.
  • No database migration or rewrite of historical/queued payloads.

Alternatives Considered

  • Enrich only goal.converted: rejected because all current event types share the same undocumented-contract problem.
  • Add more flat fields: rejected because it perpetuates inconsistent producer-owned payloads.
  • Add nested fields alongside legacy aliases: rejected in favor of a clean release-versioned contract.

Release Notes

Webhook deliveries now contain typed resource snapshots and change details. Goal conversion events include privacy-bounded metadata from the triggering custom event or pageview. Receivers should select the appropriate payload schema using api_version and type.

Docs Impact

Needed: runtime OpenAPI, adjacent docs OpenAPI, webhook integration guide, goal-conversion guide, and release notes. No screenshot or localization update is required.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions