This guide provides an exhaustive exploration of every mechanism available for constructingDocumentation Index
Fetch the complete documentation index at: https://docs.finwatch.finance/llms.txt
Use this file to discover all available pages before exploring further.
when clauses in Watch Script rules. Each section includes real-world fraud detection examples, internal behavior details, and common pitfalls.
For the quick-reference version of operators and syntax, see the DSL Reference.
Simple Field Comparisons
The most fundamental building block of a condition is a comparison between a field and a value using one of the six comparison operators.Comparison Operators
| Operator | Meaning | Example |
|---|---|---|
== | Equal to | currency == "USD" |
!= | Not equal to | status != "verified" |
> | Greater than | amount > 10000 |
>= | Greater than or equal to | amount >= 5000 |
< | Less than | amount < 100 |
<= | Less than or equal to | amount <= 50000 |
Numeric Comparisons
When both sides of a comparison can be interpreted as numbers (specificallyfloat64), FinWatch performs a numeric comparison. This is the most common case for the amount field:
String Comparisons
When either side cannot be parsed as a number, FinWatch falls back to string comparison. Only== and != produce meaningful results for strings. Using >, <, >=, or <= on non-numeric strings will return false.
Warning: String comparison is case-sensitive."USD"does not equal"usd". If you need case-insensitive matching, use theregexoperator with(?i).
Boolean Comparisons
The DSL supportstrue and false as literal values:
Accessing Nested Data
Transaction data in FinWatch is a JSON object. Fields can be nested inside themetadata (or meta_data) object. You access nested fields using dot notation.
Top-Level Fields
These fields are directly available on every transaction:Metadata Fields (1-Level Nesting)
Your application can send arbitrary metadata with each transaction. Access it with a single dot:Deeply Nested Fields (2+ Levels)
If your metadata contains nested objects, chain the dots:How It Works Internally
The interpreter uses adig() function that splits the field path by . and traverses the JSON tree one level at a time:
dig() returns (nil, false) and the condition evaluates to false. This is a deliberate design choice — it prevents rules from crashing on incomplete data.
The Silent Failure Pitfall
This behavior means a typo in a field name will cause a rule to never trigger — and there will be no error message to alert you:Tip: When a rule isn’t triggering as expected, the first thing to check is field name spelling. Log the actual transaction JSON and verify the exact field paths.
Combining Conditions with and
The and operator requires both conditions to be true for the combined expression to be true.
Syntax
and conditions as you need. All must be true for the rule to fire.
Example: Multi-Factor Suspicious Transaction
true:
- Amount exceeds $10,000
- Currency is not USD
- Payment method is a wire transfer
- Destination country is not US
false, the entire rule does not fire.
Top-Level AND Semantics
At the top level of a rule’swhen clause, conditions connected by and are evaluated with AND semantics — meaning all must pass. The interpreter short-circuits: if any condition fails, it stops evaluating and returns false immediately.
This short-circuit behavior is important for performance. See the Short-Circuit Evaluation section below.
Combining Conditions with or
The or operator requires at least one condition to be true for the combined expression to be true.
Syntax
Example: Late Night Transaction Detection
The Precedence Pitfall
This is one of the most critical details in the entire DSL:There is no implicit grouping that makesandandorhave equal precedence and are evaluated left-to-right.
and bind tighter than or (unlike most programming languages where && has higher precedence than ||).
Consider this rule:
A or (B and C). In Watch Script, it is interpreted as (A or B) and C — because it’s evaluated strictly left-to-right.
This can lead to unexpected behavior. For example:
(hour >= 23 OR hour <= 4) AND amount > 5000 — which means a $100 late-night transaction would not trigger the rule, even though you might have intended it to.
How to Handle Complex Logic
Option 1: Restructure your conditions. Place theand conditions before the or:
OR logic between independent patterns, create separate rules:
Set Membership with in
The in operator checks whether a field’s value exists within a given list. This is essential for blacklists, whitelists, and category checks.
Syntax with Inline Arrays
Syntax with Variables
$) reference externally-managed lists. This is the recommended approach for lists that change over time. See the Variables and Dynamic Data guide for details.
How It Works Internally
The interpreter converts the field value to a string, then iterates through the list and compares each element as a string:in is a string membership check. The number 7995 and the string "7995" will match each other because both are converted to the string "7995" before comparison.
Example: Sanctioned Country Check
Example: High-Risk Merchant Category
7995: Gambling6012: Financial institutions4829: Wire transfers6211: Security brokers
Pattern Matching with regex and not_regex
Regular expression operators let you match patterns in string fields. This is powerful for detecting suspicious keywords in free-text fields like transaction descriptions.
Syntax
regexreturnstrueif the pattern matches anywhere in the field value.not_regexreturnstrueif the pattern does not match.
The Pattern Language
FinWatch uses Go’sregexp package, which implements the RE2 syntax. This is a safe subset of regular expressions that guarantees linear-time matching (no catastrophic backtracking).
Case-Insensitive Matching
Use(?i) at the beginning of your pattern for case-insensitive matching:
Example: Suspicious Description Patterns
btc,bitcoin,crypto,wallet,transfer— cryptocurrency and money transfer keywordsgift.?card— “giftcard”, “gift card”, “gift-card” (.?matches zero or one character)western.?union— “westernunion”, “western union”, “western-union”
Example: Email Domain Filtering
Example: Excluding Known-Good Patterns
Performance Warning
Regular expressions are evaluated at runtime for every transaction. While RE2 guarantees linear-time matching, complex patterns on long strings can still be slow. Guidelines:- Keep patterns simple. Prefer alternation (
a|b|c) over complex nested groups. - Anchor when possible. Use
^and$to anchor patterns to the start/end of the string. This allows the engine to fail fast. - Avoid
.+on large fields. Patterns like.+somethingmust scan the entire string. - Combine with cheap conditions. Use
and amount > Xto filter out low-value transactions before the regex runs.
Type Coercion
Understanding how FinWatch compares values of different types is essential for writing correct rules.The Comparison Algorithm
When evaluating a comparison likeamount > 10000, the interpreter follows this algorithm:
- Try numeric comparison first. If both the field value and the comparison value can be parsed as
float64, perform a numeric comparison. - Fall back to string comparison. If either value cannot be parsed as a number, convert both to strings using
fmt.Sprint()and compare lexicographically. For string comparisons, only==and!=are supported;>,<,>=,<=returnfalse.
Practical Implications
Numbers work as expected:amount field arrives as a string "15000" (which can happen depending on your JSON serialization), FinWatch will still parse it as a number and compare correctly — because toFloat() handles string-to-float conversion.
However, if you compare a string field like currency with >, the result may surprise you:
Tip: Only use==and!=for string fields. Use>,<,>=,<=exclusively for numeric fields.
JSON Number Types
When transaction data arrives as JSON, numbers without quotes are parsed asfloat64 or json.Number. Numbers with quotes are parsed as strings. FinWatch’s toFloat() function handles both:
Short-Circuit Evaluation
FinWatch’s interpreter uses short-circuit evaluation for logical operators. This has important implications for both correctness and performance.How It Works
and: If the left condition isfalse, the right condition is not evaluated. The result isfalse.or: If the left condition istrue, the right condition is not evaluated. The result istrue.
Why It Matters for Performance
Aggregate functions (count(), sum(), etc.) are the most expensive operations in FinWatch because they execute SQL queries against DuckDB. Simple field comparisons are nearly free — they just look up a value in a map.
By placing cheap conditions before expensive conditions in an and chain, you can avoid running expensive queries on transactions that would fail the cheap check anyway:
The Rule of Thumb
Order yourand conditions from cheapest to most expensive:
- Simple field comparisons (
amount > X,currency == "USD") — near zero cost. - Metadata lookups (
metadata.country == "US") — very cheap. - Regex matching (
description regex "...") — moderate cost. - Time functions (
hour_of_day(timestamp) >= 23) — cheap but involves parsing. - Aggregate functions (
count(...),sum(...)) — expensive (SQL query). - Previous transaction lookups (
previous_transaction(...)) — most expensive (SQL query with joins).
Next Steps
You now have a thorough understanding of every condition mechanism in the Watch Script DSL. Continue your learning with:- Aggregate Functions Guide — Deep dive into
count(),sum(),avg(),max(),min()for detecting behavioral patterns over time. - Time-Based Rules — Use
hour_of_day(),day_of_week(), and other time functions. - Previous Transaction Lookups — Detect sequential fraud patterns with
previous_transaction(). - Variables and Dynamic Data — Use
$variablesfor externally-managed lists and$currentfor self-referencing filters. - DSL Reference — The complete language specification.
.png?fit=max&auto=format&n=0JF6z69u57hmqsWm&q=85&s=531373acedba0eb783b669f6d558dfd8)