Chat node type: chat
A two-phase Human-in-the-Loop node that sends a message to a designated actor via the BizFirst WorkDesk inbox, suspends the workflow, and resumes when the actor replies. The reply is classified by IChatReplyParser and routed to approved, rejected, or success depending on its content.
When to Use
- Expense approval workflows: Send the expense details to a finance manager. The workflow suspends until the manager replies. Downstream nodes receive the structured decision and the free-text reason.
- Exception handling requiring human judgment: When an automated step fails or hits an edge case, route execution to a Chat node targeting an operations analyst. They review the context and reply, resuming the workflow on the correct branch.
- Content review before publication: A content creation workflow pauses at the Chat node to ask a legal reviewer to sign off. Approval routes to the publish step; rejection routes to the revision loop.
- Fraud alert human review: A fraud detection sub-workflow fires a Chat node to a risk analyst with transaction details. The analyst replies from their WorkDesk inbox and the original workflow resumes with the human verdict attached.
- Change management approvals: Infrastructure change requests pause at the Chat node to collect a CAB (Change Advisory Board) member's explicit sign-off before any production changes are applied.
waiting. The workflow instance is durably persisted. When an actor replies through the WorkDesk UI, the engine resumes execution at this node with the reply data in inputData["approvalDecision"] and routes to approved, rejected, or success.
Configuration
| Field | Required | Description |
|---|---|---|
message |
Required | The message text displayed to the actor in their inbox. Supports BizFirst template substitution using {{variableName}} syntax. Example: "Hi {{firstName}}, please review order {{orderId}} and reply APPROVE or DENY." |
actors |
Optional* | Comma-separated list of actor IDs who will receive the chat inbox item. Takes priority over user_id and channel. Example: "user:jane.doe,user:john.smith". At least one of actors, user_id, or channel is required. |
user_id |
Optional* | Single target user identifier. Format: user:email@company.com. Used when actors is not set. At least one of actors, user_id, or channel is required. |
channel |
Optional* | Target channel identifier. Used as fallback when both actors and user_id are absent. Example: "payroll-approvals". At least one of actors, user_id, or channel is required. |
context |
Optional | Additional context text attached to the pending inbox item. Displayed below the message for the actor. Useful for supplementary reference data without cluttering the main message. |
timeout_minutes |
Optional | Integer minutes before the waiting session expires. When the session expires the EngageSession onTimeout policy fires (defaults to "rejected"), resuming the workflow. Set to 0 or omit for unlimited wait. Must be >= 0. |
actors (comma-separated list) → user_id → channel. If all three are absent, the node routes to the error port immediately in Phase 1 without sending any message.
Sample Configuration
{
"message": "Hi {{requesterName}}, your expense claim for {{currency}}{{amount}} requires manager sign-off. Reply APPROVE to proceed or DENY to return for revision.",
"actors": "user:finance.manager@company.com",
"context": "Claim ID: {{claimId}} | Submitted: {{submittedAt}} | Category: {{expenseCategory}}",
"timeout_minutes": 1440
}
The workflow suspends for up to 24 hours (1440 minutes). The {{…}} tokens are resolved at runtime from the workflow data context before the message is sent to the actor's inbox.
Validation Errors
| Error Message | Cause & Fix |
|---|---|
message is required |
The message field is empty or missing. Provide a non-blank string. |
At least one of actors, user_id, or channel is required |
No actor target is configured. Set actors, user_id, or channel. |
timeout_minutes must be >= 0 |
timeout_minutes was set to a negative integer. Use 0 for unlimited or any positive integer. |
No actors configured for chat node. Set actors, user_id, or channel. |
Runtime validation found all actor fields empty after config is loaded. A {{variable}} expression may have resolved to an empty string at runtime. |
Output
Waiting Port (waiting)
Fired during Phase 1. The workflow is suspended and durably persisted. Output contains session tracking data for observability.
| Field | Type | Description |
|---|---|---|
engage.session_id | integer | Internal EngageSession ID created by the ProcessEngage infrastructure. Used by the resume endpoint to look up this suspended execution. |
engage.actor_count | integer | Number of actors who received an InboxItem. Useful for audit logging in downstream observer nodes. |
Approved Port (approved)
Fired during Phase 2 when IChatReplyParser classifies the reply as an approval. Default approval keyword: "approved" (exact decision value from the resume envelope).
| Field | Type | Description |
|---|---|---|
approvalDecision | object | Full approval decision envelope from the ProcessEngage resume callback, containing decision, actorId, decidedAt, and additionalData. |
reply_text | string | Raw reply text typed by the actor (extracted from additionalData.reply_text). |
decision | string | "approved" |
Rejected Port (rejected)
Fired during Phase 2 when the reply is classified as a rejection, or when the session expires and the onTimeout policy resolves to "rejected".
| Field | Type | Description |
|---|---|---|
approvalDecision | object | Full approval decision envelope. |
reply_text | string | Raw reply text typed by the actor, or empty string on timeout. |
decision | string | "rejected" |
Success Port (success)
Fallback port for Phase 2. Fires when a reply is received but does not match the approved or rejected classification — for example a free-text comment, an abstention, or an unrecognised keyword. Also fires when the IChatReplyParser is not registered and the raw decision is "unknown".
| Field | Type | Description |
|---|---|---|
approvalDecision | object | Full approval decision envelope. |
reply_text | string | Raw reply text typed by the actor. |
decision | string | "unknown" or "abstained" |
Error Port (error)
Fires during Phase 1 if configuration is invalid (missing message or no actors) or if EngageSession creation fails at the infrastructure level. Session timeout routing is controlled by the EngageSession onTimeout policy, not the error port.
| Field | Type | Description |
|---|---|---|
error | string | Human-readable description of the configuration or infrastructure error. |
Sample Output
// Phase 1 — Waiting port
{
"engage.session_id": 4721,
"engage.actor_count": 2
}
// Phase 2 — Approved port
{
"approvalDecision": {
"decision": "approved",
"actorId": "user:jane.doe@company.com",
"decidedAt": "2026-03-15T14:32:00Z",
"additionalData": {
"reply_text": "APPROVE — all figures look correct, proceed."
}
},
"reply_text": "APPROVE — all figures look correct, proceed.",
"decision": "approved"
}
// Phase 2 — Rejected port
{
"approvalDecision": {
"decision": "rejected",
"actorId": "user:jane.doe@company.com",
"decidedAt": "2026-03-15T14:45:00Z",
"additionalData": {
"reply_text": "DENY — invoiced amount does not match PO line."
}
},
"reply_text": "DENY — invoiced amount does not match PO line.",
"decision": "rejected"
}
Expression Reference
On Phase 2 ports, reference output fields in downstream nodes using the workflow node key:
| Expression | Value |
|---|---|
{{ $output.chatNode.decision }} | Routing decision string: "approved", "rejected", or "unknown" |
{{ $output.chatNode.reply_text }} | Raw reply text typed by the actor |
{{ $output.chatNode.approvalDecision.actorId }} | Actor ID of the person who replied |
{{ $output.chatNode.approvalDecision.decidedAt }} | ISO 8601 timestamp of the reply |
{{ $output.chatNode["engage.session_id"] }} | EngageSession ID (available on the waiting port) |
{{ $output.chatNode["engage.actor_count"] }} | Number of actors notified (available on the waiting port) |
chatNode with the actual key assigned to this node in the workflow designer (set in the node properties panel). Expression paths are case-sensitive.
Node Policies & GuardRails
- Always set a timeout in production: Set
timeout_minutesso workflows cannot hang indefinitely. Connect theerrorport (which fires after timeout escalation) to a notification node or escalation sub-workflow. - Durable suspension: The workflow state is persisted to the ProcessEngine database at the moment the
waitingport fires. A server restart or crash will not lose the pending item — the actor can reply later and the resume endpoint will correctly locate and resume the execution. - Authoritative audit trail: Every reply is stored in the ProcessEngage EngageSession record with actor ID, timestamp, and full reply text. EngageSession records must never be altered or deleted; they are the system-of-record for approval audit logs.
- Actor identity verification: The resume endpoint verifies that the replying actor is assigned to the EngageSession. Replies from non-assigned actors are rejected at the infrastructure level before the workflow sees them.
- FirstResponse strategy: When multiple actors are listed in
actors, whichever responds first resumes the workflow. The remaining open InboxItems are automatically closed by the ProcessEngage infrastructure. - SignalR real-time notification: A HIL event is emitted via
IWorkflowEventProducerso FlowStudio and WorkDesk receive instant push notification. Notification failures are swallowed and logged as warnings — the suspension itself is never rolled back. - No form on a Chat node: The Chat node sets
FormSupported = falsein its HIL event. If structured field collection is needed alongside a message, use the Form node instead. - Reply parser fallback: If
IChatReplyParseris not registered, a reply with an unknowndecisionvalue routes tosuccess. Always register the reply parser in production to ensure deterministic routing.
Examples
Example 1 — Payroll Exception Approval
A payroll processing workflow detects an anomaly (employee hours exceed the 60-hour weekly cap). It pauses and asks the payroll manager to confirm before processing the run.
{
"message": "Payroll anomaly detected for {{employeeName}} (ID: {{employeeId}}). Hours reported: {{hoursWorked}} hrs. This exceeds the 60-hour weekly cap. Reply APPROVE to process as-is, or DENY to hold for manual correction.",
"actors": "user:payroll.manager@company.com",
"context": "Pay Period: {{payPeriod}} | Department: {{department}} | Baseline Hours: {{normalHours}}",
"timeout_minutes": 480
}
The approved port connects to the payroll processing step. The rejected port connects to a correction task node that reassigns the timesheet. The 8-hour timeout ensures the workflow does not miss the payroll run window — if the manager does not reply, the session expires and routes through the error port to an escalation notification.
Example 2 — Multi-Actor Fraud Review
A fraud detection sub-workflow flags a high-value transaction and sends the alert to two risk analysts simultaneously. The first to respond wins via the FirstResponse strategy.
{
"message": "FRAUD ALERT — Transaction {{transactionId}} for {{currency}}{{amount}} to account {{recipientAccount}} flagged by risk model (score: {{riskScore}}/100). Reply CLEAR to release funds or HOLD to freeze the transaction.",
"actors": "user:risk.analyst.a@company.com,user:risk.analyst.b@company.com",
"context": "Customer: {{customerName}} | Account Age: {{accountAgeDays}} days | Transaction Location: {{transactionLocation}} | Previous Flags: {{previousFlagCount}}",
"timeout_minutes": 60
}
Both analysts receive an InboxItem simultaneously. The first reply wins; remaining items are closed. The 60-minute timeout connects to a fraud freeze escalation sub-workflow via the error port — ensuring that if neither analyst responds, the transaction is automatically frozen as a safety measure.
Example 3 — Infrastructure Change Approval (CAB Gate)
A deployment pipeline pauses at a Chat node to collect explicit CAB Chair sign-off before applying schema migrations to the production database.
{
"message": "CAB APPROVAL REQUIRED — Change {{changeId}}: {{changeTitle}}. Planned execution window: {{executionWindow}}. This change will modify {{affectedTableCount}} database tables in {{environmentName}}. Reply APPROVE to allow deployment, or ROLLBACK to abort and revert the pipeline.",
"user_id": "user:cab.chair@company.com",
"context": "Risk Rating: {{riskRating}} | Rollback Plan: {{rollbackPlan}} | Requested By: {{requestedBy}} | Peer Review: {{peerReviewUrl}}",
"timeout_minutes": 120
}
Uses user_id (single actor) since the CAB Chair is the designated approver. The approved port proceeds to apply migrations. The rejected port triggers the pipeline abort sequence. The success port (non-matching reply) connects to a clarification step that sends a follow-up message and waits again. The 2-hour timeout prevents the deployment window from being missed.