Appearance
Transaction velocity
Monitoring on facts that arrive after onboarding: payments are ingested as events, a workflow evaluates velocity rules over a subject's recent events and opens a high-priority case when the pattern looks wrong. Pack: examples/policies/transaction-velocity/.
A pattern for expressing a monitoring policy, not compliance guidance: the windows, thresholds and the choice of signals are placeholders; a real policy comes from the business's own loss data.
The decision it automates
Whether a subject's recent payment behaviour is fine, needs a person, or should be blocked, as of the moment the workflow runs. Today the run is started by the integrating system (for example after every batch of events, or on a schedule); events as workflow triggers are on the roadmap.
Data expected
- A subject (any kind) and events for it ingested through
POST /events: typepayment, anamountandcurrency, anoccurredAt, a caller-chosenidempotencyKey, the promotedcardFingerprintkey when known, andattributes.status(approved,declined). See Events. - Nothing from
subject.data.
Steps and rules
| Step | Type | Why |
|---|---|---|
rules | evaluate_rules | The velocity_monitoring rule set; every rule aggregates the subject's events as of now. |
route | branch | rules.outcome == manual_review opens a case. |
review | create_case | velocity_review, priority high, 24-hour SLA: money may be moving. |
decide | emit_decision | From the rules, or the reviewer's decision. |
The rule set velocity_monitoring (useCase: monitoring) sums weights into bands 0-40 approve, 40-90 manual_review, 90-100 reject:
| Rule | Aggregate | Severity, weight | Fails when |
|---|---|---|---|
velocity_payment_sum_24h | sum of amount over 24 hours, type payment | warn, 50 | more than 5 000 |
velocity_payment_count_1h | count over 1 hour, type payment | warn, 40 | 5 or more |
velocity_declined_count_24h | count over 24 hours, type payment, status: declined | block, 100 | 3 or more (card testing) |
velocity_distinct_cards_24h | distinct_count of card_fingerprint over 24 hours | warn, 40 | 3 or more distinct cards |
Volume alone (50) is a review; volume with a burst or many cards (90) rejects; repeated declines block on their own. Each result's details carries the computed value, the key and the window, so a reviewer sees the number, not only the verdict. Windows end at the evaluation time, which is what makes backtests reproduce live results.
What a reviewer sees
A velocity_review case with the four results and their values. The subject's timeline in the admin console lists the events behind them. The reviewer's reject becomes the decision; what the integrating system does with a rejected subject (hold payouts, close the account) is its call.
How to adapt it
Group by a promoted key instead of the subject (groupBy: ip with groupValuePath) to catch one device behind many accounts; add avg or max rules for unusually large single payments; use attributes filters for your own event vocabulary (channel, country). Dry-run a rule against a stored subject (POST /rule-definitions/:key/dry-run) and backtest a draft over recorded evaluations before publishing a new threshold.
Run it
bash
pnpm policy:import examples/policies/transaction-velocity --publishThe fixture (expect.json) ingests four payments of 1 500 over the last 90 minutes (two cards, one declined): the 24-hour sum of 6 000 fails, the burst, decline and card rules pass, the score of 50 opens a velocity_review case, the check rejects it and the run completes with a manual reject.