Skip to main content
Every Pix transaction follows a pattern: debit the sender, credit the receiver, maybe collect a fee. When that pattern lives only in application code, every team that touches Pix must re-implement the same validation logic — and every implementation is a chance for inconsistency. Transaction Routes move that pattern into the ledger itself. You define the rules once, and Midaz enforces them on every transaction automatically. The result is fewer integration errors, a clear audit trail, and a single source of truth for how Pix money flows through your system. This page walks through two real-world scenarios — a simple peer-to-peer transfer and a transfer with fee collection — showing how to set up routes and what your team gains from using them.

Why this matters


For product and operations teams, Transaction Routes mean predictable, auditable Pix flows without relying on application-level enforcement. Every transaction carries a reference to the route it followed, making compliance reviews and incident investigations straightforward. For engineering teams, routes eliminate repetitive validation code. Instead of checking account types and fee destinations in every Pix integration, you configure the rules once and let Midaz handle enforcement at the ledger level.
Without RoutesWith Routes
Each integration must enforce its own account rulesDefine the rules once, reuse across all Pix transactions
No automatic validation — constraints live in application codeMidaz rejects transactions that don’t match the route’s rules
Adding fees requires changes across every Pix integrationAdd a new Operation Route, create a new Transaction Route variant
Hard to trace which pattern a transaction was supposed to followEvery transaction stores its route ID — straightforward to audit
For a deeper look at how Transaction Routes and Operation Routes work, see Transaction Routing.

Prerequisites


Both scenarios assume a Midaz environment with the following structure already in place:
EntityAliasTypePurpose
Alice’s account@alice_checkingcheckingSender — Alice’s main checking account
Bob’s account@bob_checkingcheckingReceiver — Bob’s main checking account
BRL assetBrazilian Real, registered as the operating asset
Values in Midaz are represented in the smallest unit of the currency. For BRL, 15000 means R$ 150.00 (centavos).

Scenario 1: Simple Pix transfer


Alice sends R$ 150.00 to Bob via Pix. The money moves from one checking account to another — no fees, no splits, just a clean peer-to-peer transfer.

The goal

  • Debit Alice’s checking account by R$ 150.00
  • Credit Bob’s checking account by R$ 150.00
  • Validate that both accounts are of type checking before processing
  • Make this pattern reusable for every Pix transfer between checking accounts

Setting up the routes

1

Create the source Operation Route

This route defines the debit side of the transfer. The account_type rule means any account of type checking is valid as a source — the route doesn’t hardcode a specific sender.
POST /v1/organizations/{org_id}/ledgers/{ledger_id}/operation-routes

{
  "title": "PIX - Debit Sender",
  "description": "Debits the sender's checking account in a Pix transfer",
  "code": "PIX-SEND-SRC",
  "operationType": "source",
  "account": {
    "ruleType": "account_type",
    "validIf": ["checking"]
  },
  "metadata": {
    "payment_method": "pix",
    "direction": "outbound"
  }
}
Save the returned id — you’ll need it when building the Transaction Route.
2

Create the destination Operation Route

This route defines the credit side. Same rule type: any checking account qualifies as a valid receiver.
POST /v1/organizations/{org_id}/ledgers/{ledger_id}/operation-routes

{
  "title": "PIX - Credit Receiver",
  "description": "Credits the receiver's checking account in a Pix transfer",
  "code": "PIX-SEND-DST",
  "operationType": "destination",
  "account": {
    "ruleType": "account_type",
    "validIf": ["checking"]
  },
  "metadata": {
    "payment_method": "pix",
    "direction": "inbound"
  }
}
3

Create the Transaction Route

Group both Operation Routes into a single Transaction Route. This is the pattern that represents “Pix Transfer” in your system.
POST /v1/organizations/{org_id}/ledgers/{ledger_id}/transaction-routes

{
  "title": "PIX Transfer",
  "description": "Standard Pix transfer between two checking accounts",
  "operationRoutes": [
    "<pix-send-src-id>",
    "<pix-send-dst-id>"
  ],
  "metadata": {
    "payment_rail": "pix",
    "spi_message_type": "pacs.008",
    "regulation": "BCB_PIX"
  }
}
Replace the placeholder IDs with the actual Operation Route IDs returned in the previous steps.

Executing a Pix transfer

With the route in place, every Pix transfer references the Transaction Route ID. Midaz validates that the accounts match the route’s rules before processing.
POST /v1/organizations/{org_id}/ledgers/{ledger_id}/transactions/json

{
  "chartOfAccountsGroupName": "PIX",
  "description": "Pix transfer from Alice to Bob",
  "code": "PIX-20260306-001",
  "route": "<pix-transfer-route-id>",
  "send": {
    "asset": "BRL",
    "value": 15000,
    "source": {
      "from": [
        {
          "accountAlias": "@alice_checking",
          "amount": { "asset": "BRL", "value": 15000 },
          "description": "Pix sent to Bob",
          "route": "<pix-send-src-id>"
        }
      ]
    },
    "distribute": {
      "to": [
        {
          "accountAlias": "@bob_checking",
          "amount": { "asset": "BRL", "value": 15000 },
          "description": "Pix received from Alice",
          "route": "<pix-send-dst-id>"
        }
      ]
    }
  },
  "metadata": {
    "pix_end_to_end_id": "E123456782026030614300000000001",
    "pix_key_type": "cpf",
    "pix_key": "123.456.789-00"
  }
}

What happens under the hood

1

Midaz receives the transaction

The request includes the Transaction Route ID in the route field. Midaz loads the route configuration.
2

Source validation

For each from entry, Midaz checks the account against the source Operation Route rules. Alice’s account is type checking — matches the account_type rule. Validation passes.
3

Destination validation

For each to entry, Midaz checks the account against the destination Operation Route rules. Bob’s account is type checking — validation passes.
4

Transaction is processed

Both validations pass, so Midaz creates the transaction atomically: debits @alice_checking by R$ 150.00 and credits @bob_checking by R$ 150.00.
If Alice tried to send from a savings account instead, Midaz would reject the transaction — the route only accepts checking accounts as sources. No application-side validation needed.

Scenario 2: Pix transfer with fee collection


Same flow as Scenario 1, but now the bank charges a R$ 1.50 fee on each Pix transfer. This adds a third Operation Route for the fee destination, and Alice’s total debit increases to R$ 151.50.

What changes

You already have the source and destination Operation Routes from Scenario 1. You need one additional Operation Route for the fee and a new Transaction Route that groups all three.
EntityAliasTypePurpose
Fee revenue account@revenue_pix_feesrevenueInternal account that collects Pix transfer fees

Setting up the fee route

1

Create the fee Operation Route

Unlike the previous routes that use account_type, this one uses the alias rule type. That means it targets a specific account — @revenue_pix_fees — and no other account can be used.
POST /v1/organizations/{org_id}/ledgers/{ledger_id}/operation-routes

{
  "title": "PIX - Fee Collection",
  "description": "Credits the bank's revenue account with the Pix transfer fee",
  "code": "PIX-FEE-DST",
  "operationType": "destination",
  "account": {
    "ruleType": "alias",
    "validIf": "@revenue_pix_fees"
  },
  "metadata": {
    "fee_type": "pix_transfer_fee"
  }
}
2

Create the Transaction Route with fee

This route groups the original source and destination routes with the new fee route. It’s a separate Transaction Route from the simple transfer — your system can offer both variants.
POST /v1/organizations/{org_id}/ledgers/{ledger_id}/transaction-routes

{
  "title": "PIX Transfer with Fee",
  "description": "Pix transfer between checking accounts with fee collection",
  "operationRoutes": [
    "<pix-send-src-id>",
    "<pix-send-dst-id>",
    "<pix-fee-dst-id>"
  ],
  "metadata": {
    "payment_rail": "pix",
    "includes_fee": true
  }
}

Executing a Pix transfer with fee

Alice sends R$ 150.00 to Bob. The bank collects R$ 1.50. Alice’s total debit is R$ 151.50.
POST /v1/organizations/{org_id}/ledgers/{ledger_id}/transactions/json

{
  "chartOfAccountsGroupName": "PIX",
  "description": "Pix transfer from Alice to Bob (with fee)",
  "code": "PIX-20260306-002",
  "route": "<pix-transfer-with-fee-route-id>",
  "send": {
    "asset": "BRL",
    "value": 15150,
    "source": {
      "from": [
        {
          "accountAlias": "@alice_checking",
          "amount": { "asset": "BRL", "value": 15150 },
          "description": "Pix sent to Bob + transfer fee",
          "route": "<pix-send-src-id>"
        }
      ]
    },
    "distribute": {
      "to": [
        {
          "accountAlias": "@bob_checking",
          "amount": { "asset": "BRL", "value": 15000 },
          "description": "Pix received from Alice",
          "route": "<pix-send-dst-id>"
        },
        {
          "accountAlias": "@revenue_pix_fees",
          "amount": { "asset": "BRL", "value": 150 },
          "description": "Pix transfer fee",
          "route": "<pix-fee-dst-id>"
        }
      ]
    }
  },
  "metadata": {
    "pix_end_to_end_id": "E123456782026030614300000000002",
    "fee_amount": 150
  }
}
Result: Alice is debited R$ 151.50. Bob receives R$ 150.00. The bank collects R$ 1.50 in fees. All in a single atomic transaction — fully balanced, fully auditable.

What this unlocks

  • Transparent fee collection — the fee is a first-class ledger entry, not hidden metadata. Finance and compliance teams see exactly where the R$ 1.50 went.
  • Reusable building blocks — the source and destination Operation Routes are shared between the simple and fee variants. You only add what changes.
  • Route-level control — your system can offer both “PIX Transfer” and “PIX Transfer with Fee” as distinct products, each backed by its own Transaction Route.
  • Easy evolution — need a percentage-based fee? A split across multiple revenue accounts? Add new Operation Routes and compose a new Transaction Route. Existing flows remain untouched.

Understanding rule types


The two rule types serve different purposes. Choosing the right one depends on whether the account in a route should be dynamic or fixed.
Rule typevalidIf formatBehaviorWhen to use
account_typeArray of strings — e.g., ["checking"] or ["checking", "savings"]Accepts any account that matches one of the specified typesDynamic participants — the sender or receiver can be any account of that type
aliasString — e.g., "@revenue_pix_fees"Must target a specific account by its aliasFixed participants — the route always hits the same account, like a fee or settlement account
You can combine both rule types within a single Transaction Route. Scenario 2 does exactly that: account_type for the dynamic sender and receiver, alias for the fixed fee account.

What you need to get started


RequirementDetails
Midaz (v3.x.x+)Core ledger with Transaction Route validation enabled
Route validation configSet TRANSACTION_ROUTE_VALIDATION in the Transaction service .env to include your organization_id:ledger_id
Accounts and assetAt minimum: two customer accounts and a BRL asset registered in the ledger
Operation RoutesOne per operation leg (source, destination, fee)
Transaction RouteGroups the Operation Routes into a reusable pattern
Transaction Route validation must be explicitly enabled per ledger. See Working with Transaction Routing for the configuration steps.

Next steps