BACEN Gateway
The BACEN Gateway (bacen_gateway) is the core integration layer between FluxiQ SPB and the Brazilian Central Bank's RSFN (Rede do Sistema Financeiro Nacional). It handles all 979 BACEN message types, manages IBM MQ connections, and enforces message schema validation and digital signatures.
Port: 4002
Responsibilities
- Connect to BACEN's RSFN network via IBM MQ
- Build, validate, and parse all 979 BACEN XML message types
- Apply digital signatures to outbound messages via HSM
- Verify digital signatures on inbound BACEN messages
- Manage message sequencing and the NUOp (Numero Unico de Operacao)
- Handle R1, R2, and R3 response correlation
- Provide a BACEN simulator mode for development and testing
RSFN Connection Architecture
FluxiQ SPB BACEN / RSFN
+------------------+ +------------------+
| | IBM MQ | |
| BACEN Gateway |<-------------->| RSFN Network |
| | Channel: | |
| +-------------+ | BACEN.SVRCONN| +------------+ |
| | MQ Producer |--+--------------->| | Inbound Q | |
| +-------------+ | | +------------+ |
| | | |
| +-------------+ | | +------------+ |
| | MQ Consumer |<-+----------------| | Outbound Q | |
| +-------------+ | | +------------+ |
+------------------+ +------------------+Configuration
elixir
# config/runtime.exs
config :bacen_gateway, BacenGateway.MQ,
host: System.get_env("BACEN_MQ_HOST", "localhost"),
port: System.get_env("BACEN_MQ_PORT", "1414") |> String.to_integer(),
channel: System.get_env("BACEN_MQ_CHANNEL", "BACEN.SVRCONN"),
queue_manager: System.get_env("BACEN_MQ_QUEUE_MANAGER", "QM1"),
inbound_queue: System.get_env("BACEN_MQ_INBOUND", "BACEN.IN"),
outbound_queue: System.get_env("BACEN_MQ_OUTBOUND", "BACEN.OUT"),
ssl_cipher_spec: "TLS_AES_256_GCM_SHA384",
ssl_key_repository: System.get_env("MQ_SSL_KEY_REPO")
config :bacen_gateway, BacenGateway.Institution,
ispb: System.fetch_env!("BACEN_ISPB"),
institution_name: System.get_env("INSTITUTION_NAME", "FluxiQ Dev"),
mode: System.get_env("BACEN_MODE", "simulator")
config :bacen_gateway, BacenGateway.Schema,
xsd_path: Path.join(:code.priv_dir(:bacen_gateway), "xsd"),
catalog_version: System.get_env("BACEN_CATALOG_VERSION", "042")Message Lifecycle
Outbound (Sending to BACEN)
elixir
defmodule BacenGateway.Outbound do
@doc "Sends a message to BACEN through the full outbound pipeline"
def send_message(system, message_type, payload) do
with {:ok, nuop} <- generate_nuop(),
{:ok, xml} <- build_xml(system, message_type, payload, nuop),
{:ok, validated} <- validate_schema(xml, message_type),
{:ok, signed} <- sign_message(validated),
{:ok, _ref} <- enqueue_mq(signed),
{:ok, _} <- register_pending(nuop, message_type) do
{:ok, %{nuop: nuop, status: :sent}}
end
end
defp generate_nuop do
# NUOp format: ISPB + Date + Sequential
# Example: 12345678202601150001
seq = :counters.get(counter_ref(), 1)
:counters.add(counter_ref(), 1, 1)
date = Date.utc_today() |> Date.to_iso8601(:basic)
{:ok, "#{@ispb}#{date}#{String.pad_leading("#{seq}", 4, "0")}"}
end
endInbound (Receiving from BACEN)
elixir
defmodule BacenGateway.Inbound do
use Broadway
def start_link(_opts) do
Broadway.start_link(__MODULE__,
name: __MODULE__,
producer: [
module: {BacenGateway.MQProducer,
queue: @inbound_queue,
poll_interval: 100
},
concurrency: 1
],
processors: [
default: [concurrency: 20, max_demand: 1]
]
)
end
@impl true
def handle_message(_, %Broadway.Message{data: xml} = message, _) do
with {:ok, parsed} <- parse_xml(xml),
{:ok, verified} <- verify_signature(parsed),
{:ok, validated} <- validate_schema(verified),
{:ok, routed} <- route_message(validated) do
message
else
{:error, reason} ->
Broadway.Message.failed(message, reason)
end
end
endMessage Building
The gateway uses XSD-driven XML builders for each message type:
elixir
defmodule BacenGateway.XML.Builder do
@doc "Builds a BACEN XML message from a structured map"
def build(:STR, "STR0001", params) do
xml_document do
element :BCMSG do
element :IdentdEmissor, params.ispb
element :IdentdDesworko, "99999999"
element :NUOp, params.nuop
element :DtHrMsg, DateTime.utc_now() |> format_bacen_datetime()
end
element :SISMSG do
element :STR0001 do
element :CodMsg, "STR0001"
element :NumCtrlIF, params.control_number
element :ISPBIF, params.sender_ispb
element :ISPBFav, params.receiver_ispb
element :Valor, Decimal.to_string(params.amount)
element :DtMovto, Date.to_iso8601(params.date)
element :FinlddTrsf, params.purpose_code
end
end
end
end
endResponse Correlation
When a message is sent to BACEN, the gateway tracks its NUOp and correlates R1, R2, and R3 responses:
elixir
defmodule BacenGateway.ResponseTracker do
use GenServer
@doc """
Tracks outbound messages and correlates responses.
State machine per message:
:sent -> :r1_received -> :r2_received -> :r3_received
:sent -> :r1_received -> :r2_rejected
:sent -> :timeout
"""
def track(nuop, message_type) do
GenServer.call(__MODULE__, {:track, nuop, message_type})
end
def correlate_response(nuop, response_type, payload) do
GenServer.cast(__MODULE__, {:response, nuop, response_type, payload})
end
endSchema Validation
All messages are validated against BACEN's official XSD schemas:
elixir
defmodule BacenGateway.Schema.Validator do
@doc "Validates XML against the corresponding BACEN XSD schema"
def validate(xml, message_type) do
xsd_path = Path.join(@xsd_base, "#{message_type}.xsd")
case :xmerl_xsd.process_schema(String.to_charlist(xsd_path)) do
{:ok, schema} ->
case :xmerl_xsd.validate(xml, schema) do
{:ok, _} -> {:ok, xml}
{:error, reason} -> {:error, {:validation_failed, reason}}
end
{:error, reason} ->
{:error, {:schema_load_failed, reason}}
end
end
endBACEN Simulator
When BACEN_MODE=simulator, the gateway routes messages to the built-in simulator instead of IBM MQ:
elixir
defmodule BacenGateway.Simulator do
@moduledoc """
Simulates BACEN/RSFN behavior for development and testing.
Features:
- Message validation against official XSD schemas
- Configurable response delays (simulating network latency)
- Settlement window simulation with configurable schedules
- Error injection for testing failure scenarios
- R1/R2/R3 response generation with realistic payloads
"""
def handle_message(xml) do
with {:ok, parsed} <- parse(xml),
{:ok, _} <- validate(parsed),
{:ok, r1} <- generate_r1(parsed),
{:ok, r2} <- schedule_r2(parsed),
{:ok, r3} <- schedule_r3(parsed) do
{:ok, %{r1: r1, r2_scheduled: r2, r3_scheduled: r3}}
end
end
endThe simulator dashboard is accessible at http://localhost:4020 and provides:
- Real-time message flow visualization
- Settlement window controls (open/close/force-settle)
- Error injection panel (reject messages, simulate timeouts)
- Message history with full XML inspection
Health Check
bash
curl http://localhost:4002/health
# {
# "status": "ok",
# "mode": "simulator",
# "mq_connection": "connected",
# "pending_messages": 3,
# "schema_version": "042"
# }Metrics
bacen_messages_sent_total- Outbound messages by system and typebacen_messages_received_total- Inbound messages by system and typebacen_message_processing_duration_seconds- Processing time histogrambacen_response_correlation_duration_seconds- Time between send and R2/R3bacen_mq_connection_status- MQ connection health gaugebacen_schema_validation_errors_total- Schema validation failures