---
title: Routing Rules
slug: routing-rules
docTags: 
createdAt: 2026-04-13T14:01:00.051Z
---

# Transaction Routing Rules

This guide explains how to write transaction routing rules that control which acquirer processes a given transaction. Rules are defined as JSON objects and evaluated at transaction time using the inputs described below.

## Overview

A transaction routing rule definition is a JSON object that defines conditions (expressions) and outcomes (actions). When a transaction arrives, the rules engine evaluates your rule against the transaction data and produces a **routing output** — an object mapping acquirer identifiers to numeric weightings. The platform then uses these weightings, combined with your routing strategy, to select an acquirer.

For your convenience, we have provided a UI for constructing rule definitions. This can be found embedded in our documentation on the 'Rule Builder' page.

### Data structure

Creating a new transaction routing rule will typically require three API calls: `POST v1/rules` for the rule's general details, `POST v1/rule-versions` for the rule's definition, and `POST v1/transaction-routing-rules` for transaction-routing specific information (in future the rules engine will be applied to other domains than just transaction routing, so this information is separate). Please refer to the API documentation for the request formats.

To change an existing rule's definition, you should create a new rule version, then update the `current_version` field on the rule (`PATCH v1/rules/:id`). Rule versions cannot be deleted, in order to maintain observability of historical routing decisions.

The rest of this page will focus on writing rule definitions.

### Routing output

Your rule must return an object where keys are acquirer identifiers and values are numeric weights. Currently supported acquirers:

| Key           | Acquirer                        |
| ------------- | ------------------------------- |
| `shift4`      | Shift4 (Visa, Mastercard, etc.) |
| `shift4_amex` | Amex via Shift4                 |

Please see the OpenAPI documentation for the latest list of valid acquirer identifiers.

Each value is a numeric weight (0 or undefined = do not route, >0 = eligible; larger numbers indicate greater preference).
The system will use these weights according to the rules routing strategy (`sorted` or `weighted_random`).

### Routing strategies

The routing strategy is configured for the rule outisde of the rule definition itself and determines how the weightings are interpreted:

- `sorted` — the acquirer with the highest weight is selected. We will fall back to lower-weighted acquirers if the preferred acquirer is unavailable, but acquirers with a weight of `0` will never be routed to.
- `weighted_random` — an acquirer is selected randomly, with probability proportional to its weight.

***

## Rule structure

A rule is a JSON object with the following fields:

```json
{
  "ruleName": "MyRule",
  "expression": "...",
  "localParams": [...],
  "operator": "And" | "Or",
  "rules": [...],
  "actions": {
    "onSuccess": { ... },
    "onFailure": { ... }
  }
}
```

### Fields

| Field         | Type              | Required | Description                                                                                                                                                                      |
| ------------- | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ruleName`    | string            | Yes      | A unique name for the rule. Used in error messages.                                                                                                                              |
| `expression`  | string            | No       | A boolean expression to evaluate. Required for leaf rules (rules without nested `rules`).                                                                                        |
| `localParams` | array             | No       | Scoped parameters — intermediate values computed before the expression is evaluated.                                                                                             |
| `operator`    | `"And"` or `"Or"` | No       | How to combine nested rules. Required when `rules` is present.                                                                                                                   |
| `rules`       | array             | No       | Nested child rules. Each child is a rule object with the same structure.                                                                                                         |
| `actions`     | object            | No       | What to return when the rule succeeds or fails. Although optional, if an action is not returned all acquirers will be weighted as `0` and the transaction will be unprocessable. |

***

## Expressions

Expressions are boolean-valued strings evaluated against the available inputs. The expression language is based on [expr-eval](https://github.com/silentmatt/expr-eval) with some extensions.

### Operators

| Category        | Operators                        | Example                                                         |
| --------------- | -------------------------------- | --------------------------------------------------------------- |
| Comparison      | `==`, `!=`, `>`, `>=`, `<`, `<=` | `binLookupResult.cardBrand == "Visa"`                           |
| Logical         | `and`, `or`, `not`               | `x > 0 and y > 0`                                               |
| Arithmetic      | `+`, `-`, `*`, `/`, `%`, `^`     | `messageBody.Transaction.TransactionDetails.TotalAmount * 0.15` |
| Ternary         | `? :`                            | `isAmex ? 1 : 0`                                                |
| Grouping        | `(...)`                          | `(a or b) and c`                                                |
| Property access | `.`                              | `binLookupResult.cardBrand`                                     |

**Important:** String comparisons are case-sensitive. `"Visa"` is not the same as `"visa"`.

### Literals

- Strings: `'hello'`
- Numbers: `42`, `3.14`, `0.5`
- Booleans: `true`, `false`
- Arrays: `[1, 2, 3]`, `['12-25', '12-26']`

### Arrays

Array comparisons are frequently useful - for example, to have a routing rule based on a cardholder's authentication method you would need to access `messageBody.Environment.Cardholder.Authentication`, which is an array field.

The [lodash](https://lodash.com/docs) JavaScript package is available to the rules engine via a variable named `_` - this contains all of the regular JavaScript array handling methods as well as `lodash`'s extensions. It is important to note that methods requiring callbacks (e.g. `_.filter(collection, predicate)`) must be done with the `iteratorCallback` utility documented below.&#x20;

### Built-in functions

The expression language includes these built-in functions:

| Function         | Description                                                                                                               |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `min(a, b, ...)` | Smallest value                                                                                                            |
| `max(a, b, ...)` | Largest value                                                                                                             |
| `abs(x)`         | Absolute value                                                                                                            |
| `round(x)`       | Round to nearest integer                                                                                                  |
| `roundTo(x, n)`  | Round `x` to `n` decimal places                                                                                           |
| `floor(x)`       | Round down                                                                                                                |
| `ceil(x)`        | Round up                                                                                                                  |
| `length(x)`      | Length of a string or array                                                                                               |
| `indexOf(x, a)`  | First index of `x` in string or array `a`, or `-1`                                                                        |
| `if(c, a, b)`    | Returns `a` if `c` is true, else `b` (note: both branches are always evaluated — prefer the ternary operator `c ? a : b`) |

***

## Available inputs

When your rule is evaluated, the following named inputs are available for use in expressions:

### `binLookupResult`

Data from the BIN lookup performed on the card number. Available fields depend on BIN data coverage, and any field may be absent. Common fields include:

| Field             | Type   | Example values                     |
| ----------------- | ------ | ---------------------------------- |
| `cardBrand`       | string | `"Visa"`, `"Mastercard"`, `"Amex"` |
| `cardCountryCode` | string | `"GB"`, `"US"`, `"FR"`             |
| `cardProductType` | string | `"consumer"`, `"business"`         |

### `messageHeader`, `messageBody`, `messageTrailer`

The raw nexo authorisation request fields. Refer to the nexo messaging specification for the full schema. These give you access to transaction-level details such as amount, currency, and merchant category code.

### `poiContext`

The point-of-interaction context, including merchant configuration and terminal details. Useful for merchant-level overrides.

***

## Local parameters

Local parameters let you define intermediate values to keep your expressions clean. Each parameter has a `name` and an `expression`. Parameters are evaluated in order and can reference earlier parameters.

```json
{
  "localParams": [
    {
      "name": "CardBrandIsAmex",
      "expression": "binLookupResult.cardBrand == 'Amex'"
    },
    {
      "name": "CardBrandIsVisaOrMastercard",
      "expression": "binLookupResult.cardBrand == 'Visa' or binLookupResult.cardBrand == 'Mastercard'"
    },
    {
      "name": "Shift4Weight",
      "expression": "CardBrandIsVisaOrMastercard ? 1 : 0"
    },
    {
      "name": "Shift4AmexWeight",
      "expression": "CardBrandIsAmex ? 1 : 0"
    }
  ]
}
```

***

## Actions

Actions define what the rule returns when it succeeds or fails. Two action types are available:

### `ReturnObject`

Returns a literal object. The `context` field is the object that will be returned. A transaction routing rule must use this action to return an appropriate object.

```json
{
  "actions": {
    "onSuccess": {
      "name": "ReturnObject",
      "context": {
        "shift4": 1,
        "shift4_amex": 0
      }
    }
  }
}
```

### `ReturnObjectFromParamKeys`

Returns an object where the values are resolved from local parameters or input names. This is useful when the weights are computed dynamically.

In the `context`, each key is an acquirer identifier and each value is the **name** of a local parameter (or input) whose computed value should be used.

```json
{
  "localParams": [
    {
      "name": "CardBrandIsAmex",
      "expression": "binLookupResult.cardBrand == 'Amex'",
    },
    {
      "name": "CardBrandIsVisaOrMastercard",
      "expression": "binLookupResult.cardBrand == 'Visa' or binLookupResult.cardBrand == 'Mastercard'",
    },
    {
      "name": "Shift4Weight",
      "expression": "CardBrandIsVisaOrMastercard ? 1 : 0",
    },
    {
      "name": "Shift4AmexWeight",
      "expression": "CardBrandIsAmex ? 1 : 0",
    }
  ],
  "actions": {
    "onSuccess": {
      "name": "ReturnObjectFromParamKeys",
      "context": {
        "shift4": "Shift4Weight",
        "shift4_amex": "Shift4AmexWeight"
      }
    }
  }
}
```

### `onFailure`

You can also define an `onFailure` action, which runs when the rule's expression evaluates to `false`. If no `onFailure` action is defined and the rule fails, no output is produced for that rule (equivalent to all weights being `0`).

```json
{
  "actions": {
    "onSuccess": {
      "name": "ReturnObject",
      "context": { "shift4_amex": 1 }
    },
    "onFailure": {
      "name": "ReturnObject",
      "context": { "shift4": 1 }
    }
  }
}
```

***

## Nested rules

Rules can contain child rules combined with `And` or `Or` operators. This lets you build complex conditions without writing a single long expression.

With `And`, all child rules must pass for the parent to succeed. With `Or`, at least one must pass.

```json
{
  "ruleName": "GbAndConsumer",
  "operator": "And",
  "rules": [
    {
      "ruleName": "CardCountryIsGB",
      "expression": "binLookupResult.cardCountryCode == \"GB\""
    },
    {
      "ruleName": "CardSegmentIsConsumer",
      "expression": "binLookupResult.cardProductType == \"consumer\""
    }
  ],
  "actions": {
    "onSuccess": {
      "name": "ReturnObject",
      "context": { "shift4_amex": 1 }
    },
    "onFailure": {
      "name": "ReturnObject",
      "context": { "shift4": 1 }
    }
  }
}
```

***

## Utility functions

A set of utility functions is available under the `Utils` namespace for time-based and date-based routing.

All of the date functions optionally take a timezone string as their last input parameter. This must be an IANA time zone identifier (e.g. `Asia/Kolkata`) or a time zone offset (e.g. `+08:30`). The time zones will default to UTC, which is generally undesireable (you will typically want daylight savings time to be taken into account).

### `Utils.isInWeekdayIndexArray(dayIndexArray, timezone?)`

Returns `true` if the current day of the week (in the given timezone) is in the provided array. Days are numbered 1 (Monday) through 7 (Sunday).

```javascript
Utils.isInWeekdayIndexArray([6, 7])           // Saturday or Sunday, UTC
Utils.isInWeekdayIndexArray([6, 7], "Europe/London")  // in London timezone
```

### `Utils.isInMonthDayArray(dateArray, timezone?)`

Returns `true` if today's date is in the provided array (formatted as `"MM-DD"`).

```javascript
Utils.isInMonthDayArray(["12-25", "12-26"])   // Returns TRUE if today is Christmas Day or Boxing Day
```

### `Utils.isOnExactDateArray(dateArray, timezone?)`

Returns `true` if today's date including the year is in the provided array (formatted as `"YYYY-MM-DD"`).

```javascript
Utils.isOnExactDateArray(["2026-04-03", "2026-05-01"])
```

### `Utils.isBetweenTwoTimes(start, end, timezone?)`

Returns `true` if the current time is between `start` and `end` (inclusive). Times are in `HH:mm:ss.sssssssss` format (minor units are optional and default to 0).

```javascript
Utils.isBetweenTwoTimes("09:00", "17:00", "Europe/London")
```

### `Utils.nowZonedDateTimeISO(timezone?)`

Returns the current date-time as an ISO string in the given timezone. Primarily useful in local parameters for advanced date logic.

### `Utils.substring(input, start, end?)`

Returns a substring. Equivalent to JavaScript's `String.prototype.substring`.

### `Utils.iteratorCallback(expression)`

A helper for calling functions that require a callback as input, such as many of the `lodash` methods. Takes as input an expression in string form, following the same syntax as any other expression in the rules builder.&#x20;

NB: the evaluator for this doesn't have access to the full context, so e.g. the other utilities above cannot be used, and neither can the local parameters; if this is a problem you should evaluate an intermediate value in a parameter.

```javascript
_.some(poiContext.toro_config.supported_card_entry_modes, Utils.iteratorCallback('element == "integrated_circuit_card"'))
```

***

## Examples

### Example 1: Route all transactions to a single acquirer

The simplest possible rule. Always routes to Shift4.

```json
{
  "ruleName": "AlwaysShift4",
  "expression": "true",
  "actions": {
    "onSuccess": {
      "name": "ReturnObject",
      "context": {
        "shift4": 1
      }
    }
  }
}
```

### Example 2: Route by card scheme

Route American Express cards to the Amex acquirer and Visa/Mastercard to Shift4, using local parameters and `ReturnObjectFromParamKeys`.

```json
{
  "ruleName": "SchemeBasedRouting",
  "localParams": [
    {
      "name": "CardBrandIsAmex",
      "expression": "binLookupResult.cardBrand == 'Amex'"
    },
    {
      "name": "CardBrandIsVisaOrMastercard",
      "expression": "binLookupResult.cardBrand == 'Visa' or binLookupResult.cardBrand == 'Mastercard'"
    },
    {
      "name": "Shift4Weight",
      "expression": "CardBrandIsVisaOrMastercard ? 1 : 0"
    },
    {
      "name": "Shift4AmexWeight",
      "expression": "CardBrandIsAmex ? 1 : 0"
    }
  ],
  "expression": "CardBrandIsAmex or CardBrandIsVisaOrMastercard",
  "actions": {
    "onSuccess": {
      "name": "ReturnObjectFromParamKeys",
      "context": {
        "shift4": "Shift4Weight",
        "shift4_amex": "Shift4AmexWeight"
      }
    }
  }
}
```

### Example 3: Routing based on card country and segment

Route UK consumer cards to one acquirer and everything else to another, using nested rules.

```json
{
  "ruleName": "GbConsumerRouting",
  "operator": "And",
  "rules": [
    {
      "ruleName": "CardCountryIsGB",
      "expression": "binLookupResult.cardCountryCode == 'GB'"
    },
    {
      "ruleName": "CardSegmentIsConsumer",
      "expression": "binLookupResult.cardProductType == 'consumer'"
    }
  ],
  "actions": {
    "onSuccess": {
      "name": "ReturnObject",
      "context": { "shift4_amex": 1 }
    },
    "onFailure": {
      "name": "ReturnObject",
      "context": { "shift4": 1 }
    }
  }
}
```

### Example 4: Time-based routing

Route transactions on weekends or holidays to a different acquirer. Uses `Or` nesting and utility functions.

```json
{
  "ruleName": "WeekendOrHolidayRouting",
  "localParams": [
    {
      "name": "holidays",
      "expression": "['12-25', '12-26', '01-01']"
    }
  ],
  "operator": "Or",
  "rules": [
    {
      "ruleName": "IsHoliday",
      "expression": "Utils.isInMonthDayArray(holidays)"
    },
    {
      "ruleName": "IsWeekend",
      "expression": "Utils.isInWeekdayIndexArray([6, 7])"
    }
  ],
  "actions": {
    "onSuccess": {
      "name": "ReturnObject",
      "context": { "shift4_amex": 1 }
    },
    "onFailure": {
      "name": "ReturnObject",
      "context": { "shift4": 1 }
    }
  }
}
```

### Example 5: Even split for weighted random routing

When using the `weighted_random` strategy, give two acquirers equal probability.

```json
{
  "ruleName": "EvenSplit",
  "expression": "true",
  "actions": {
    "onSuccess": {
      "name": "ReturnObject",
      "context": {
        "shift4": 1,
        "shift4_amex": 1
      }
    }
  }
}
```

To weight 70/30 instead, you would use `"shift4": 0.7, "shift4_amex": 0.3`.

***

## How weighting works

The acquirer weights in the output will be used to determine the appropriate routing according to the strategy you have set for the rule.

- If the strategy is `sorted`: the acquirers will be sorted in order of preference, and the highest-weighted acquirer will always be used unless it is unavailable, falling back to the next-highest weighted, and so on.
- If the strategy is `weighted_random`: an acquirer will be randomly chosen, with the probability determined by the weightings. This is useful if you want to divide transactions between multiple acquirers according to a ratio.

***

## Common pitfalls

- **String comparisons are case-sensitive.** `"Visa"` and `"visa"` are different values. `binLookupResult.cardBrand` is in Titlecase.
- **Every acquirer key you omit defaults to 0.** If your rule only returns `{ "shift4_amex": 1 }`, all other acquirers get a weight of `0`.
- **An&#x20;**`onFailure`**&#x20;action is optional.** If omitted and the rule fails, no output is produced — which means all acquirers default to `0` for that rule.
- **The&#x20;**`expression`**&#x20;field is required for leaf rules** (rules without nested `rules`). Parent rules that use `operator` and `rules` do not need an `expression`.
- **Local parameter names must not collide with input names** (`messageHeader`, `messageBody`, `messageTrailer`, `poiContext`, `binLookupResult`).
