Skip to content

Visa ISO 8583 over mTLS TCP

This is the production data plane for real fintech integrations: ISO 8583 over a mutually authenticated TLS socket. Complete Before you begin first — this transport needs a client certificate issued during onboarding and a configured behaviour for the card you plan to send.

Client-initiated authorization over this listener is planned, not yet available (#538). If you connect and write a 0100 / 0200 frame expecting a 0110 / 0210 back on the same socket, SMP does not yet dispatch it — there is no inbound framing loop routing fintech-initiated frames to SVE/SME, so unsolicited inbound bytes are logged and discarded.

The mTLS TCP connection is the delivery channel for the server-first REST round trip:

  1. You originate the authorization with a REST call — POST /transaction.
  2. The Visa engine (SVE) prepares the outbound ISO 8583 message.
  3. SMP writes that frame to you over your open mTLS TCP connection, then reads your ISO 8583 response frame off the same connection.
  4. SVE finalizes that response — applying any configured behaviour — and SMP returns the result in the REST response.

So your side of this connection acts as the responder: keep the socket open, read the frame SMP delivers, and write your response frame back.

The end-to-end round trip is not runnable yet (#533). On connect SMP reads one frame from you for the DE 32/33 cross-check before it registers the connection for delivery. A client that connects and immediately blocks on a read deadlocks with the concurrent REST call, which then finds no registered connection and returns 502. Until #533 lands, the connection only accepts deliveries after you have sent that connect-time cross-check frame (see Connection lifecycle).

The rest of this page is the connection and wire contract that the server-first flow and the planned client-initiated flow both rely on.

tcp.switchbench.com:11012 # prod
test-tcp.switchbench.com:11012 # test

Non-production environments use a single-level environment prefix on the host. The port selects the scheme and engine — 11012 is Visa, routed to the Visa network engine (SVE). Mastercard (11013) and HSMaaS (11014) have their own ports.

The port selects the engine; the client certificate selects the tenant. There is no per-tenant port: every Visa customer connects to the same host:11012, and Switchbench resolves your organization from the certificate you present.

Open a TLS socket to the port and present the client certificate issued to your org during onboarding (Certificate PEM + Private key PEM from the issuance screen — see Before you begin).

  • mTLS terminates at SMP, not at the edge. Customer connections reach the platform through an FRP edge (an AWS Lightsail server forwarding raw TCP to the Mac Mini host). That edge is a raw L4 passthrough — it never sees plaintext and injects no tenant metadata. The TLS handshake passes through it and terminates at the Message Processor (SMP).
  • The certificate must chain to the Switchbench CA root. The root is issued by KMS. The TLS handshake itself rejects a certificate that is missing, expired, self-signed, or does not chain to that root.
  • Tenant resolution happens after the handshake. Once TLS completes, SMP computes the certificate’s SHA-256 fingerprint and resolves your org_id from it via the Control Plane Service (GET /api/resolve-cert, a bounded cache on the hot path).
  • A revoked or unknown certificate is closed after the handshake, before any payload is processed. Such a certificate still chains to the CA root, so TLS succeeds; the /api/resolve-cert lookup then returns a revoked or unknown status and SMP drops the connection. Revoking a certificate in the dashboard, or suspending the org, takes effect on the next lookup.

Both directions use a length-prefixed frame:

[2-byte big-endian length][ISO 8583 bytes starting at the MTI]
  • The length header is big-endian and counts the payload bytes only — it does not include the 2 header bytes.
  • The payload is the raw ISO 8583 message beginning at the MTI. Any TCP length prefix is stripped before the message is decoded; do not include a second prefix inside the payload.
  • A 4-byte header variant exists in the framing layer, but 2 bytes is the platform convention for ISO 8583 and payShield — send and expect 2.
  • The reader reassembles partial reads until the full declared payload has arrived, so a frame split across TCP segments is handled transparently.
  • A frame whose declared length exceeds the configured maximum is rejected at the framing layer. What happens next depends on which phase the read is in — see Errors and rejections.

Implementation reference: switchbench_smp/src/Switchbench.Smp/Framing/MessageFramer.cs (big-endian header, ReadExactlyAsync partial-read reassembly, max-frame-size guard).

On an ISO 8583 connection SMP reads the first frame you send after connecting and uses it only for a defense-in-depth tenant cross-check — it is not dispatched as a transaction. DE 32 (acquiring institution identification code) and, when an intermediary sits between the originating terminal and SMP, DE 33 (forwarding institution identification code) are checked against the ids configured for your org (carried in the /api/resolve-cert response, so the check adds no hot-path call).

  • Enforce mode is on by default (Smp:De3233Crosscheck:Enforce, default true). A present field that disagrees with your org’s configured id drops the connection.
  • Observe mode logs the mismatch without rejecting.
  • Missing fields, or an org with no configured ids, are skipped — the validated client certificate remains the authoritative tenant identity, with DE 32/33 only a cross-check on top of it.

The interaction between this connect-time read and the server-first round trip is being reworked in #533; treat this section as the current behaviour, not a stable contract.

Implementation reference: switchbench_smp/src/Switchbench.Smp/Iso8583/VisaFrameParser.cs.

You must establish the mTLS TCP connection and keep it alive. SMP delivers the outbound leg of the REST-originated authorization — and reads your response — over that same connection.

On connect, in order:

  1. TLS handshake (certificate chain validated against the KMS CA root).
  2. GET /api/resolve-cert resolves your org_id from the certificate fingerprint; a revoked or unknown certificate ends the connection here.
  3. SMP reads exactly one frame from you — the DE 32/33 cross-check frame. You must send it before SMP registers the connection, so nothing can be delivered to you until you have. This connect-time read, and its interaction with the REST round trip, is what #533 is reworking.
  4. The connection is registered and can now receive deliveries.

If no registered connection exists for your org when SMP needs to write, the delivery fails and is logged and metered as a delivery failure. A REST caller with no such connection sees this as a 502 on POST /transaction.

The frame SMP delivers to you is a Visa 0100 / 0200 authorization prepared by SVE. Write back a valid, MTI-matching response — a 0110 / 0210 frame with DE 39 set — as a faithful counterparty would. SVE decodes that response before it does anything else, so it must parse and its MTI must be the request’s +10 counterpart; an undecodable or mismatched response is rejected as an engine error.

BCS behaviour is applied at finalize time, on top of your response. After decoding, SVE resolves the behaviour configured for the card’s PAN (see Before you begin — Scenario behaviour) and, when one matches, overrides the final REST outcome: it rewrites DE 39 (approve / decline / refer), adjusts DE 4 for a partial approval, or applies a delay. So the response you send is the faithful leg; the behaviour shapes what the REST caller ultimately sees.

A timeout behaviour is the exception — SVE emits no finalized message for it. Today the REST caller then receives a 502 engine error (RFC 7807 problem+json, no transaction payload): SVE finalize returns 504 internally and SMP currently maps every engine error response to 502. Do not try to simulate a timeout by withholding your response frame — that only stalls finalization and never reaches the behaviour; configure a timeout behaviour instead.

For the currently supported authorization flows, prepare-leg cryptographic fields are generated automatically by the engine — you do not configure them. Response-leg crypto (PIN-change confirmation, key-exchange finalization) and PIN-based transaction routing (0100 with DE 3 = 71xxxx / 70xxxx / 72xxxx) are not yet wired — see #536. Full Visa field semantics are owned by SVE and are out of scope here.

ConditionResult
Certificate missing, expired, self-signed, or not chaining to the KMS CA rootTLS handshake fails
Revoked or unknown certificate (still chains to the CA root)Connection closed after the handshake, before any payload is processed
DE 32 / DE 33 mismatch, enforce mode (default)Connection dropped after the connect-time cross-check frame
Connect-time cross-check frame malformed, undecodable, or over the maximum sizeConnection closed by the listener
Delivery-leg response frame truncated or over the maximum sizeDelivery failure; SMP evicts the connection from its registry (reconnect and re-register). 502 on the REST leg
Delivery-leg response frame well-framed but the ISO 8583 payload does not decode, or its MTI is wrongSVE rejects it as an engine error (502 on the REST leg); the TCP connection stays registered and reusable
No registered mTLS connection for the org when SMP needs to deliverDelivery failure (logged and metered; 502 on the REST leg)

Confirm your certificate is accepted and the chain resolves before wiring up framing. Substitute the client cert and key filenames from your onboarding issuance screen:

Terminal window
openssl s_client -connect test-tcp.switchbench.com:11012 \
-cert client-cert.pem \
-key client-key.pem \
-CAfile switchbench-ca-root.pem \
-quiet

A certificate that chains to the CA root completes the handshake and keeps the socket open. A revoked or unknown certificate completes the handshake and is then dropped when /api/resolve-cert resolves its status.

Read a delivered frame, write the response

Section titled “Read a delivered frame, write the response”

Language-agnostic — the framing is the same in any TCP client. This is your side of the server-first round trip: SMP writes, you read and reply.

The end-to-end round trip is blocked on #533, so today this sequence is exercised in isolation, not as a full REST round trip. Note the mandatory connect-time preamble.

# on connect, after the TLS handshake: send the DE 32/33 cross-check frame FIRST.
# SMP consumes and discards it, then registers the connection for delivery.
xcheck = iso8583_bytes # 0100/0200 carrying DE 32 (and DE 33 if used)
socket.write(len(xcheck).to_bytes(2, "big") + xcheck)
# inbound: read the length, then read exactly that many bytes
header = socket.read_exactly(2)
length = int.from_bytes(header, "big")
request = socket.read_exactly(length) # 0100/0200 from SVE, starts at the MTI
# ... build a valid, MTI-matching ISO 8583 response (0110/0210 with DE 39 set) ...
# the configured behaviour is applied by SVE at finalize time, not here.
# outbound: prefix the length, then write
payload = response_bytes # 0110/0210, starts at the MTI
header = len(payload).to_bytes(2, "big") # payload bytes only
socket.write(header + payload)

Keep the socket open afterwards — the same connection carries every subsequent delivery for your org.