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
- Emit → a service records the event with name, payload, and optional meta.
- Select → the dispatcher finds enabled handlers for that name, then applies the selector.
- Execute → matching handlers run in priority order according to mode.
- 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.versionnumber for the envelope (start at 1).meta.traitscanonical attributes likeappKey,origin,language.meta.tagsfree-form labels for segmentation like["vip","pilot"].meta.aliasesoptional 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,notEqualsinList(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-continueorstop-on-match. - priority: lower number runs earlier. Default is 100.
Reference
Event
namestring, for examplechatbot.widget.requestCallbackpayloadobjectmeta?object with:versionnumber, start at 1traits?object (stable attributes, routing oriented)tags?array of stringsaliases?map of alias name to JSONPath-like string into payload
statusone ofqueued,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.
versionnumber, usually1.all?array of predicates with shape{ path, operator, value? }.- When
selectoris 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.traitsormeta.tagsrather than on raw payload whenever possible. - Keep handlers small and focused; use multiple handlers with
fire-and-continuefor 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.provideContactschatbot.widget.requestCallbackchatbot.widget.customFormSubmitchatbot.widget.requestLiveAgentchatbot.widget.userMessagechatbot.widget.sessionHistoryDeletedchatbot.widget.sessionHistoryExportedchatbot.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 tobaseUrl.{ "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}}") orSourceRefobjects 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
SourceReflookups. - baseUrl: optional override for the request base URL.
- path: optional URL path; may contain templates and secrets.
- headers: additional headers merged over
defaultHeaders. - query:
ParamMapresolved into query string parameters. - body:
JsonTemplateresolved into JSON request payload. - expectJson: default
true; if false, raw body or status text is used. - saveAs: key under
steps[id]used for laterSourceReflookups (defaults toid). - onStatus.retryOn: statuses that trigger a retry at dispatcher level.
- onStatus.stopOn: statuses that stop further steps but do not fail the handler.
- id: step identifier used in later
assign:{ "kind": "assign", "id": "noteText", "saveAs": "note", "value": SourceRef }Resolvesvalueonce and stores it understeps[saveAs](orsteps[id]whensaveAsis 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 resolvesfromafter 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. Currently1. Used to evolve the selector language in the future. - all?
type:SelectorPredicate[]
Optional.
List of predicates that must all be satisfied (logical AND). Whenallis missing or empty, the selector matches all events for the handler’seventName.
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 examplemeta.traits.appKey).meta.tags– tags array used withinList.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 exceptexists. ForinList, use an array of possible values.