Watch scripts
Setting Conditions
Write conditions in the Blnk Watch DSL: comparisons, logical operators, aggregates, and time-based checks.
The when clause is used to describe a set of conditions that trigger a transaction in Watch. Rules still evaluate transactions only. If you need extra context, include it in meta_data on the transaction first.
When setting conditions, you can only use the supported fields in the Watch transaction payload: transaction_id, amount, reference, source, destination, description, created_at, and meta_data.
Basic conditions
Basic conditions compare transaction fields to literal values. You can compare numeric fields (e.g. amount), string fields (e.g. currency, source, destination), and metadata (e.g. meta_data.kyc_tier).
For example, this is a simple rule flagging amounts greater than 10,000:
rule largeTransfer {
when amount > 10000
then review
score 0.6
reason "Transaction amount exceeds 10,000"
}
When directly comparing fields with values, Blnk supports the following operators:
| Operator | Meaning | Example |
|---|---|---|
== | equal to | currency == "USD" |
!= | not equal to | source != destination |
> | greater than | amount > 4000 |
>= | greater than or equal to | amount >= 4000 |
< | less than | amount < 100 |
<= | less than or equal to | amount <= 100 |
in | value is in a list | meta_data.channel in("card", "bank_transfer") |
regex | matches a regular expression | description regex"(?i)(gift.?card|crypto)" |
not_regex | does not match a regular expression | description not_regex"(?i)test" |
Combined conditions
To create conditions with two or more filters, Watch supports two logical operators:
- and: Every condition must be true.
- or: At least one of the conditions must be true.
For example, we could safeguard our earlier rule by specifying what currencies the transaction limit is for:
rule highValueTransfer {
when amount > 4000
and currency in ("USD", "EUR")
then review
score 0.5
reason "USD/EUR transaction exceeds 4,000"
}
With or, one of two conditions can trigger the same risk action.
For example, in a cross-border app you might treat amounts over 10,000 as high-risk regardless of currency; but if the transaction is in PKR, IRR, or SYP, you treat it as high-risk regardless of amount.
rule highRiskTransaction {
when amount > 10000
or currency in ("PKR", "IRR", "SYP")
then review
score 0.5
reason "High-risk transaction detected"
}
Recommendation
If your or conditions start to get complex, you should consider using a separate rule for each condition. For example, a complex set of conditions like this …
when (currency == 'PKR' and amount > 1000)
or (currency == 'IRR' and amount > 8000)
or (currency == 'SYP' and amount > 5000)
… is better written as three separate rules:
rule highRiskPKRTransaction {
when currency == 'PKR' and amount > 1000
then review
score 0.5
reason "High-risk PKR transaction detected"
}rule highRiskIRRTransaction {
when currency == 'IRR' and amount > 8000
then review
score 0.5
reason "High-risk IRR transaction detected"
}rule highRiskSYPTransaction {
when currency == 'SYP' and amount > 5000
then review
score 0.5
reason "High-risk SYP transaction detected"
}This makes your rule set easier to read, reason about, and change in your workflow.
Metadata reference
When setting conditions on information that is not part of the top-level parameters, such as source country, destination country, customer KYC tier, or promo code, pass the value in meta_data.
You can then reference the value in your rule using dot notation. For example, here’s a rule that flags when a customer has used at least one of the supported promo codes:
rule promoCodeReuse {
when meta_data.promo_code in ('DISCOUNT10', 'WELCOME15', 'REFERRAL20')
then review
score 0.25
reason "Current transaction has redeemed a supported promo code"
}
Now, you can use this flag to update the customer’s account, e.g. to prevent them from using any of those promo codes again.
Dynamic references
Dynamic references allow you to reuse values from the transaction being evaluated when defining a condition. This works with any field in the transaction payload, including values in meta_data.
Use dynamic references when the value you want to compare depends on the transaction being evaluated, rather than a fixed value.
To reference a value from the current transaction, use:
$current.<field>
For example, to flag transactions where the source and destination are the same:
rule sameSourceAndDestination {
when source == $current.destination
then review
score 0.45
reason "Source and destination resolve to the same account"
}
Another common use case is mismatched countries. For example, you may want to flag transactions where the customer’s registered country is different from the country the transaction is coming from.
This can indicate unusual activity or possible fraud:
rule mismatchedCountries {
when meta_data.registered_country != $current.destination_country
then review
score 0.45
reason "Registered country does not match source country"
}
Aggregate operators
Aggregate conditions evaluate groups of transactions over a defined time window rather than a single transaction. This makes it possible to detect patterns in activity.
Here are some examples of aggregate conditions in practice:
rule userSpendingLimit {
when sum(amount when source == $current.source, "PT24H") > 10000
and meta_data.customer_tier != "premium"
then review
score 0.65
reason "User spending exceeds 10,000 in 24 hours"
}rule burstToSameDestination {
when count(when destination == $current.destination, "PT30M") >= 5
then review
score 0.65
reason "Five or more transactions to the same destination in 30 minutes"
}rule repeatedTransactions {
when count(when source == $current.source, "PT24H") >= 5
and meta_data.kyc_tier == "basic"
then review
score 0.65
reason "Five or more transactions from the same source in 24 hours"
}You can use them to identify behaviors such as repeated transactions from the same source, burst activity to the same destination, or a user spending above a defined limit within a period, to name a few.
Instead of checking just the current transaction, aggregate conditions analyze related transactions that match a filter and calculate metrics such as count or sum within the specified time window.
Rules can then trigger when those values cross a threshold defined in your condition.
To use an aggregate:
<aggregate>(when <filter>, "<window>")
aggregatedefines the metric to compute, e.g.countorsum.when <filter>selects the transactions to include in the calculation.<window>defines how far back to look from the current transaction.
Watch supports the following aggregate operators:
| Aggregate | Description |
|---|---|
count | Number of matching transactions in the window |
sum | Total of a numeric field (e.g. amount) over the window |
avg | Average value of a numeric field over the window |
max | Largest value of a numeric field in the window |
min | Smallest value of a numeric field in the window |
The time window is written as an ISO 8601 duration string in quotes.
| Unit | Example | Meaning |
|---|---|---|
| Seconds | "PT30S" | 30 seconds |
| Minutes | "PT30M" | 30 minutes |
| Hours | "PT24H" | 24 hours |
| Days | "P7D" | 7 days |
Time-based functions
Time-based functions let you write rules that depend on when a transaction occurs. For example, you might want to review large transactions late at night:
rule lateNightLargeTransfer {
when hour_of_day(created_at) >= 22
and amount > 3000
then review
score 0.6
reason "Large USD transaction during late-night hours"
}
In this rule, Watch checks the transaction timestamp and evaluates whether it occurred after 10 PM. Time-based functions read from created_at on the transaction: the value you send on inject or that Watch syncs from Blnk Core.
You can write time-based conditions using helper functions that extract parts of the timestamp.
| Function | Description | Example |
|---|---|---|
hour_of_day(created_at) | Hour of day (0-23) | hour_of_day(created_at) >= 22 |
day_of_week(created_at) | Weekday: 0 = Sunday, 6 = Saturday. With in, you can use day names | day_of_week(created_at) in (0, 6) or day_of_week(created_at) in("Saturday", "Sunday") |
day_of_month(created_at) | Day of month (1-31) | day_of_month(created_at) == 17 |
day_of_year(created_at) | Day of year (1-366) | day_of_year(created_at) <= 31 |
month_of_year(created_at) | Month (1-12) | month_of_year(created_at) == 3 |
week_of_year(created_at) | ISO week number (1-53) | week_of_year(created_at) == 12 |
year(created_at) | Year (e.g. 2026) | year(created_at) == 2026 |
Also note: Time-based conditions support standard comparison operators:
==,!=,>,>=,<,<=,in, andnot_in.
For
day_of_week, when using theinoperator, you can use day names instead of numbers. Soday_of_week(created_at) in ("Saturday", "Sunday")is equivalent today_of_week(created_at) in (0, 6).
For example, to flag high value transactions that occur on a weekend:
rule weekendHighValueTransactions {
when day_of_week(created_at) in ("Saturday", "Sunday")
and amount > 10000
and currency == "USD"
then review
score 0.45
reason "Transaction occurred on a weekend"
}
“Previous transaction” function
previous_transaction checks if any transaction in a past time window matches specific criteria. It returns true if at least one matching transaction exists, otherwise false.
Use it to detect patterns like:
- This source had a failed transaction recently
- This destination already received a transaction in the last hour
To use this function:
previous_transaction(within: "<duration>", match: { <field>: <value>, ... })
- within - How far back to look. Uses ISO 8601 duration strings, e.g.
"PT1H"(1 hour),"P1D"(1 day). - match - A set of fields that must all match for a transaction to count. You can use literal values, e.g.
meta_data.status: "failed"or dynamic references,source: \$current.source.
For example, to flag and block when the same source has had a failed transaction in the last hour:
rule blockIfPreviousFailed {
when previous_transaction(
within: "PT1H",
match: { source: "$current.source", status: "failed" }
)
and amount > 700000
then block
score 1.0
reason "Previous transaction from this source failed; blocking high amount"
}
Here’s another example that checks if a transaction has received at least one transaction in the last 30 minutes:
rule burstToSameDestination {
when previous_transaction(
within: "PT30M",
match: { destination: "$current.destination" }
)
then review
score 0.5
reason "Another transaction to this destination in the last 30 minutes"
}
Note:
- This is a boolean check, not a count. It only answers: “Did at least one matching transaction occur?”
- The lookback window ends at the current transaction’s
created_at, not server time.matchkeys must be table columns (source,destination,status, and so on) or metadata paths (meta_data.status,metadata.status). Dotted keys in object literals are supported (for examplemeta_data.status: "failed").- Uses the same transaction store and time window format as aggregate conditions.