Protecting Indonesia’s Largest Digital Payments App

How we built Fraud Rules Service (FRS), the abuse mitigation engine which protects GoPay.

Protecting Indonesia’s Largest Digital Payments App

By Ujjawal Dixit

GoPay is Indonesia’s leading digital payments provider, and more than 50% of Gojek’s transactions happen through GoPay. With an ecosystem this large, there’s an obvious risk of abusive activities which we need to constantly monitor and mitigate.

Fraud Rules Service (FRS) is a rule-based engine that helps us do exactly that.

The engine is written in Clojure and uses gRPC protocol for communication.

Here’s what went into creating FRS and the major decisions that led to it:

✅ Choosing Redis as a data source to support velocity based rules

Keeping the history data in an effective and efficient manner is one of the key aspects of running any velocity rule engine.

In our analysis, we found preserving the whole data was an overkill. Instead, it was better to put in specific data fields which would act as anchors for taking the decision. This led us to the formation of a basic first rule in FRS.

ZSET as a data structure :

ZSET in Redis implements the mix of hash and sorted sets data structure, with one key holding many values in a sorted form, based on the score provided.

  • Sets property helped us in maintaining the idempotency
  • Sort feature on values for a key helped in maintaining the time window for evaluation of velocity-based rules
  • Hash property for fast access to the data field and SLA being maintained

✅ Filtering data based on the (.) notation

In earlier stages, we created some logic to extract data fields which were strongly bound to contract and not extendable. This was basically due to the data stream structure.

To mitigate this problem, we tried to make an effort towards generalisation of specifying the data field by using the JQ parser along with some performance improvements. This helped in leveraging the use of dot notation along with extra features provided by the parser library.

We use a Clojure library that implements the JQ parser logic.

✅ Not just velocity rules

Apart from supporting velocity rules, we also added the support of simple clauses and watchlist rules.

Simple Clause Rules are rules with simple conditions being violated. Example: The event amount is greater than a certain specified amount. These rules don’t need historical data.

Watchlist Rules are meant to mark the event as ‘allowed’ or ‘rejected’ if the entity is described in the watchlist. They constitute of two types: allow-list and deny-list.

Allow List Rules helps bypassing running other rules if the transaction has some entity that is allowed universally. Alternatively, Deny List Rules helps in rejecting the transaction if the transaction has some entity that is not allowed universally.

✅ Segregating Reads and Writes

We added behaviour-based rules by segregating reads (query) and writes (ingestion) to Redis historical data. This helped us in:

  • Reducing the time for Redis operations by excluding write instructions
  • Avoiding data duplication by re-using the data in other rules. The ingestion rules have specific catalog id which can be used in query-rule to address the specific data points

Removing the expired data: Every ingestion rule has a data expiry time window threshold. So, historical data will be removed whenever the ingestion request comes to Redis based on the time window in the rule.

✅ Running rules on multiple data points

We had several use cases that would be using multiple catalogs provided by ingestion rules. Example: The sum of the amount transacted by the user is greater than X dollars in the last Y days and the number of logins done by the user is also greater than Z.

For solving this problem, we added the query DSL in the query rules. So, creating two ingestion rules for storing amount and event timestamp, and using query rule to use these data points in AND condition would be enough.

✅ Moving towards generic APIs

One of the major problems in the ecosystem was extendability and abstractions. Creating generic endpoints helped us with faster integration and easy understandability. Now, all we need is the data in the JSON string. 😃

What's next for us?

  • Completely migrating to generic APIs
  • Open-sourcing the project 💚

Click here for more stories about how we build our Gojek #SuperApp.
Click here if you’d like to build it with us. 💚