Routing systems need to ingest data, make decisions, execute those decisions, and then learn from the transactions they enable. These aren’t isolated components. They’re necessary pieces that work together to optimally route payments. Some pieces you might build yourself, and some you might outsource. Many companies have some version of these components, but the biggest difference between a good routing system and a great one is how well these components are integrated with each other.
The best routing systems fulfill loops that look something like this:
There’s quite a bit going on beneath the surface of that flow, so we’ll break it all down by layer. We’re going to start by talking about data, but know that our next chapter will go into more detail on what to collect, and the signal you actually get from that information.
You can only optimize for what you can observe. You need solid BIN, risk, and pricing data, and the quality of that data sets the upper bound for routing performance. This means your system needs access to several things.
This information is used to make downstream routing decisions (which we’ll cover in a moment). The sources for this data, as well as the granularity and quality of it, depend heavily on who you partner with to process payments. It also depends on the infrastructure you have for ingesting and storing the data. For example, you might collect customer and device information on your own, but then use a third party for risk analysis data, which you then need to pull into your own systems.
Processing costs are a big part of the decision layer too but we’re going to talk about them now. They’re arguably the most difficult to obtain or calculate ahead of a transaction (there are lots of ways to get data like device information and BIN details). Part of the challenge is that processing costs aren’t really known until after a transaction is complete. There are numerous variables (e.g., card type, purchase location) that can impact the cost after a transaction is sent out for processing. Your PSP’s standard pricing gives you a starting point, but that can change based on the path the transaction takes across the payment rails.
On top of all that, PSP pricing models can either help or hinder your projections. Blended pricing, where there’s a single fee that includes all the others (e.g., processor fee, interchange fee), doesn’t really give you the granularity you need to make intelligent routing decisions. Because everything is packed together, you don’t get visibility into scheme fees, interchange fees, etc. The pricing model that does give you this visibility, and that’s often more useful, is IC++ (Interchange Plus Plus).
IC++ separates processing costs into three parts: interchange (paid to the cardholder's issuing bank), scheme fees (paid to Visa or Mastercard), and the PSP's markup. The PSP markup is usually predictable. The other two layers are where the variability lives. Interchange can vary by card type, geography, channel, authentication status, and merchant category code (MCC). Scheme fees also vary based on whether the transaction is crossborder or domestic, currency mismatches, and other factors. Having visibility into all of these variables enables you to make more informed routing decisions.
This all might seem like unnecessary detail but what you do with this information is hugely impactful. Ideally, you build an internal reference for how transactions behave and how much they cost across PSPs, card networks, etc. This makes it possible to generate projections for future transactions. When transactions are settled, you can compare your projections with the real costs and use that information to improve future estimates. This all relates to historical data, so let’s cover that briefly now.
As transactions are processed, you grow the size of your historical data (whether payments succeed or fail, the reason for those outcomes, etc.). It’s critical to collect and store this information because it feeds all of the other layers. What this looks like in practice varies, but at the most basic level, you’d likely have a database table that tracks this information. It’d include columns for things like the transaction amount, BIN data, PSP, card network, processing fees, a timestamp, the outcome (response from the PSP), whether the transaction succeeded or failed, failure reasons, etc. It should give you a detailed picture of every transaction.
This is closely tied to the decision layer (covered next), but what historical data enables is the ability to query your own dataset so you can route new transactions to the best possible PSP. The flow might look roughly like this.
Many of the challenges with this layer relate to data that’s fragmented, coarse, or stale. Each PSP and partner packages and shares data differently. There might be some similarities but you’re ingesting data from multiple sources, which needs to be normalized for you to benefit from it. You have to figure out how to build a unified view of this information that provides actionable insights.
Part of the issue is with the granularity of the data you receive. Some sources might only provide high level information, which means you miss out on issuer or BIN-level details. Routing at the country level or something similar isn’t optimal, and you need that lower level information to make the best routing decisions.
Payments happen in real time, which means you need to collect data and track performance at the same pace. Relying on batch processes like weekly or monthly ETLs (extract, transform, load) means you'll react slowly to changes in PSP performance, partner outages, etc. This leaves a lot of efficiency and performance optimizations unrealized.
There isn’t an exact playbook for normalizing data across PSPs and your other sources. It’s an exercise every company needs to do on their own. However, it’s not always that technically complex, it just takes a little time. PSPs are going to have similar data, so a large part of the exercise is mapping values across PSPs to single values within your own system. So for example, one PSP might define the amount of a transaction as amount, while another uses total_amount. To compare these values internally, you’d likely want a single column (named whatever you wanted) that gets sourced from the different values passed to you from your PSPs.
Let’s look at decline codes specifically to give you a better idea of what normalizing data looks like in practice. Each PSP has its own set of decline reasons and human-readable messages. Most of these are translations of the same ISO 8583 standard, which defines two-digit response codes (e.g., 51 means insufficient funds, 03 means invalid merchant) that flow through the card networks. Many PSPs expose these raw codes alongside their own. Stripe's API, for example, includes a network_decline_code field with the original ISO 8583 code. If you can capture this value across PSPs, you have a stable identifier for normalizing decline codes.
While not an exhaustive list, here are some data points you might want to normalize:
Chase and Chase Bank)There can be situations where PSPs don’t have equivalent values for certain data points. For example, Checkout.com’s reconciliation API provides full IC++ breakdowns for every transaction. This includes line items for interchange fees, scheme fixed fees, and scheme variable fees. Worldpay’s Net Settle Sales report on the other hand breaks interchange down per transaction, but aggregates scheme fees across total volume. There's no mention of scheme fees at the transaction level. This reporting mismatch creates a major blindspot. Scheme fixed fees are flat charges triggered by specific technical actions or network rules (like an authorization retry or an international routing penalty). When these fees are rolled up into a monthly total on Worldpay, you lose the ability to cross-reference an individual customer order against the exact fee it incurred. Without this row-by-row data link, you can’t isolate technical formatting errors that trigger network penalties, and you can’t easily A/B test smart-routing changes to see if they successfully lower transaction costs.
Let’s assume you have all the data you need. The next component to look at is the decision layer. This is where routing logic lives, and it determines how well you act on your data and how well you adjust to PSP variance. What’s so impactful about this layer is that you get to build logic that optimizes for what matters most to your business. That could be acceptance rates, reducing costs, cutting fraud, etc. In many cases, it’s a blend of these things but every business is different, and this layer supports the customization needed to route payments in ways that meet your specific needs.
For example, you might set up thresholds for sending high risk transactions to an alternative PSP that has better chargeback protection than your primary provider. You can define those thresholds based on almost any parameter that matters to you (customer location, transaction amount, the product being purchased, or a combination of all of those).
The logic at the decision layer takes many forms and is often a blend of static rules, weighted logic, probabilistic models, and experimentation frameworks. Maybe you build everything from scratch, or maybe you mix and match third-party solutions with in-house systems. What’s important is that you build something that closely ties your data with your routing logic. Both sides of that equation need to be high quality for this layer to function properly.
Static rules make routing decisions like “if a card is issued in Japan, route the transaction through Adyen”. They’re usually more straightforward and only really change when someone updates them manually. Many orchestration platforms support logic like this, and it’s fairly trivial to implement at the code level as well.
In its most basic form, weighted logic uses preconfigured percentages to route transactions. For example, when you have multiple PSPs, you might send a small subset of traffic through your secondary PSPs just to keep the integrations “warm”. Or when you go live with a new PSP, you might start by routing 1% of your transactions through it, and then slowly ramp up traffic to make sure everything is running smoothly.
Probabilistic models have been around for a while, and machine learning and AI have amplified their use. These models take multiple inputs (historical acceptance rates, BIN details, risk signals, etc.) and project PSP performance. This commonly means generating the probability of acceptance, but they can also include costs, latency, or fraud likelihood. ML and AI are increasingly used for these calculations, especially as the number of inputs and interactions grows, but simpler statistical approaches can work too.
From those projections, you can compute per-PSP scores and combine them with weights based on what matters most to your business. So for example, acceptance rate might count for 80%, processing fees 10%, and fraud risk 10%. The result might look like this:
| Acceptance | Fee | Chargeback | Score | |
|---|---|---|---|---|
| PSP A | 94% | 1.4% | 0.8% | 87.6 |
| PSP B | 89% | 2.2% | 0.6% | 81.4 |
| PSP C | 85% | 1.1% | 1.5% | 78.1 |
For this example, each input is normalized to a 0-100 scale before the weights are applied. This puts the fee on common ground with the acceptance rate. It also flips direction where lower is better, so low fees and low chargeback rates score high.
After you calculate the scores, you can compare them and route the transaction to the PSP with the highest value. If you're familiar with Blackjack or other card games, this whole process is similar to calculating the expected value for each action you can take during a hand. But instead of comparing hit vs. stand, you're comparing one PSP against another.
Experimentation is also a big part of the decision layer. If you don’t build this into your system, you miss out on improvements because you never test your current setup against alternatives. You might try sending a subset of transactions with a certain BIN through a new PSP to see what their acceptance rates look like. If they’re higher than your other PSPs, you could slowly ramp up transactions through that PSP to see if the higher rate continues. Maybe you end up moving a majority of your traffic to the new PSP, or maybe performance breaks down at a certain threshold so you stop sending transactions through that PSP altogether.
There are of course some pitfalls to pay attention to. Transaction volume is important to consider, especially when you’re projecting performance or calculating expected values. It might take millions of transactions in some cases to come up with meaningful values. It’s also possible to over-engineer your routing logic, which makes it brittle. For example, if you have a cascading set of 10 “if this, then that” rules, it’s difficult to parse results and you might not end up with meaningful sample sizes. On the other side, if your rules are too generic, you might miss out on segment level differences that have more impact on acceptance rates and revenue.
This layer is where theoretical optimization turns into real-world outcomes, and it’s often where value is either captured or lost. The execution layer includes everything from your PSP integrations to how you handle retries, fallbacks, and latency. It goes all the way down to individual API calls, and how your system drives transactions to completion. It’s important to get this layer right because a good routing decision that’s poorly executed is indistinguishable from a bad decision.
When you integrate with any PSP, there are things like reliability (uptime, latency, etc.), API structure, retries, and error handling that impact how individual transactions are executed. One of the primary challenges when building your execution layer is how you reconcile these different elements across PSPs within your own system. You need to be able to run transactions through each PSP, but you also need to track transactions and their outcomes across providers. For example, if a transaction fails with one PSP, you need to decide whether to retry it with that PSP or send it through a different provider. If you choose to reroute it, you have to make sure there’s no retry with the original PSP.
Most PSP APIs have built-in idempotency and logic that makes it safe to retry operations, but that idempotency is scoped to the individual PSP. That doesn’t help you in situations where you want to try a transaction through a different provider. For this, you need some kind of internal identifier for the transaction that can tie payment attempts and their outcomes across PSPs. On top of that, you need the retry logic itself that governs when to retry and how many times. These decisions aren’t trivial. Retrying too quickly can lead to repeated failures, while retrying too slowly can increase user drop-off. Similarly, retrying with the same PSP may succeed in some cases (e.g., transient network issues), while switching PSPs might be more effective in others (e.g., issuer-specific routing differences).
Fallback handling further determines how resilient your system is under failure conditions. When a PSP is unavailable or underperforming, the system needs to gracefully redirect traffic without introducing excessive latency or failure risk. Poorly implemented fallbacks can lead to cascading failures or inconsistent user experiences, particularly if multiple layers of retries and routing decisions aren’t coordinated.
This is the primary layer that separates static from adaptive routing systems. It feeds payment outcomes and other transaction metadata back into your routing system so you can track performance metrics and make better decisions. It enables your system to learn over time, adjust to transient anomalies, and identify long term trends. There are a few things that can make building this layer difficult:
Delayed signals just mean that there are times when the outcome of a payment isn't available immediately after routing decisions are made. Subscription-based businesses see this a lot when their dunning processes pick up past due payments, but settlement timing is a more general cause. Fee data in particular isn't finalized until after transactions settle, and settlement timing varies by PSP (some settle daily, others every two or three days). How you access this data varies as well. Newer PSPs typically expose it through an API, but legacy providers often require a manual download from a dashboard or an SFTP pull, which makes parsing more involved.
Attribution is about tying payment outcomes to routing decisions. Sometimes this is straightforward but it can get complicated. In a scenario where you retry a transaction through a second PSP, it can be difficult tying the eventual successful transaction to one or more of the routing decisions. It could have been the PSP, the retry timing, or maybe the authentication flow was more robust and that’s what enabled the payment to succeed. Without clear attribution, it’s difficult for the system to learn over time, and it can potentially cause future decisions to be based on flawed data or assumptions.
We covered a lot about data already but it comes up as an issue here as well. PSPs report on different schedules, and in different formats and levels of detail. One PSP might offer realtime, issuer-level insights, while another provides delayed, aggregated reports. This makes it challenging to collect and normalize data across PSPs, and then use it to inform future routing decisions.
When you start defining segments, there’s a risk that you actually go too far. This results in segments that are so granular that they don’t yield reliable or impactful data. There’s either so little data that making reliable conclusions is difficult, or so few transactions that there isn’t a meaningful revenue uplift.
It’s sort of self-evident but not completing the loop and feeding results back into your decisioning means the system never learns. Or if the loop is completed but only at superficial levels (e.g., tracking failed transactions but not why they failed), you miss out on the benefits you get from granular data.
Manual updates or tweaks to the system, especially as you scale, become unsustainable. The system needs to learn and adapt as independently as possible. You still need visibility into what’s happening and the resulting changes, but manually adjusting how the system works becomes difficult at scale.
You should also be careful of biased measurement based on how payment traffic is split. If you’re sending 99% of your traffic through a single PSP, then you’re likely getting some strong signals but just for that PSP. Counter to that, if you split traffic evenly across five PSPs, you get a broader view across providers, but less insight into individual ones. It really depends on your volume, your PSPs, your segments, and how you route payments. There isn’t a single best way, so it’s more about understanding your own system and knowing where things could be out of balance.
We’ve covered each piece individually but it’s important to look at the system as a whole, and where issues usually occur. Most teams know what they want to do at a conceptual level. The challenge lies in implementing a system that operates reliably under real-world conditions. This isn’t an exhaustive list, but here are some things to watch out for.
The main point here is that issues often spawn out of components being poorly connected. Even though each layer is responsible for a different piece of your routing system, you can’t run them in isolation. You might have data in one system, execution logic in another, etc., but each piece needs to flow into the next effectively. This can be difficult of course because there are so many moving parts.
We mentioned earlier that systems can only act on what they can observe. No matter how sophisticated your routing logic is, it can’t compensate for missing or low quality data. We also touched on some data points that you should pay attention to (e.g., BIN information, PSP performance). It’s time now to look deeper at what to track, but also the signals you get from this information.
This content is for general information and educational purposes only. It shouldn't be taken as legal, compliance, or tax advice. Evervault doesn't guarantee the completeness or accuracy of this content, and you should always consult with a lawyer, qualified security assessor (QSA), accountant, or similar professional for advice on any of the topics covered.