Events and Event Handlers

This page is a deep technical guide to the Event Bus and Event Handlers in the Liteed Platform. For a UI walkthrough, see the Platform User Manual: Events.

Understanding events and event handlers

An Event is a recorded occurrence emitted by a product or service. It has a name, a business payload that carries data, and a processing status. Event Handlers react to events with the same name. A handler can notify people, call external systems, or both, depending on its type and configuration.

Event lifecycle

  1. Emit → a service records the event with name, payload, and optional meta.
  2. Select → the dispatcher finds enabled handlers for that name, then applies the selector.
  3. Execute → matching handlers run in priority order according to mode.
  4. Record → outcomes, retries, and exports are persisted for observability.

Meta envelope

Events may include a stable meta envelope that is purpose-built for routing. It decouples matching logic from the free-form business payload.

  • meta.version number for the envelope (start at 1).
  • meta.traits canonical attributes like appKey, origin, language.
  • meta.tags free-form labels for segmentation like ["vip","pilot"].
  • meta.aliases optional map to derive traits from payload without schema changes.

Selectors

A selector decides when a handler should run. Selectors use a small DSL with a version and a list of predicates combined with AND.

{ "version": 1, "all": [ { "path": "meta.traits.appKey", "operator": "equals", "value": "store-bot" } ] }

Operators

  • equals, notEquals
  • inList (value in array)
  • matchesRegex (ECMAScript syntax)
  • exists (path presence and not null)
  • lessThan, greaterThan (numeric or lexicographic where applicable)

Paths can reference meta.traits.*, meta.tags, or payload.*.

Handler types

  • notify → sends notifications.
  • httpCall → executes a step pipeline to call external systems.

Execution modes and priority

  • mode: fire-and-continue or stop-on-match.
  • priority: lower number runs earlier. Default is 100.

Reference

Event

  • name string, for example chatbot.widget.requestCallback
  • payload object
  • meta? object with:
    • version number, start at 1
    • traits? object (stable attributes)
    • tags? array of strings
    • aliases? map of alias name to JSONPath-like string into payload
  • status one of queued, processing, done, dead

Event Handler

  • eventName string
  • type one of notify, httpCall
  • description? short human text at top level
  • enabled boolean
  • mode? fire-and-continue or stop-on-match
  • priority? number, lower runs first
  • selector object:
    • version number, start at 1
    • all? array of predicates
    • predicate: { path, operator, value? }
  • config object:
    • notify: { notify: { email?: string[], internal?: string[] } }
    • httpCall:
      • auth one of none, staticBearer, basic, apiKey, webhookKey, oauth2
      • defaultHeaders? map of header → value or SourceRef
      • steps array of typed steps
      • exports? array of { name, from: SourceRef }

httpCall steps

  • httpRequest: { kind: "httpRequest", id, baseUrl, path, method, headers?, query?, body?, expectJson?, saveAs?, onStatus? }
  • assign: { kind: "assign", id, saveAs, value: SourceRef }

SourceRef can read from event, a const literal, or a previous step result, for example: { from: "event", path: "payload.customer.email" } or { from: "step", stepId: "createLead", path: "body.id" }.

Examples

Notification handler (minimal)

{
  "type": "notify",
  "description": "Notify sales on callback",
  "selector": {
    "version": 1,
    "all": [
      {
        "path": "meta.traits.appKey",
        "operator": "equals",
        "value": "store-bot"
      }
    ]
  },
  "config": {
    "notify": {
      "email": [
        "sales@example.com"
      ]
    }
  }
}

Selector: match any event for a name

{
  "version": 1
}

Selector: only a specific chatbot (by appKey)

{
  "version": 1,
  "all": [
    {
      "path": "meta.traits.appKey",
      "operator": "equals",
      "value": "store-bot"
    }
  ]
}

Selector: tags and segmentation

Use inList to target events labeled with specific tags like vip or pilot.

{
  "version": 1,
  "all": [
    {
      "path": "meta.tags",
      "operator": "inList",
      "value": [
        "vip",
        "pilot"
      ]
    }
  ]
}

httpCall: single step CRM intake

{
  "type": "httpCall",
  "description": "Forward lead to CRM",
  "selector": {
    "version": 1,
    "all": [
      {
        "path": "meta.traits.appKey",
        "operator": "equals",
        "value": "store-bot"
      },
      {
        "path": "payload.customer.phone",
        "operator": "exists"
      }
    ]
  },
  "config": {
    "auth": {
      "type": "staticBearer",
      "token": "{{secret:crmApiKey}}"
    },
    "defaultHeaders": {
      "Content-Type": "application/json",
      "X-Source": "liteed-platform",
      "X-App": "{{event.meta.traits.appKey}}"
    },
    "steps": [
      {
        "kind": "httpRequest",
        "id": "createLead",
        "baseUrl": "https://api.example.com",
        "method": "POST",
        "path": "/intake",
        "headers": {
          "Authorization": "Bearer {{secret:crmApiKey}}"
        },
        "body": {
          "name": "{{event.payload.customer.name}}",
          "email": "{{event.payload.customer.email}}",
          "phone": "{{event.payload.customer.phone}}",
          "source": "{{event.meta.traits.appKey}}"
        },
        "expectJson": true,
        "saveAs": "leadResp",
        "onStatus": {
          "retryOn": [
            429,
            500,
            502,
            503
          ],
          "maxRetries": 3,
          "backoffMs": 1200
        }
      }
    ],
    "exports": [
      {
        "name": "externalId",
        "from": {
          "from": "step",
          "stepId": "leadResp",
          "path": "body.id"
        }
      }
    ]
  }
}

httpCall: multi-step with OAuth2 and chaining

{
  "type": "httpCall",
  "description": "Create contact then attach note",
  "selector": {
    "version": 1,
    "all": [
      {
        "path": "meta.traits.appKey",
        "operator": "equals",
        "value": "support-bot"
      }
    ]
  },
  "config": {
    "auth": {
      "type": "oauth2",
      "grantType": "client_credentials",
      "tokenUrl": "https://auth.example.com/oauth/token",
      "clientId": "{{secret:clientId}}",
      "clientSecret": "{{secret:clientSecret}}",
      "scopes": [
        "contacts.write"
      ]
    },
    "steps": [
      {
        "kind": "httpRequest",
        "id": "createContact",
        "method": "POST",
        "baseUrl": "https://api.example.com",
        "path": "/contacts",
        "body": {
          "email": "{{event.payload.customer.email}}",
          "name": "{{event.payload.customer.name}}"
        },
        "expectJson": true,
        "saveAs": "contact"
      },
      {
        "kind": "assign",
        "id": "noteText",
        "saveAs": "note",
        "value": {
          "from": "const",
          "value": "Requested a live agent via chatbot."
        }
      },
      {
        "kind": "httpRequest",
        "id": "attachNote",
        "method": "POST",
        "baseUrl": "https://api.example.com",
        "path": "/contacts/{{steps.createContact.body.id}}/notes",
        "body": {
          "text": "{{steps.note}}"
        },
        "expectJson": true
      }
    ],
    "exports": [
      {
        "name": "contactId",
        "from": {
          "from": "step",
          "stepId": "createContact",
          "path": "body.id"
        }
      }
    ]
  }
}

Best practices

  • Route on meta.traits or meta.tags rather than on raw payload whenever possible.
  • Keep handlers small and focused; use multiple handlers with fire-and-continue for fan-out.
  • Use Secrets Vault placeholders; values are injected server side from the database.
  • Configure retries only for transient errors like 429 and 5xx.

User manual section

For hands-on instructions on creating, editing, enabling, and deleting handlers in the UI, see Platform User Manual: Events. This technical page focuses on definitions and configuration. The user manual page links back here when you need exact config details.

Supported event names

  • chatbot.widget.provideContacts
  • chatbot.widget.requestCallback
  • chatbot.widget.customFormSubmit
  • chatbot.widget.requestLiveAgent