What is a Policy Rule?
A Policy Rule is a condition → actions pair. When the input facts satisfy the condition, the rule’s actions fire. Rules are evaluated in priority order (0 = highest priority).
IF condition matches → THEN execute actions
Each rule belongs to a specific policy version and has a name, a priority, a condition (tree-structured logic), and one or more actions.
Condition Syntax
Conditions use a tree structure with two node types: SINGLE and GROUP.
LexQ uses its own condition DTO format with type: "SINGLE" / type: "GROUP", field, operator, value, and valueType fields. Do not confuse this with the Console’s internal form representation which may use different field names. Always use the engine format when calling the API or CLI.
SINGLE Condition
A leaf node that compares a single fact against a value:
{
"type" : "SINGLE" ,
"field" : "payment_amount" ,
"operator" : "GREATER_THAN_OR_EQUAL" ,
"value" : 100000 ,
"valueType" : "NUMBER"
}
GROUP Condition
A branch node that combines child conditions with AND or OR:
{
"type" : "GROUP" ,
"operator" : "AND" ,
"children" : [
{ "type" : "SINGLE" , "field" : "customer_tier" , "operator" : "EQUALS" , "value" : "VIP" , "valueType" : "STRING" },
{ "type" : "SINGLE" , "field" : "payment_amount" , "operator" : "GREATER_THAN" , "value" : 50000 , "valueType" : "NUMBER" }
]
}
Operators
Operator Compatible Types Description EQUALSAll Exact match NOT_EQUALSAll Negation GREATER_THANNUMBER >GREATER_THAN_OR_EQUALNUMBER >=LESS_THANNUMBER <LESS_THAN_OR_EQUALNUMBER <=CONTAINSSTRING Substring match INSTRING, NUMBER Value is in the provided list NOT_INSTRING, NUMBER Value is not in the provided list
Value Types
Type JSON Value Example STRING"string""VIP"NUMBERnumber100000BOOLEANtrue / falsetrueLIST_STRING["a", "b"]["KR", "US"]LIST_NUMBER[1, 2][10000, 20000]
Nested Conditions Example
(customer_tier = "VIP" AND payment_amount >= 100000) OR region IN ["KR", "JP"]
{
"type" : "GROUP" ,
"operator" : "OR" ,
"children" : [
{
"type" : "GROUP" ,
"operator" : "AND" ,
"children" : [
{ "type" : "SINGLE" , "field" : "customer_tier" , "operator" : "EQUALS" , "value" : "VIP" , "valueType" : "STRING" },
{ "type" : "SINGLE" , "field" : "payment_amount" , "operator" : "GREATER_THAN_OR_EQUAL" , "value" : 100000 , "valueType" : "NUMBER" }
]
},
{ "type" : "SINGLE" , "field" : "region" , "operator" : "IN" , "value" : [ "KR" , "JP" ], "valueType" : "LIST_STRING" }
]
}
In the example above, customer_tier and region are custom facts that must be registered in Fact Definitions before use. payment_amount is a system fact available by default.
Action Types
Each rule can have multiple actions. Actions fire sequentially when the condition matches.
Type Description Key Parameters DISCOUNTApply a discount to a reference amount refVar, method, rate or valuePOINTAward points based on a reference amount refVar, method, rate or value, integrationIdCOUPON_ISSUEIssue a coupon couponId, integrationIdBLOCKBlock the transaction reasonNOTIFICATIONSend notification (SMS, EMAIL, PUSH) channel, templateId, integrationIdWEBHOOKCall an external URL url, method, payloadTemplate (optional)SET_FACTSet an output variable to a fixed value key, valueADD_TAGAdd a tag to the execution result tag
DISCOUNT — Percentage
Apply a 10% discount based on payment_amount:
{
"type" : "DISCOUNT" ,
"parameters" : {
"method" : "PERCENTAGE" ,
"rate" : 10 ,
"refVar" : "payment_amount"
}
}
If payment_amount is 200,000 → discount is 20,000.
DISCOUNT — Fixed Amount
{
"type" : "DISCOUNT" ,
"parameters" : {
"method" : "AMOUNT" ,
"value" : 5000 ,
"refVar" : "payment_amount"
}
}
SET_FACT — Output Variable
SET_FACT is a literal value setter , not an expression evaluator. It assigns a fixed value to a key in the output variables. It does not support arithmetic expressions or references to other facts.
{
"type" : "SET_FACT" ,
"parameters" : { "key" : "risk_level" , "value" : "HIGH" }
}
BLOCK — Transaction Block
{
"type" : "BLOCK" ,
"parameters" : { "reason" : "Suspected fraud" }
}
POINT — Percentage
Earn 1% of payment_amount as points:
{
"type" : "POINT" ,
"parameters" : {
"method" : "PERCENTAGE" ,
"rate" : 1 ,
"refVar" : "payment_amount" ,
"targetVar" : "total_point" ,
"integrationId" : "<your-point-integration-id>"
}
}
If payment_amount is 100,000 → earns 1,000 points. total_point is incremented and last_earned_point is set in the output.
POINT — Fixed Amount
{
"type" : "POINT" ,
"parameters" : {
"method" : "AMOUNT" ,
"value" : 500 ,
"refVar" : "payment_amount" ,
"integrationId" : "<your-point-integration-id>"
}
}
COUPON_ISSUE
{
"type" : "COUPON_ISSUE" ,
"parameters" : {
"couponId" : "WELCOME_VIP_2026" ,
"integrationId" : "<your-coupon-integration-id>"
}
}
Requires user_id in input facts to identify the recipient. No output variables are modified.
NOTIFICATION
{
"type" : "NOTIFICATION" ,
"parameters" : {
"channel" : "SMS" ,
"templateId" : "ORDER_CONFIRM_001" ,
"targetVar" : "phone_number" ,
"integrationId" : "<your-notification-integration-id>"
}
}
Channels: SMS, EMAIL, PUSH. The targetVar specifies which fact contains the recipient address (defaults to phone_number).
ADD_TAG
{
"type" : "ADD_TAG" ,
"parameters" : {
"tag" : "VIP_VERIFIED" ,
"targetVar" : "user_tags"
}
}
Appends the tag to the list fact. Duplicates are automatically prevented. If targetVar is omitted, defaults to user_tags.
WEBHOOK — Basic
{
"type" : "WEBHOOK" ,
"parameters" : {
"url" : "https://api.example.com/webhooks/orders" ,
"method" : "POST"
}
}
Without payloadTemplate, the engine sends all input/output facts as the request body (system variables excluded).
WEBHOOK — With Payload Template
Use payloadTemplate to customize the request body. Template variables are resolved at execution time.
{
"type" : "WEBHOOK" ,
"parameters" : {
"url" : "<integration-id-or-direct-url>" ,
"method" : "POST" ,
"payloadTemplate" : {
"text" : "🔔 Rule {{ruleName}} fired \n Customer: {{fact.customer_tier}} \n Amount: {{output.payment_amount}}"
}
}
}
Available Template Variables
Variable Description Example Value {{fact.xxx}}Input fact value {{fact.payment_amount}} → 150000{{output.xxx}}Output variable (after actions) {{output.last_discount_amount}} → 15000{{timestamp}}Execution time (ISO-8601) 2026-04-14T05:30:00Z{{ruleName}}Matched rule name VIP 20% Discount{{groupName}}Policy group name Payment Policy{{versionNo}}Executed version number 5{{xxx}}Direct fact lookup (shorthand) {{customer_tier}} → VIP
Slack requires {"text": "..."}, Discord requires {"content": "..."}. Use payloadTemplate to match each platform’s expected format.
{{fact.xxx}} and {{output.xxx}} reference the same data map. If an earlier action (e.g., DISCOUNT) modifies payment_amount, both {{fact.payment_amount}} and {{output.payment_amount}} reflect the modified value.
Mutex — Rule-Level Conflict Resolution
Within a single version, rules can belong to a mutex group to control how many matching rules fire.
Field Description mutexGroupA string key grouping related rules (e.g., "best-discount") mutexModeNONE (default) or EXCLUSIVEmutexStrategyFIRST_MATCH, HIGHEST_PRIORITY, or MAX_BENEFITmutexLimitMax number of rules to fire from this group (for EXCLUSIVE)
When mutexMode is EXCLUSIVE, only the winning rule fires. Other matching rules in the same mutex group are skipped. This is logged in the Decision Trace as BLOCKED_BY_MUTEX.
Complete Rule Example
{
"name" : "VIP 20% Discount" ,
"priority" : 0 ,
"condition" : {
"type" : "GROUP" ,
"operator" : "AND" ,
"children" : [
{ "type" : "SINGLE" , "field" : "customer_tier" , "operator" : "EQUALS" , "value" : "VIP" , "valueType" : "STRING" },
{ "type" : "SINGLE" , "field" : "payment_amount" , "operator" : "GREATER_THAN_OR_EQUAL" , "value" : 100000 , "valueType" : "NUMBER" }
]
},
"actions" : [
{ "type" : "DISCOUNT" , "parameters" : { "method" : "PERCENTAGE" , "rate" : 20 , "refVar" : "payment_amount" } },
{ "type" : "SET_FACT" , "parameters" : { "key" : "discount_applied" , "value" : true } }
],
"mutexGroup" : "best-discount" ,
"mutexMode" : "EXCLUSIVE" ,
"mutexStrategy" : "HIGHEST_PRIORITY" ,
"isEnabled" : true
}
Next Steps
Fact Definitions Define the input variables your rules expect.
Dry Run Test your rules before publishing.