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.*. For exact selector fields, see the selector field reference below.

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, routing oriented)
    • tags? array of strings
    • aliases? map of alias name to JSONPath-like string into payload
  • status one of queued, processing, done, dead

Event handler config

The config object defines what the handler does once it matches an event. Its shape depends on the handler type:

  • notify handlers
    { "notify": { "email"?: string[], "internal"?: string[] } }
    See notify config fields.
  • httpCall handlers
    { "auth": AuthConfig, "defaultHeaders"?: HeaderMap, "steps": Step[], "exports"?: VariableExport[] }
    See httpCall config fields.

The config object is the part you edit in the UI or API when you define what the handler should send or call.

Event handler selector

The selector object decides when the handler should run for a given event. It sits alongside config at the top level of the handler and is stored as JSON.

  • version number, usually 1.
  • all? array of predicates with shape { path, operator, value? }.
  • When selector is missing or empty, the system defaults to a match-all selector { "version": 1 }.

For exact selector fields, see selector field reference and selector predicate fields .

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

{
  "notify": {
    "email": [
      "sales@example.com",
      "another-user@example.com"
    ],
    "internal": [
      "john@example.com",
      "smith@example.com"
    ]
  }
}

Selector: match any event

{
  "version": 1
}

Selector: only a specific chatbot (by appKey)

{
  "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.

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

httpCall: single step CRM intake

{
  "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

{
  "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
  • chatbot.widget.userMessage
  • chatbot.widget.sessionHistoryDeleted
  • chatbot.widget.sessionHistoryExported
  • chatbot.widget.fileUploaded

Event handler config field reference

config (notify handlers)

Applies when type is "notify". Shape in JSON:

{ "notify": { "email": ["user1"], "internal": ["user2"] } }
            
  • notify.email?
    type: string[]
    Optional.
    List of usernames in the organization that should receive email notifications for the event. Any value that is not a valid username in the organization is ignored.
  • notify.internal?
    type: string[]
    Optional.
    List of usernames in the organization that should receive in-app notifications. Used for internal alerts inside Liteed.

config (httpCall handlers)

Applies when type is "httpCall". Shape in JSON:

{ "auth": { "type": "none" }, "defaultHeaders": { "X-App": "{{event.meta.traits.appKey}}" }, "steps": [], "exports": [] }
            
  • auth
    type: AuthConfig
    Required.
    Controls how outbound HTTP requests are authenticated. Possible forms:
    • { "type": "none" } – no authentication.
    • { "type": "staticBearer", "token": "{{secret:crmApiKey}}" } – static bearer token, usually with secrets.
    • { "type": "basic", "username": "...", "password": "..." } – basic auth, values may use secrets or templates.
    • { "type": "apiKey", "name": "...", "value": "...", "location": "header" | "query" } – API key placed into header or query string.
    • { "type": "webhookKey", "baseUrl": "...", "userId": "...", "secret": "..." } – webhook key appended to baseUrl.
    • { "type": "oauth2", "tokenUrl": "...", "clientId": "{{secret:clientId}}", "clientSecret": "{{secret:clientSecret}}", "scopes": [...], "grantType": "client_credentials" | "password" | "refresh_token", "tokenRequestExtras"?: {...}, "tokenPlacement"?: { "in": "header" | "query", "name"?: "..." } } The dispatcher obtains an access token before running HTTP steps and reuses it for all steps in the same handler execution.
  • defaultHeaders?
    type: Record<string, string | SourceRef>
    Optional.
    Headers applied to all HTTP steps before per-step headers. Values can be plain strings with templates (for example "{{secret:crmApiKey}}" or "{{event.meta.traits.appKey}}") or SourceRef objects that read from event or step outputs.
  • steps
    type: Step[]
    Required.
    Ordered list of actions executed for the handler. Supported step kinds:
    • httpRequest: { "kind": "httpRequest", "id": "createLead", "baseUrl"?: "...", "path"?: "...", "method": "GET" | "POST" | "PUT" | "PATCH" | "DELETE", "headers"?: HeaderMap, "query"?: ParamMap, "body"?: JsonTemplate, "expectJson"?: boolean, "saveAs"?: string, "onStatus"?: { "retryOn"?: number[], "stopOn"?: number[], "maxRetries"?: number, "backoffMs"?: number } }
      • id: step identifier used in later SourceRef lookups.
      • baseUrl: optional override for the request base URL.
      • path: optional URL path; may contain templates and secrets.
      • headers: additional headers merged over defaultHeaders.
      • query: ParamMap resolved into query string parameters.
      • body: JsonTemplate resolved into JSON request payload.
      • expectJson: default true; if false, raw body or status text is used.
      • saveAs: key under steps[id] used for later SourceRef lookups (defaults to id).
      • onStatus.retryOn: statuses that trigger a retry at dispatcher level.
      • onStatus.stopOn: statuses that stop further steps but do not fail the handler.
    • assign: { "kind": "assign", "id": "noteText", "saveAs": "note", "value": SourceRef } Resolves value once and stores it under steps[saveAs] (or steps[id] when saveAs is omitted).
  • exports?
    type: VariableExport[]
    Optional.
    Makes selected values from event or steps available as named exports for other systems. Each export has the form { "name": "externalId", "from": SourceRef }. The dispatcher resolves from after all steps complete.

selector object

The selector controls when a handler runs for a given event. If you omit selector when creating or updating a handler, the system uses a match-all selector { "version": 1 }.

  • version
    type: number
    Required.
    Version of the selector DSL. Currently 1. Used to evolve the selector language in the future.
  • all?
    type: SelectorPredicate[]
    Optional.
    List of predicates that must all be satisfied (logical AND). When all is missing or empty, the selector matches all events for the handler’s eventName.

selector predicate fields

Each item in selector.all has the shape { path, operator, value? }:

  • path
    type: string
    Required.
    Dot path into the event object. Common prefixes:
    • meta.traits.* – stable routing attributes (for example meta.traits.appKey).
    • meta.tags – tags array used with inList.
    • payload.* – business payload fields.
  • operator
    type: "equals" | "notEquals" | "inList" | "matchesRegex" | "exists" | "lessThan" | "greaterThan"
    Required.
    See the operator list above for semantics.
  • value?
    type: any JSON value
    Optional.
    Required for all operators except exists. For inList, use an array of possible values.