Skip to content
MCP Server

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. A configured behaviour for the card is optional and only selects a simulated outcome; without one SVE finalizes your response with its normal faithful processing.

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 to a structured transaction route, e.g. POST /transaction/sale/pos.
  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 (a Hostinger VPS 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).
  • Your client certificate must chain to the Switchbench client CA root. SMP pins that root (issued by KMS) as the sole trust anchor for incoming client certificates and rejects, in the handshake itself, a certificate that is missing, expired, self-signed, or not chaining to it.
  • SMP’s server certificate chains to the separate Switchbench server CA root. Add that root to your client’s trust store so your side can verify the handshake, and verify the hostname you dialed against it — never disable verification. It is a different root from the client CA; obtain it as described in Before you begin — Server trust 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 — SMP validates your client certificate against the client CA root, and your client verifies SMP’s server certificate (and hostname) against the server 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 the structured transaction route it called.

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) is not yet wired — see #549 (response-leg SHE crypto in /transaction/finalize). PIN operations accept clear PIN digits only (#536). Full Visa field semantics are owned by SVE and are out of scope here.

ConditionResult
Client certificate missing, expired, self-signed, or not chaining to the Switchbench client CA rootTLS handshake fails
SMP’s server certificate not trusted by your client (server CA root missing or wrong environment), or its hostname does not match the endpoint you dialedYour client aborts the handshake before any bytes are exchanged
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)
Organization has no active trial or subscription for Visa (checked when the connection is established, and re-checked while it is open)Connection dropped before any frame is read, or closed on the next idle re-check; no frames are delivered until the product is entitled again

Confirm both directions of trust before wiring up framing: that you accept SMP’s server certificate, and that SMP accepts your client certificate. Substitute the client cert and key filenames from your onboarding issuance screen, and the server CA root for your environment (see Before you begin — Server trust root):

Terminal window
openssl s_client -connect test-tcp.switchbench.com:11012 \
-servername test-tcp.switchbench.com \
-verify 4 \
-verify_hostname test-tcp.switchbench.com \
-verify_return_error \
-CAfile switchbench-server-ca-test.pem \
-cert client-cert.pem \
-key client-key.pem
  • -CAfile is the server CA root for this environment — the anchor for SMP’s server certificate. It is not the client CA root and not your client certificate.
  • -verify 4 turns on peer-chain verification (max chain depth 4) — without it s_client inspects the server chain for information only and never fails on a bad one, so -verify_return_error would have nothing to act on. It fails closed here: an unverifiable server certificate aborts the handshake.
  • -verify_hostname fails the connection if the server certificate’s SAN does not cover the host you dialed; -verify_return_error makes any verification failure abort the connection instead of printing a warning and continuing. -verify_hostname and -verify_return_error need OpenSSL 1.1.1 or newer — the LibreSSL openssl shipped as /usr/bin/openssl on macOS does not accept them.
  • -cert / -key are your client certificate and its private key.
  • The example omits -quiet on purpose: -quiet suppresses the Verify return code: summary line this procedure checks.

On success OpenSSL prints Verify return code: 0 (ok) near the end of the session output and the socket stays open. A revoked or unknown client certificate still completes the handshake and is then dropped when /api/resolve-cert resolves its status.

A verification failure closes the connection and names the cause — as a Verify return code: <n> (<reason>) line without -quiet, or as a verify error:num=<n>:<reason> line if you add -quiet back. For example:

Verify return codeCause
21 (unable to verify the first certificate) or 19 (self-signed certificate in certificate chain)Wrong -CAfile — you passed the client CA root, your client certificate, or an unrelated root instead of this environment’s server CA root
62 (hostname mismatch)The server certificate does not cover the host you connected to — recheck the endpoint and -servername
10 (certificate has expired)The server CA root or the served certificate is outside its validity window — re-fetch the current root from your onboarding contact

Never work around a failure by dropping -verify_* or adding -noverify, and never pass a CA private key — handshake verification only ever needs the public server CA root.

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.