Need help setting up or customizing iso8583sim? Email us at [email protected] Get in touch →

iso8583sim 1.5: from messages to networks

Five releases since the last post. iso8583sim now speaks bytes, runs a mock issuer you can load test, handles PIN blocks and MACs, converts between versions, and works from your AI assistant. Here's the tour.

The last post ended on a line I liked: the messages are real; only the network is imaginary. That was true, and it was also the library’s biggest limitation. iso8583sim could build and parse any message you asked for, but the moment you wanted to send one to something, you were on your own.

Five releases later, that gap is closed. iso8583sim now puts messages on the wire in the byte layouts real hosts use, can stand in for the issuer on the other end, and covers the security fields that every serious test harness eventually needs. Here’s what changed, and why.

Messages are bytes

Inside the library, a message has always been a string: an ASCII MTI, a hex bitmap, binary fields as hex. That’s convenient to read and to diff, and it’s not what travels between an acquirer and a switch. Real links use a binary bitmap, often packed BCD for numbers and lengths, and sometimes EBCDIC if there’s a mainframe involved.

The new iso8583sim.wire module converts between the two, without changing anything you already wrote:

from iso8583sim.core.builder import ISO8583Builder
from iso8583sim.core.parser import ISO8583Parser
from iso8583sim.core.samples import sample_message
from iso8583sim.wire import WireFormat

# A sample VISA authorization as BCD: 63 bytes
data = ISO8583Builder().build_bytes(sample_message(), WireFormat.bcd())
message = ISO8583Parser().parse_bytes(data, WireFormat.bcd())

The same authorization is 100 bytes as ASCII text and 63 as BCD. Every part is configurable: the MTI, bitmap, length prefixes, numbers and text can each be ASCII, EBCDIC, BCD or binary. Byte layouts are exactly where bugs hide, so the encodings are checked byte for byte against an independent implementation, pyiso8583. That check caught a padding bug the round-trip tests had missed, which is the whole point of having a second opinion.

A mock issuer you can talk to

Once messages are bytes, you need something to send them to. iso8583sim serve runs a mock issuer host over TCP. You describe how it should behave in a small rules file, and the first rule that matches decides the answer:

rules:
  - match: {amount_gt: 100000}   # over 1,000.00
    respond: "51"                # insufficient funds
  - match: {pan_prefix: "4000"}
    respond: "05"
    delay_ms: 500                # a slow decline
  - match: {pan_prefix: "4999"}
    action: drop                 # never answer
  - respond: "00"                # approve everything else
iso8583sim serve --port 8583 --rules rules.yaml --format bcd --tpdu 6000010000

That drop rule is the one I’d write first. Everyone tests the approval. Far fewer people test what their code does when the issuer simply never answers, and that’s the failure that shows up at 2am.

On the sending side, iso8583sim send fires one message and shows the decoded response, and iso8583sim load generates traffic and reports throughput, latency percentiles and response codes. From Python, ISO8583Client matches responses to requests by STAN, so dozens of requests can be in flight on one connection and come back in any order. On a laptop, with the host in its own process, a load run pushes around 20,000 round trips a second.

The security fields

Sooner or later a test needs field 52 (the PIN block) or field 64 (the MAC), and until now you had to hand-roll them. The new iso8583sim.security module covers ISO 9564 PIN blocks in formats 0, 1, 3 and 4, and ISO 9797-1 MACs, including the retail MAC most networks expect:

from iso8583sim.security import encrypt_pin_block

key = "0123456789ABCDEFFEDCBA9876543210"  # a test key
message = sample_message()
message.fields[52] = encrypt_pin_block("1234", "4111111111111111", key)

raw = ISO8583Builder().build_with_mac(message, key)  # MAC in field 64

Change one character of that message and verification fails, which is exactly what you want a test to prove. The usual caveat applies, loudly: this is for test keys and simulated traffic. Real PINs and real keys belong in an HSM.

Versions, networks, and honest validation

Two smaller tools earn their place quickly. iso8583sim convert moves a message between the 1987, 1993 and 2003 versions, and tells you what it couldn’t carry over, rather than quietly truncating it. A PIN block, for example, can’t just be padded to a new size; it has to be re-encrypted. And iso8583sim validate --against all tells you which networks would accept a given message.

Building that second one meant taking a hard look at the validator’s network rules, and some of them didn’t survive. Two checks were rejecting perfectly valid messages: one demanded that VISA’s field 44 be hexadecimal, another that Mastercard’s field 48 start with MC. Neither is true of the real formats. The rules are now tied to published field descriptions, which makes validation stricter where it should be (field 22 codes, the structure of Mastercard’s field 48) and looser where it was simply wrong. If you’re upgrading, the upgrading guide lists every change like this.

Where you already work

Not every question needs code. iso8583sim explain turns a raw message into a plain-English summary, with an LLM or with --no-llm for an offline, rule-based one. There’s a REST API (iso8583sim web) for services that aren’t written in Python, with an interactive playground in the docs.

And there’s an MCP server. Add it to Claude, Cursor or any MCP client, and you can ask “why was this transaction declined?” or “generate a Mastercard authorization and the matching approval” and have the assistant parse, build and validate real messages to answer. It can even send to your local mock host, but only to hosts you explicitly allow, because an assistant with a TCP client and no guardrails is not a good idea.

Faster by default

The Cython extensions that roughly double parsing speed have been in the repository for a while. What I hadn’t noticed is that they never actually reached PyPI: the package build was quietly ignoring them. Since 1.5.0, pip install iso8583sim gets compiled wheels for Linux, macOS and Windows on Python 3.10 to 3.14.

Building those Windows wheels turned up something embarrassing: the iso8583sim command had never started on Windows at all, because of one unconditional import. It does now. That’s the quiet benefit of testing on more platforms: you find the bugs that nobody reported because nobody could get far enough to report them.

New docs, too

The documentation moved to a new home at iso8583sim.com/docs, with search, a Python API reference generated straight from the code, and the interactive REST reference. It builds from the library repository, so it can’t drift out of date the way the old copy did.

The short version

iso8583sim started as a way to build and parse messages. It’s now closer to a test bench: real byte layouts, a scriptable issuer, the security fields, and tools that meet you in the terminal, the browser or your AI assistant. Upgrade with pip install -U iso8583sim, start with the quick start, and if you only try one new thing, write that drop rule.

Try it yourself

Everything here works with the open-source library. One line to install.

$pip install iso8583sim
← All articles