Skip to main content
View as Markdown
Version: v2

How Routes Work

A route decides which gateway profile ODUS attempts for a card payment, and where it fails over to after a decline. Unlike a cascade, a route can read the payment: the card's brand, BIN, issuer and funding type, the amount and currency, who initiated it, and your own metadata. So you can send cards from one issuer to one gateway and everything else to another.

Routes apply to card payments only, and are managed through the Core API v2. To create one and point payments at it, see Routing card payments with a route.

Versions​

A route is a named container. Its decision tree lives in a version, and a version never changes once published. To change a route, publish a new version and activate it. To roll back, activate an earlier one.

Only the active version routes payments. Each payment records the version it was routed by, so activating another version later does not change how an earlier payment is explained.

The decision tree​

A tree is built from four kinds of node:

NodeWhat it does
conditionTests the payment and follows the first branch that matches, or else.
splitSends a fixed share of payments down each of its outputs, such as 20% and 80%.
poolTries its children in turn, and fails over to the next child after a decline.
gatewayAttempts the payment at one gateway profile.

Nodes nest freely: a branch or a split's output can lead to a pool, and a pool can hold conditions.

{
"type": "condition",
"branches": [
{
"when": { "field": "card.issuerName", "op": "contains", "value": "Capital One" },
"then": {
"type": "pool",
"strategy": "ordered",
"children": [
{ "type": "gateway", "gatewayProfileId": "gwp_2mXGTxXIkqv4uSdbm8DJCzvHUIu" },
{ "type": "gateway", "gatewayProfileId": "gwp_3RTHQUROb9Bt2dJwhLmTGEykKEe" }
]
}
}
],
"else": { "type": "gateway", "gatewayProfileId": "gwp_3RTHQUROb9Bt2dJwhLmTGEykKEe" }
}

Cards issued by Capital One go to the first gateway, and fail over to the second after a decline. Every other card goes to the second gateway directly.

Conditions​

A condition is a single comparison, { "field", "op", "value" }, or all, any and not over other conditions.

FieldOperatorsValue
card.brandeq ne in nin existsA card brand, such as visa
card.binstartsWith existsA prefix of 1–8 digits, or a list of them
card.issuerNameeq ne in nin contains existsThe issuing bank's name
card.issuerCountryeq ne in nin existsAn ISO 3166-1 alpha-2 code, such as US
card.fundingTypeeq ne in nin existscredit, debit, prepaid or charge
amounteq ne gt gte lt lteA number in minor units
currencyeq ne in nin existsA currency code, such as usd
initiatoreq ne in nin existscustomer or merchant
statementDescriptoreq ne in nin contains existsThe payment's statement descriptor
statementDescriptorSuffixeq ne in nin contains existsThe payment's statement descriptor suffix
metadata.<key>eq ne in nin contains existsThe value of <key> in the payment's metadata

in and nin take a list, and exists takes no value. Metadata compares exactly; every other field ignores case.

statementDescriptor and statementDescriptorSuffix are the values the payment carries: the ones you sent when you created it, or else those of the first product in its cart. When the payment has no value for the field, a condition on it does not match, just as an unknown card field does not.

Branches​

A condition tests its branches in order and follows the first one that matches. When none matches, it follows else. Without else, nothing is attempted from that node.

A matched branch is final

Once a branch matches, the condition never tries another of its branches or its else — not when the gateway in that branch declines, and not when it is inactive. What happens next depends on the node around the condition: inside a pool, the pool moves on to its next child; at the top of the tree, the route ends. To fail over within a branch, put a pool inside it, as the example above does.

Splits​

A split sends each payment down one of its outputs, in proportion to each output's percent. Use it to compare two setups on live traffic, or to move payments to a new gateway a share at a time:

{
"type": "split",
"outputs": [
{
"percent": 20,
"then": { "type": "gateway", "gatewayProfileId": "gwp_2mXGTxXIkqv4uSdbm8DJCzvHUIu" }
},
{
"percent": 80,
"then": {
"type": "pool",
"strategy": "ordered",
"children": [
{ "type": "gateway", "gatewayProfileId": "gwp_3RTHQUROb9Bt2dJwhLmTGEykKEe" },
{ "type": "gateway", "gatewayProfileId": "gwp_2mXGTxXIkqv4uSdbm8DJCzvHUIu" }
]
}
}
]
}

About 20% of payments go to the first gateway alone. The other 80% go to the second gateway, and fail over to the first after a decline.

A split has 2 to 20 outputs. Each percent is a whole number from 1 to 99, and the percentages of one split add up to 100.

The output is chosen from the payment, not drawn again each time:

  • Authorizing the same payment again sends it down the same output, as long as the split and its percentages have not changed.
  • Each payment is chosen on its own. A customer's next payment, such as a subscription renewal, may follow a different output.
  • The percentages hold across many payments. Over a handful of payments, the shares can differ noticeably from them.
  • Two splits in one tree choose independently.
The chosen output is final

A split never sends a payment down a second output — not when a gateway in the chosen output declines, and not when it is inactive. As with a matched branch, what happens next depends on the node around the split. To fail over within an output, put a pool inside it, as the example above does. To fail over across every gateway, use a weighted pool instead.

Pools​

A pool tries its children in an order set by its strategy:

StrategyOrder
orderedThe order the children are written in.
roundRobinWritten order, starting at a position that moves on from one payment to the next.
weightedA random order, in proportion to weights: one weight from 1 to 100 per child.

A weighted pool picks its whole order at once, so failover after a decline also follows the weights. To send a fixed share of payments down separate paths, with no failover between them, use a split.

maxAttempts limits how many gateway attempts a pool makes, including those made by nodes inside it. When it is reached, the pool's remaining children are not tried.

Skipped is not declined​

A node that was never attempted is skipped. A gateway that was attempted and refused the payment declined. A pool moves on after either, but they are not the same:

SkippedDeclined
ExamplesAn inactive gateway profile, a condition with no matching branchThe gateway refused the payment
Creates a transactionNoYes
Counts toward maxAttempts and the attempt limitNoYes
If nothing else is attempted, the route ends asnot_routeddeclined

A gateway profile that is inactive when the payment is authorized is skipped, even though it was active when the version was published. If every gateway a payment can reach is skipped, nothing is attempted. The payment stays open and its latestMessage says so.

When card data is unknown​

ODUS reads the issuer name, issuer country and funding type from the card's BIN. They are not available for every card, and for some saved cards they are filled in later. A field ODUS does not have is unknown, and a comparison against an unknown field is neither true nor false.

An unknown field never matches

A branch whose condition is unknown is not taken, so the payment moves on to the next branch or else. This also holds for negative operators: ne, nin and not do not match an unknown field either. A card with no known issuer country is not matched by { "field": "card.issuerCountry", "op": "ne", "value": "US" }, and nothing reports an error.

To decide what happens to those cards yourself, test for them with exists, which is always true or false:

{ "not": { "field": "card.issuerCountry", "op": "exists" } }

all is false as soon as one of its terms is false, and any is true as soon as one is true, whatever the others are. Only when the result depends on an unknown term is the whole condition unknown.

ODUS may also hold fewer digits of a card's BIN than your prefix has. When the digits it holds agree with your prefix, startsWith is unknown, because the card may or may not be in that range.

A metadata key the payment does not have is unknown too.

Rules every route follows​

These hold across the whole tree, and no node can turn them off:

  • One attempt per gateway profile. A payment is attempted at each gateway profile at most once. A second node naming the same profile is skipped.
  • At most 11 attempts. One authorization makes at most 11 gateway attempts in total.
  • No failover after an unknown outcome. When a gateway gives no answer, it may have charged the card, so the route stops there.
  • A pending attempt ends the route. When an attempt is left waiting on the customer or the gateway, such as for 3D Secure, the route ends at that gateway and does not move on.
  • No failover without time for it. When too little time is left in the request for another attempt, the route stops instead of starting one. The first attempt is always made.

Automatic retries under a retry policy do not go through the route: each retry uses the gateway its retry step names.

Limits​

LimitValue
Nodes in one version100
Branches in one condition20
Children in one pool20
Outputs in one split2–20
percent on a split's output1–99
Terms in one branch's condition, all, any and not included50
Values in one in, nin or startsWith list1,000
maxAttempts on a pool1–11