BACEN Integration
This guide covers how FluxiQ PIX integrates with Banco Central do Brasil (BACEN) for participation in the PIX instant payment system.
Overview
PIX is Brazil's instant payment system operated by BACEN. Direct participants must integrate with two primary APIs:
- DICT (Diretorio de Identificadores de Contas Transacionais) - The PIX key directory
- SPI (Sistema de Pagamentos Instantaneos) - The instant payment settlement system
Both APIs require mutual TLS (mTLS) authentication using ICP-Brasil digital certificates and operate within the RSFN (Rede do Sistema Financeiro Nacional) network.
BACEN Environments
| Environment | DICT URL | SPI URL |
|---|---|---|
| Homologation | https://dict-h.pi.rsfn.net.br:16522/api/v2 | https://spi-h.pi.rsfn.net.br:16522 |
| Production | https://dict.pi.rsfn.net.br:16522/api/v2 | https://spi.pi.rsfn.net.br:16522 |
WARNING
The homologation environment has rate limits and synthetic data. Do not use production credentials in homologation.
Certificate Requirements
ICP-Brasil Certificates
All communication with BACEN requires certificates issued by an ICP-Brasil accredited authority. The certificate must:
- Be a type A1 or A3 certificate
- Contain the institution's CNPJ in the Subject Alternative Name
- Be issued by an authority in the ICP-Brasil chain of trust
- Have the ISPB registered with BACEN for the PIX participant
Certificate Configuration
# config/runtime.exs
config :shared, Shared.Bacen.Client,
cert_path: System.get_env("CERT_PATH", "./certs/client.pem"),
key_path: System.get_env("KEY_PATH", "./certs/client_key.pem"),
ca_path: System.get_env("CA_PATH", "./certs/ca_chain.pem"),
bacen_ca_path: System.get_env("BACEN_CA_PATH", "./certs/bacen_ca.pem")mTLS Handshake
The Shared BACEN client configures Erlang's :ssl module for mutual authentication:
defmodule Shared.Bacen.Client do
def ssl_options do
[
certfile: config(:cert_path),
keyfile: config(:key_path),
cacertfile: config(:ca_path),
verify: :verify_peer,
depth: 4,
versions: [:"tlsv1.2", :"tlsv1.3"],
ciphers: :ssl.cipher_suites(:default, :"tlsv1.3")
]
end
endDICT Integration
The DICT API manages PIX keys that map user identifiers (CPF, CNPJ, phone, email, EVP) to bank account information.
Supported Operations
| Operation | HTTP Method | Endpoint | Description |
|---|---|---|---|
| Create Key | POST | /api/v2/key | Register a new PIX key |
| Delete Key | DELETE | /api/v2/key/{key} | Remove a registered key |
| Lookup Key | GET | /api/v2/key/{key} | Query key details |
| List Keys | GET | /api/v2/key | List keys by ISPB or owner |
| Create Claim | POST | /api/v2/claim | Initiate key portability or ownership claim |
| Confirm Claim | PUT | /api/v2/claim/{claimId} | Accept or deny a claim |
| Create Infraction | POST | /api/v2/infraction | Report fraud or infraction |
| Create MED | POST | /api/v2/med | Special return mechanism (MED 2.0) |
Key Types
defmodule Shared.Schema.PixKey do
@key_types [:cpf, :cnpj, :phone, :email, :evp]
# CPF: 11-digit Brazilian individual tax ID
# CNPJ: 14-digit Brazilian company tax ID
# PHONE: +55DDDNNNNNNNNN format
# EMAIL: Valid email address, max 77 characters
# EVP: UUID v4 random key (Endere\u00e7o Virtual de Pagamento)
endDICT Synchronization
BACEN requires participants to maintain a local mirror of the DICT and perform periodic synchronization:
defmodule DictService.SyncWorker do
use GenServer
# Sync every 15 minutes as required by BACEN
@sync_interval :timer.minutes(15)
def handle_info(:sync, state) do
{:ok, changes} = Shared.Bacen.Client.get("/api/v2/sync/#{state.last_sync_id}")
process_changes(changes)
schedule_sync()
{:noreply, %{state | last_sync_id: changes.last_id}}
end
endSPI Integration
The SPI handles the actual movement of funds between participants.
Message Types (ISO 20022)
| Message | Type | Direction | Purpose |
|---|---|---|---|
| pacs.008 | FIToFICstmrCdtTrf | Outbound | Payment initiation |
| pacs.002 | FIToFIPmtStsRpt | Inbound | Payment status report |
| pacs.004 | PmtRtr | Outbound | Payment return/reversal |
| camt.053 | BkToCstmrStmt | Inbound | Account statement |
| camt.060 | AcctRptgReq | Outbound | Statement request |
XML Message Construction
SPI messages follow ISO 20022 XML format. The platform constructs and signs these messages:
defmodule SpiService.Message.Pacs008 do
@moduledoc "Constructs pacs.008 payment initiation messages"
def build(payment) do
"""
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.09">
<FIToFICstmrCdtTrf>
<GrpHdr>
<MsgId>#{payment.message_id}</MsgId>
<CreDtTm>#{DateTime.to_iso8601(DateTime.utc_now())}</CreDtTm>
<NbOfTxs>1</NbOfTxs>
<SttlmInf>
<SttlmMtd>CLRG</SttlmMtd>
</SttlmInf>
</GrpHdr>
<CdtTrfTxInf>
<PmtId>
<EndToEndId>#{payment.end_to_end_id}</EndToEndId>
<TxId>#{payment.transaction_id}</TxId>
</PmtId>
<IntrBkSttlmAmt Ccy="BRL">#{payment.amount}</IntrBkSttlmAmt>
<!-- ... additional fields ... -->
</CdtTrfTxInf>
</FIToFICstmrCdtTrf>
</Document>
"""
end
endJWS Signing
All SPI messages must be signed using JWS (JSON Web Signature) with the institution's private key:
defmodule Shared.Bacen.JwsSigner do
def sign(payload, private_key) do
header = %{
"alg" => "PS256",
"typ" => "JWT",
"kid" => key_id(private_key)
}
JOSE.JWS.sign(private_key, payload, header)
end
endCompliance Requirements
Operational Hours
PIX operates 24/7/365. The platform must maintain availability at all times, including:
- Real-time payment processing (SPI)
- Key management (DICT)
- Settlement cycles (multiple daily windows)
Response Time Requirements
BACEN mandates specific response times:
| Operation | Maximum Response Time |
|---|---|
| Key lookup | 500ms |
| Payment initiation | 1,000ms |
| Payment confirmation | 500ms |
| Claim processing | 10 seconds |
Data Retention
- Transaction data: minimum 5 years
- Audit logs: minimum 10 years
- Key history: minimum 5 years after key removal
MED 2.0 (Mecanismo Especial de Devolucao)
MED 2.0 is BACEN's fraud prevention mechanism. The platform supports:
- Infraction reports - Flag suspicious transactions
- Special returns - Automated reversal for confirmed fraud
- Account blocking - Temporary hold on suspected fraud accounts
- Notification relay - Forward MED notifications to affected participants
defmodule DictService.Med do
def create_infraction(params) do
Shared.Bacen.Client.post("/api/v2/infraction", %{
"EndToEndId" => params.end_to_end_id,
"InfractionType" => params.type, # "FRAUD" | "REQUEST_REFUND"
"ReportDetails" => params.details
})
end
endError Handling
BACEN APIs return structured error responses. The platform maps these to internal error types:
| BACEN Error | Internal Handling |
|---|---|
KEY_NOT_FOUND | Return 404, log lookup miss |
KEY_ALREADY_CLAIMED | Queue for claim resolution |
ENTRY_LOCKED | Retry with exponential backoff |
INSUFFICIENT_BALANCE | Reject payment, notify user |
RATE_LIMITED | Queue and retry after cooldown |
CERTIFICATE_ERROR | Alert ops team, circuit break |
The BACEN client implements circuit breaker pattern to prevent cascading failures:
defmodule Shared.Bacen.CircuitBreaker do
use GenServer
@failure_threshold 5
@reset_timeout :timer.seconds(30)
# States: :closed (normal), :open (failing), :half_open (testing)
end