PlatformXeDocs
Get API Key

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, injury
  • DISPUTE -- payment or service disagreement
  • COMPLAINT -- cleanliness, noise, amenities

A healthcare platform might define:

  • CLINICAL_CONCERN -- adverse reaction, missed dose
  • BILLING_DISPUTE -- incorrect charge
  • ACCESS_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, or ADMIN_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

EventTriggerPayload
message.flaggedMessage flagged by participant or auto-detectionFlag + message + thread context
thread.escalatedEscalation rule matched and actions executedEscalation + rule + actions taken
flag.reviewedAdmin reviews or dismisses a flagFlag + 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"
  }
}
FieldTypeRequiredDescription
delayMinutesnumberYesMinutes to wait before the follow-up check
checkTypestringNoType of check to perform. Default: ISSUE_RESOLVED
escalateIfUnresolvedbooleanNoWhether to re-escalate if the issue is still open. Default: true
webhookUrlstringNoWebhook 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');