Skip to main content
Events are evaluated by the same engine as transactions. There is one rule model, one condition vocabulary and one set of actions - a rule simply declares which record kinds it applies to.

Applies to

scope.record_types decides what a rule fires on.
Leaving it out keeps a rule transaction-only. Every rule written before events existed behaves exactly as it did, and a rule never starts matching logins because its scope happened to be empty.
In the console this is the Applies to selector on a rule’s Trigger step. Choosing Events reveals an Event categories checklist that narrows the rule further; leave it all unchecked to evaluate every category.
"__current__" means “the same value as the event being evaluated” - that is how a rule says per subject, per device or per round without naming one.

Event fields

Conditions read namespaced paths. The transaction vocabulary you already know (action_type, subject_vendor_data, subject_ip_country, custom_values.<key>) also resolves on an event. Subject signals are cached on the monitored user, not recomputed per event, so a rule can read them at ingestion speed.

Aggregations

Velocity

The reducers you already use on transactions - count, sum, avg, min, max, distinct_count - over a rolling window (30m, 24h, 7d, 30d).

Ratio

Two reducers over the same window. This is how wager-to-deposit, withdrawal-to-deposit, bonus-to-deposit and chargeback rates are expressed.
Deposit 1,000, wager 20, request a withdrawal, and the ratio is 0.02 - classic minimal-play laundering.
A ratio with no denominator never matches. No deposits is not a ratio of zero, it is no evidence, and firing there would flag every brand-new user.

Sequence

Ordered predicates inside a window. Each step matches on action_type, event_category or an arbitrary match filter, and can require min_count occurrences before the sequence advances.
Credential stuffing, then the MFA takeover, then the payout redirect, then the drain. The rule fires on the event that completes the chain, and the matched evidence lists the event ids of every step. Sequence state is kept per rule and subject, so it advances as events arrive instead of rescanning the timeline. Progress older than the window is discarded rather than counted.

Correlation

A cross-subject join: reduce over every event that shares this event’s group.
"pair": true matches the two ends as an unordered set, so A→B and B→A land in the same group - which is what lets one rule see both halves of a collusion. Chip dumping, opposite betting and affiliate self-referral are all this shape. Always give a correlation rule narrowing filters. Without them it considers every event in the window.

Real-time and asynchronous rules

Some rules can answer inside the ingestion request and some cannot. An event whose rules are still pending comes back with "correlation_pending": true. Its status and score update in place once the asynchronous pass completes; re-running is idempotent, so a rule never applies its score twice. This split is deliberate: a cross-subject ring query must not make a login wait.

Actions

The same action vocabulary as transaction rules - add_score, change_status, add_tags, add_note, add_to_list, open_case. Two differences on events:
  • change_status to AWAITING_USER is a transaction-only outcome (there is no event remediation flow) and is applied as IN_REVIEW.
  • open_case links the case to the subject rather than to a transaction, and records the matched event id on the case.

Evidence

Every match stores what it saw: the aggregation window, the computed value, the expected value, sampled event ids, and the source of each matched field. An analyst can therefore tell a server-stamped IP from a client-supplied hint before acting on a match. The console shows the source next to each matched field in the event drawer.

Worked examples

Deposit, token play, fast withdrawal. Condition event.action_type eq withdrawal_requested, plus the wager-to-deposit ratio above at lte 0.1 over 24h.
Ratio of count(chargeback_opened) to count(deposit_completed) per subject over 30d, gte 0.5. Group by event.payment.payment_fingerprint instead of the subject to catch one instrument across accounts.
The ato_drain sequence above, with add_score 90 and change_status DECLINED.
The pair correlation above. Raise value to the amount that is material for your tables, and add event.gameplay.game_id to group_by to keep games separate.
Conditions on event.action_type eq bot_signal_observed, event.bot.timing_variance_ms lte 10 and event.device.bot_score gte 90, with add_tags. Combine with a distinct_count of event.device.fingerprint per IP to find a farm rather than one script.
Condition on event.action_type eq self_exclusion_match and event.payload.re_registration_attempt eq true, escalating with open_case.

Next

Event ingestion

The envelope, the ten categories and the batch contract.

Rules API

Create, update and back-test rules programmatically.