Escalation Framework
Tenant-configurable rules engine for bridging conversations to formal issue tracking and resolution.
Conversations surface problems. A guest reporting smoke, a patient flagging a medication concern, a customer disputing a charge -- these start as messages and escalate into formal issues that need tracking and resolution.
The Escalation Framework is a tenant-configurable rules engine built into Contextual Messaging. PlatformXe provides the infrastructure -- flag storage, condition evaluation, action execution. You define the rules -- what flag reasons exist, what keywords auto-detect, what actions fire when conditions are met.
PlatformXe does not decide what constitutes an escalation. It evaluates the rules you wrote.
How It Works
Message flagged (or keyword detected)
|
v
Load your channel's escalation config
|
v
Evaluate your rules (JSON Logic conditions)
|
v
Execute matched actions:
- Create issue in PlatformXe
- Fire webhook to your system
- Post system message to thread
- Close the thread
Core Concepts
Flag Reasons
You define the flag reasons that make sense for your domain. PlatformXe stores and transmits them but assigns no meaning.
A hospitality platform might define:
SAFETY-- fire, flooding, injuryDISPUTE-- payment or service disagreementCOMPLAINT-- cleanliness, noise, amenities
A healthcare platform might define:
CLINICAL_CONCERN-- adverse reaction, missed doseBILLING_DISPUTE-- incorrect chargeACCESS_ISSUE-- portal or scheduling problem
Escalation Rules
A rule defines: when a condition is met, what actions to execute.
Each rule has:
- Trigger -- what fires the rule (
PARTICIPANT_FLAG,KEYWORD_MATCH, orADMIN_ESCALATE) - Conditions -- a JSON Logic expression evaluated against the escalation context
- Actions -- ordered list of actions to execute when conditions are met
- Priority -- evaluation order (lower number = higher priority)
- Cooldown -- minimum minutes between escalations per thread per rule
Auto-Detection
Keyword patterns that automatically flag messages without human action. Configure patterns per channel -- when a message matches, it is either auto-flagged (triggering full escalation) or a suggestion is returned to the recipient.
Configuration
Set Escalation Config on a Channel
PUT /api/v1/threads/channels/:channelId/escalation
{
"flagReasons": [
{ "code": "SAFETY", "label": "Safety concern", "severity": "HIGH" },
{ "code": "DISPUTE", "label": "Payment dispute", "severity": "MEDIUM" },
{ "code": "COMPLAINT", "label": "General complaint", "severity": "LOW" }
],
"autoDetection": [
{
"patterns": ["fire", "flooding", "gas leak", "smoke", "injury"],
"flagReason": "SAFETY",
"autoFlag": true,
"notifyRoles": ["PLATFORM"]
},
{
"patterns": ["refund", "overcharged", "wrong amount"],
"flagReason": "DISPUTE",
"autoFlag": false,
"suggestFlag": true
}
],
"rules": [
{
"id": "rule-safety-001",
"name": "Safety auto-escalation",
"trigger": "PARTICIPANT_FLAG",
"conditions": {
"in": [{ "var": "flag.reason" }, ["SAFETY"]]
},
"actions": [
{
"type": "CREATE_ISSUE",
"config": {
"title": "SAFETY: {{thread.subject}}",
"priority": "URGENT",
"tags": ["safety", "escalated"]
}
},
{
"type": "NOTIFY_PARTICIPANTS",
"config": {
"systemMessage": "A safety concern has been reported. Our team has been notified.",
"roles": ["ALL"]
}
},
{
"type": "WEBHOOK",
"config": {}
}
],
"priority": 1,
"isActive": true,
"cooldownMinutes": 60
}
]
}
Get Escalation Config
GET /api/v1/threads/channels/:channelId/escalation
Flagging Messages
Participant Flags a Message
Any participant in the thread can flag a message with a reason from the channel's configured flagReasons.
POST /api/v1/threads/:threadId/messages/:messageId/flag
{
"reason": "SAFETY",
"note": "Guest reports smoke in the unit",
"flaggedByExternalId": "usr-abc",
"flaggedByRole": "GUEST"
}
Response (201):
{
"success": true,
"data": {
"flag": {
"id": "flg-001",
"reason": "SAFETY",
"status": "ESCALATED",
"createdAt": "2026-04-12T19:00:00.000Z"
},
"escalations": {
"rulesEvaluated": 1,
"rulesMatched": 1,
"escalations": [
{
"ruleId": "rule-safety-001",
"ruleName": "Safety auto-escalation",
"actions": [
{ "type": "CREATE_ISSUE", "status": "success", "resultId": "iss_abc123" },
{ "type": "NOTIFY_PARTICIPANTS", "status": "success" },
{ "type": "WEBHOOK", "status": "success" }
]
}
]
}
}
}
The response tells you exactly which rules matched and what actions were taken. Your application can use this to show the user what happened.
Admin Escalates a Thread
Platform admins can escalate an entire thread, not just a single message.
POST /api/v1/threads/:threadId/escalate
{
"reason": "DISPUTE",
"note": "Guest and host cannot agree on refund",
"escalatedByExternalId": "admin-001",
"escalatedByRole": "PLATFORM"
}
Review a Flag
Admins review flags to confirm or dismiss them.
PATCH /api/v1/threads/flags/:flagId
{
"status": "REVIEWED",
"reviewedByExternalId": "admin-001",
"note": "Confirmed -- maintenance dispatched"
}
Status options: REVIEWED (confirmed) or DISMISSED (false alarm).
List Flags
For a specific thread:
GET /api/v1/threads/:threadId/flags?status=OPEN
Across all threads (admin dashboard):
GET /api/v1/threads/flags?status=OPEN&channelSlug=booking
JSON Logic Conditions
Rules use JSON Logic for condition evaluation -- a standard, safe, portable format with libraries in every language.
Context Object
When a rule is evaluated, PlatformXe builds a context object from the flag, message, thread, and channel data:
{
"flag": {
"reason": "SAFETY",
"note": "Smoke detected",
"severity": "HIGH",
"flaggedByRole": "GUEST"
},
"message": {
"id": "msg-001",
"content": "There is smoke in the apartment",
"senderRole": "GUEST",
"type": "TEXT"
},
"thread": {
"id": "th-001",
"entityId": "BK-2026-00451",
"status": "OPEN",
"messageCount": 5,
"subject": "Booking BK-2026-00451"
},
"channel": {
"entityType": "BOOKING",
"slug": "booking"
}
}
Condition Examples
Flag reason is SAFETY or HARASSMENT:
{ "in": [{ "var": "flag.reason" }, ["SAFETY", "HARASSMENT"]] }
Flag reason is SAFETY AND flagged by a GUEST:
{
"and": [
{ "==": [{ "var": "flag.reason" }, "SAFETY"] },
{ "==": [{ "var": "flag.flaggedByRole" }, "GUEST"] }
]
}
Thread has more than 10 messages (heated conversation):
{ ">": [{ "var": "thread.messageCount" }, 10] }
Always match (catch-all rule):
{ "==": [1, 1] }
Message content contains a keyword:
{ "in": ["emergency", { "var": "message.content" }] }
See jsonlogic.com for the complete operator reference.
Actions
Each rule can execute one or more actions in order.
CREATE_ISSUE
Creates a formal issue in PlatformXe's global issue tracker, linked back to the thread and message.
{
"type": "CREATE_ISSUE",
"config": {
"title": "{{flag.reason}}: {{thread.subject}}",
"description": "Flagged by {{flag.flaggedByRole}} in {{channel.entityType}} {{thread.entityId}}",
"priority": "URGENT",
"tags": ["safety", "escalated", "{{channel.slug}}"]
}
}
Template variables ({{field.path}}) are resolved against the escalation context before execution.
WEBHOOK
Dispatches the escalation to your registered webhook endpoints via PlatformXe's webhook infrastructure (HMAC-signed, with retry).
{
"type": "WEBHOOK",
"config": {}
}
The webhook payload includes the full escalation context -- flag, message, thread, and channel data. Your application receives it and can create tickets in your own system, send notifications, trigger workflows, or take any domain-specific action.
NOTIFY_PARTICIPANTS
Posts a system message to the thread, visible to specified roles.
{
"type": "NOTIFY_PARTICIPANTS",
"config": {
"systemMessage": "A safety concern has been reported. Our team is responding.",
"roles": ["ALL"]
}
}
Use role-scoped visibility to control who sees the notification:
["ALL"]-- everyone in the thread["HOST", "PLATFORM"]-- host and platform only (guest excluded)["PLATFORM"]-- internal note
CLOSE_THREAD
Automatically closes the thread when a severe escalation occurs.
{
"type": "CLOSE_THREAD",
"config": {
"reason": "Safety escalation -- conversation suspended"
}
}
Auto-Detection
Configure keyword patterns that run on every message sent in a channel. When a pattern matches:
autoFlag: true-- the system automatically creates a flag (triggering the full escalation pipeline). No human action needed.suggestFlag: true-- a suggestion is returned in the API response, but no flag is created. Your UI can prompt the recipient to flag it.
{
"autoDetection": [
{
"patterns": ["fire", "flooding", "gas leak", "injury", "police"],
"flagReason": "SAFETY",
"autoFlag": true,
"notifyRoles": ["PLATFORM"]
},
{
"patterns": ["refund", "overcharged", "wrong amount"],
"flagReason": "DISPUTE",
"autoFlag": false,
"suggestFlag": true
}
]
}
Pattern matching is case-insensitive substring matching. A message containing "there is a GAS LEAK" will match the pattern "gas leak".
Auto-detection runs asynchronously after message delivery -- it never blocks or delays message sending.
Cooldowns
Prevent duplicate escalations with per-rule cooldowns. If cooldownMinutes is set, the same rule will not fire again for the same thread within that window.
{
"id": "rule-safety",
"cooldownMinutes": 60,
...
}
This means: if a SAFETY flag triggers this rule, another SAFETY flag in the same thread within 60 minutes will not re-trigger it. Different rules are evaluated independently.
Webhook Events
| Event | Trigger | Payload |
|---|---|---|
message.flagged | Message flagged by participant or auto-detection | Flag + message + thread context |
thread.escalated | Escalation rule matched and actions executed | Escalation + rule + actions taken |
flag.reviewed | Admin reviews or dismisses a flag | Flag + review decision |
Autonomous Escalation Actions
Beyond the core actions (CREATE_ISSUE, WEBHOOK, NOTIFY_PARTICIPANTS, CLOSE_THREAD), the escalation framework supports six autonomous action types. These actions bridge PlatformXe conversations to domain-specific operations in your application via direct webhook dispatch.
Each autonomous action sends a structured payload to a webhookUrl you configure in the action config. Your application receives the webhook, validates the payload, and executes the domain logic. PlatformXe handles delivery, HMAC signing, and retry.
DISPATCH_SERVICE
Auto-dispatch a service request (cleaning, repair, key access) in response to an escalation. Your application receives the webhook and creates the appropriate work order.
{
"type": "DISPATCH_SERVICE",
"config": {
"webhookUrl": "https://api.yourapp.com/hooks/dispatch",
"serviceType": "CLEANING",
"urgency": "HIGH",
"notes": "Guest reported cleanliness issue in {{thread.entityId}}"
}
}
Webhook payload fields: serviceType, urgency, notes, entityId, entityType, thread, flag.
Lettings scenario -- cleanliness auto-dispatch: A guest flags a message with reason COMPLAINT and the auto-detection pattern matches keywords like "dirty", "unclean", or "stains". The rule dispatches a cleaning crew to the property:
{
"id": "rule-clean-001",
"name": "Cleanliness auto-dispatch",
"trigger": "KEYWORD_MATCH",
"conditions": {
"==": [{ "var": "flag.reason" }, "COMPLAINT"]
},
"actions": [
{
"type": "DISPATCH_SERVICE",
"config": {
"webhookUrl": "https://api.lettings.example.com/hooks/dispatch",
"serviceType": "CLEANING",
"urgency": "HIGH",
"notes": "Auto-dispatched for {{thread.entityId}}"
}
},
{
"type": "NOTIFY_PARTICIPANTS",
"config": {
"systemMessage": "We have dispatched a cleaning team. They will arrive within 2 hours.",
"roles": ["GUEST"]
}
}
],
"priority": 2,
"isActive": true,
"cooldownMinutes": 120
}
PROCESS_REFUND
Auto-evaluate and trigger a refund request. Your application receives the webhook with amount constraints and refund type, then processes the refund through your payment system.
{
"type": "PROCESS_REFUND",
"config": {
"webhookUrl": "https://api.yourapp.com/hooks/refund",
"maxAmount": 50000,
"refundType": "PARTIAL",
"reason": "Cleanliness complaint for {{thread.entityId}}"
}
}
Webhook payload fields: maxAmount, refundType, reason, entityId, entityType, thread, flag.
Lettings scenario -- refund processing: A dispute flag is raised after multiple complaints. The rule evaluates the conditions and triggers a partial refund:
{
"id": "rule-refund-001",
"name": "Dispute auto-refund",
"trigger": "PARTICIPANT_FLAG",
"conditions": {
"and": [
{ "==": [{ "var": "flag.reason" }, "DISPUTE"] },
{ ">": [{ "var": "thread.messageCount" }, 5] }
]
},
"actions": [
{
"type": "PROCESS_REFUND",
"config": {
"webhookUrl": "https://api.lettings.example.com/hooks/refund",
"maxAmount": 50000,
"refundType": "PARTIAL",
"reason": "Auto-refund: dispute in booking {{thread.entityId}}"
}
},
{
"type": "NOTIFY_PARTICIPANTS",
"config": {
"systemMessage": "We are processing a partial refund for your booking. You will receive confirmation shortly.",
"roles": ["GUEST"]
}
}
],
"priority": 3,
"isActive": true,
"cooldownMinutes": 1440
}
BLOCK_ENTITY
Suspend a property, host account, or booking in response to severe escalations. Your application receives the webhook and applies the block.
{
"type": "BLOCK_ENTITY",
"config": {
"webhookUrl": "https://api.yourapp.com/hooks/block",
"entityType": "PROPERTY",
"blockType": "SUSPEND",
"reason": "Safety concern: {{flag.reason}}",
"duration": "7d"
}
}
Webhook payload fields: targetEntityType, targetEntityId, blockType, reason, duration, thread, flag.
Lettings scenario -- safety lockdown: A SAFETY flag triggers an immediate property suspension and booking cancellation:
{
"id": "rule-safety-lockdown",
"name": "Safety lockdown",
"trigger": "PARTICIPANT_FLAG",
"conditions": {
"==": [{ "var": "flag.reason" }, "SAFETY"]
},
"actions": [
{
"type": "BLOCK_ENTITY",
"config": {
"webhookUrl": "https://api.lettings.example.com/hooks/block",
"entityType": "PROPERTY",
"blockType": "SUSPEND",
"reason": "Safety escalation in {{thread.entityId}}",
"duration": "7d"
}
},
{
"type": "CREATE_ISSUE",
"config": {
"title": "SAFETY LOCKDOWN: {{thread.subject}}",
"priority": "URGENT",
"tags": ["safety", "lockdown", "{{channel.slug}}"]
}
},
{
"type": "NOTIFY_PARTICIPANTS",
"config": {
"systemMessage": "This property has been suspended pending safety review. Our team will contact you directly.",
"roles": ["ALL"]
}
},
{
"type": "CLOSE_THREAD",
"config": {
"reason": "Safety lockdown -- thread suspended"
}
}
],
"priority": 1,
"isActive": true,
"cooldownMinutes": 60
}
ASSIGN_HANDLER
Auto-assign a handler from a pool of available agents. Your application receives the webhook and assigns the most appropriate handler based on availability, skills, and workload.
{
"type": "ASSIGN_HANDLER",
"config": {
"webhookUrl": "https://api.yourapp.com/hooks/assign",
"handlerPool": "support-team",
"priority": "HIGH",
"dueWithin": 30
}
}
Webhook payload fields: handlerPool, priority, dueWithinMinutes, entityId, entityType, thread, flag, message.
EMIT_EVENT
Fire a platform event through the PlatformXe event system. Use this to chain escalation outcomes into workflow triggers, analytics pipelines, or cross-service automation.
{
"type": "EMIT_EVENT",
"config": {
"eventType": "escalation.safety.triggered",
"payload": {
"severity": "CRITICAL",
"source": "guest-report"
}
}
}
The event is emitted with organizationId, threadId, entityId, entityType, and source: "escalation-engine" merged into the payload automatically.
SCHEDULE_FOLLOW_UP
Schedule a timed re-check via Inngest durable functions. After the configured delay, PlatformXe checks whether the issue has been resolved and optionally re-escalates if not.
{
"type": "SCHEDULE_FOLLOW_UP",
"config": {
"delayMinutes": 120,
"checkType": "ISSUE_RESOLVED",
"escalateIfUnresolved": true,
"webhookUrl": "https://api.yourapp.com/hooks/follow-up"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
delayMinutes | number | Yes | Minutes to wait before the follow-up check |
checkType | string | No | Type of check to perform. Default: ISSUE_RESOLVED |
escalateIfUnresolved | boolean | No | Whether to re-escalate if the issue is still open. Default: true |
webhookUrl | string | No | Webhook to call at follow-up time for custom resolution checks |
SDK Reference
// Configure escalation rules on a channel
await client.threads.setEscalationConfig('ch-001', {
flagReasons: [...],
autoDetection: [...],
rules: [...],
});
// Get current escalation config
const config = await client.threads.getEscalationConfig('ch-001');
// Flag a message
const result = await client.threads.flagMessage('th-001', 'msg-001', {
reason: 'SAFETY',
note: 'Smoke detected',
flaggedByExternalId: 'usr-abc',
flaggedByRole: 'GUEST',
});
// result.escalations tells you which rules matched
// Admin escalates a thread
await client.threads.escalateThread('th-001', {
reason: 'DISPUTE',
escalatedByExternalId: 'admin-001',
escalatedByRole: 'PLATFORM',
});
// Review a flag
await client.threads.reviewFlag('flg-001', {
status: 'REVIEWED',
reviewedByExternalId: 'admin-001',
});
// List flags
const flags = await client.threads.listFlags('th-001');