Routing Rules
38 min
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 { "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 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 { "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 { "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 { "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 ) { "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 { "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) 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" ) 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" ) 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) 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 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 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 { "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 { "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 { "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 { "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 { "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 onfailure action is optional if omitted and the rule fails, no output is produced — which means all acquirers default to 0 for that rule the expression 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 )
