Post
🇬🇧 EN 🇪🇸 ES

Routing ISO 8583 Without Pretending to Be a Payment Switch

Routing ISO 8583 Without Pretending to Be a Payment Switch

fluxrig 0.7.0 is out, and the source is on GitHub. It is the payment-switch release, and since that name promises far more than the release delivers, I would rather start by drawing the boundary than by listing features.

What shipped

The centrepiece is the Conductor: a gear that routes each request across a tree of strategy nodes (failover, round_robin, least_loaded), correlates the reply back to the terminal that sent it, and surfaces timeouts on a dedicated error port rather than discarding them silently.

Two details a practitioner will ask about immediately. Routing is an ordered table of predicates over decoded fields, first match wins, with an explicit catch-all; the tutorial routes on the BIN. Correlation is keyed on a configured field tuple rather than on anything hard-coded, so on ISO 8583 you point it at iso8583.field.11 and get STAN matching, and on another protocol you point it somewhere else. Around it: a correlation engine with per-ticket TTLs, cross-Conductor handoff so a reply can return through a different node than the one the request left from, native TLS and mTLS on the ISO 8583 I/O path, and self-describing manifests for every gear.

There is a tutorial that builds a two-region payment switch out of those pieces: POS terminals and two independent card schemes in each region, traffic preferring its local region, failing over across regions when a scheme link degrades.

The ISO 8583 codec is built on moov-io/iso8583, and that is not an incidental dependency. It is the layer I trust to be correct about the parts of this protocol that are genuinely fiddly, which is what lets the rest of the system be about routing instead of about bitmaps.

What this is not

fluxrig is a long way from being a full payment switch, and for now it is not trying to be.

No settlement, no clearing, no reconciliation, no dispute handling, no HSM key ceremony, no scheme certification. Those are large problems, and the products that solve them properly carry decades of accumulated correctness that I am not going to hand-wave past. If you need a certified switch, buy a certified switch.

jPOS is where I learned most of what I know about this protocol, and a great deal of the payments world still runs on it more than twenty years on. A growing part of it now runs on moov as well, whose ISO 8583 library this project depends on. Both earned that position in production, and nothing here is an attempt to replace either.

Where I keep finding it useful is the awkward work around a system rather than the system itself. A test harness in front of a real link. A migration where two systems run in parallel and their responses get compared. A failover somebody has to demonstrate to a regulator. A lab where traffic must be sunk and answered so the thing upstream can be exercised at all. In my experience those get solved with bespoke glue: written for one occasion, and then maintained for years.

And it is not really about payments either

Payments is where I aimed it, because that is a domain I know and where the failure modes are unforgiving. But the Conductor does not know what ISO 8583 is. It routes structured messages and correlates replies; the codec is the part that speaks the protocol.

There is prior art worth naming. jPOS’s Common Message Format had the right instinct back in 2009: agree on one internal representation instead of every project inventing its own, and publish it for anyone to adopt rather than keeping it proprietary. Its vocabulary stayed inside payments, which is entirely reasonable given where it came from and what it was for.

fluxrig’s internal message is deterministic CBOR (RFC 8949) carrying whatever the codec decoded, with no domain vocabulary of its own. That is the mechanical reason the routing layer above it can stay indifferent to the protocol underneath: there is nothing about authorizations in the envelope for it to know.

While on the subject of neighbours: I came across Coreflux after fluxrig already had its name, and it is worth a look if MQTT is your world. A broker, a control panel, and a scripting language for automating what flows through it. The shared syllable is just a crowded word doing its job; flux turns up everywhere from FluxCD to InfluxDB’s query language because it only means flow. The name is also filed as a trademark in Uruguay.

The projects differ in substrate rather than in degree, which is the more interesting part. Coreflux is built around publish and subscribe: a message goes out and whoever cares receives it. The Conductor’s problem is the opposite shape. A request goes to exactly one destination and one specific reply has to find its way back to exactly one caller, which is why correlation and timeouts are the hard part here rather than delivery.

The same shape works for anything with a request and a reply that can be decoded into a structured message: telemetry from field devices, industrial protocols, an internal dialect nobody has tooling for. Same routing predicates, same correlation key pointed at a different field, same failover between destinations. The payment switch is one instance, not the point.

The diagram is generated, not drawn

This release added fluxrig scenario viz, which generates an interactive LikeC4 topology model from the scenario file itself. The diagram below is the tutorial’s two-region switch, produced from the same YAML that runs it. Drag to pan, scroll to zoom, click a node to focus its neighbours.

The point is not that the picture looks good. It is that it cannot quietly become wrong. Anyone who has worked in operations knows the feeling of opening the architecture document and not believing it. JAAB made that case properly in the architecture-as-code evolution: change a wire in the scenario and the diagram changes with it, or the build fails.

Proving it

The invariant is easy to state and unpleasant to violate: a reply must always return to the terminal that sent it, never to another one. Get that wrong under load and you have handed one cardholder another cardholder’s authorization.

The validation is Robot Framework, and the choice is worth explaining. Its value here is that the test reads as a specification: “cut the scheme link, hold the load, restore it, assert nothing was dropped and nothing was cross-wired” is legible to a risk officer, an auditor and a developer at once, which matters when the property being proven is compliance-adjacent. It also composes with chaos tooling, so the suites drive Toxiproxy in front of the links and faults are injected from outside the system under test rather than simulated inside it.

From one real run on my own laptop, reproducible with make test-robot-conductor:

ScenarioVolumeCorrelationTPSp99
Mixed multi-scheme30 terminals × 100 txns0 cross-wired, 0 failed~1,258~103 ms
High concurrency60 terminals × 150 txns0 cross-wired, 0 failed~1,037~110 ms

Cutting one scheme mid-load and restoring it: the affected BIN saw 400 approvals and 300 declines across 700 transactions, nothing dropped and nothing cross-wired, while the unaffected BIN completed all 700 approvals, fully isolated from the fault.

Read those latencies carefully, because they are easy to misread as the switch’s overhead. They are not: this is a single machine on loopback, the schemes and terminals are simulators, there is no network and no real scheme processing time, and the load generator competes for the same CPU. Treat it as correctness-under-load evidence, not as performance.

Which is the point. The throughput is not the interesting column. The zero in the correlation column is, and so is the fact that you can run the command and disagree with me.

Why this connects to legacy modernization

I have written before about how I got into fintech and the belief I took from it: never build from scratch. That is why fluxrig is a set of composable gears rather than a product that wants to replace anyone’s core.

Modernizing a platform that has been in production for decades is not a translation exercise, and the hardest part is rarely the new system. It is running the old and the new side by side long enough to trust the swap: mirroring real traffic, comparing responses, bounding the cutover, showing somebody who signs off on the risk that nothing was lost. That is routing and correlation work.

The same argument from the business side, with the client work behind it, is on the JAAB blog: technological obsolescence and a real legacy modernization in Latin America with EBS.

Where it stands

The status table is in the tutorial itself, roadmap included: a durable store tier, encryption at rest, per destination circuit breakers, peer heartbeats. Until the durability tier lands, regulated data belongs in memory, and the documentation says so rather than leaving anyone to find out.

If you are wrestling with a link that needs to be somewhere it currently cannot go, I would be glad to hear about it: get in touch through JAAB.

This post is licensed under CC BY 4.0 by the author.