> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Pix reconciliation

> Configure Matcher to reconcile Pix cash-in and cash-out transactions between Midaz Ledger and BACEN's SPI settlement extracts.

This guide walks through reconciling Pix transactions between Midaz Ledger and BACEN (Brazil's Central Bank) settlement data using Matcher. It covers both Pix sent (cash-out) and Pix received (cash-in), from configuration to daily operation and exception handling.

By the end of this guide, you will have a fully configured Matcher context that automatically reconciles your Pix transactions against BACEN's SPI settlement extracts on a daily basis.

## Pix transaction flows

***

Understanding how Pix transactions flow through the system is essential for configuring reconciliation correctly. The two flows below show what Matcher needs to reconcile on each side.

### Pix sent (cash-out)

<Frame caption="Cash-out flow: from client initiation through SPI settlement to reconciliation in Matcher.">
  <img src="https://mintcdn.com/lerian-49cb71fc/dv54g7eWwCSrTDx5/images/en/docs/matcher-pix-cashout-flow.jpg?fit=max&auto=format&n=dv54g7eWwCSrTDx5&q=85&s=c87af13fd98a5ee017dfc95d965a2a2c" alt="Pix sent cash-out flow" width="3735" height="1673" data-path="images/en/docs/matcher-pix-cashout-flow.jpg" />
</Frame>

1. **Client initiates Pix** — The end user triggers a Pix payment via the app or API.
2. **Plugin creates initiation** — The Pix plugin creates an initiation record and resolves the destination account via DICT lookup.
3. **Plugin processes payment** — The plugin debits the client account in Midaz (transaction in `pending` status) and sends the payment instruction to SPI.
4. **Settlement confirmed** — SPI sends a webhook confirming settlement. The Midaz transaction is committed.
5. **Matcher reconciles** — Matcher compares the committed Midaz transaction against the corresponding entry in the BACEN SPI settlement extract.

### Pix received (cash-in)

<Frame caption="Cash-in flow: from inbound SPI notification through Midaz credit to reconciliation in Matcher.">
  <img src="https://mintcdn.com/lerian-49cb71fc/dv54g7eWwCSrTDx5/images/en/docs/matcher-pix-cashin-flow.jpg?fit=max&auto=format&n=dv54g7eWwCSrTDx5&q=85&s=f29e3179e6ce1f5343ec997457a75f1f" alt="Pix received cash-in flow" width="3830" height="1631" data-path="images/en/docs/matcher-pix-cashin-flow.jpg" />
</Frame>

1. **Inbound Pix arrives** — SPI sends a synchronous webhook containing the inbound Pix data.
2. **Plugin validates** — The Pix plugin validates the payload and approves the transaction.
3. **Credit transaction created** — The plugin creates a CREDIT transaction in Midaz for the recipient account.
4. **Settlement confirmed** — The settlement webhook confirms the transaction is final.
5. **Matcher reconciles** — Matcher compares the Midaz credit transaction against the corresponding entry in the BACEN SPI settlement extract.

<Note>
  In both flows, the **endToEndId** is the unique identifier that links the Midaz transaction to the BACEN settlement record. This is the primary key for reconciliation.
</Note>

## Configuration step-by-step

***

<Steps>
  <Step title="Create the context">
    Create a reconciliation context for Pix transactions. Use type `1:1` because each Pix transaction has exactly one corresponding BACEN settlement entry.

    ```json theme={null}
    {
      "name": "Pix Daily Reconciliation",
      "type": "1:1",
      "interval": "daily",
      "feeToleranceAbs": 0,
      "feeTolerancePct": 0,
      "autoMatchOnUpload": false
    }
    ```

    Pix transactions have no intermediate fees or partial settlements. A Pix of R$ 150.00 in Midaz must appear as exactly R$ 150.00 in the BACEN extract. Set both tolerance values to zero.

    Setting `autoMatchOnUpload` to `false` gives you control over when matching runs, which is important when you need both sources ingested before executing.

    <Tip>
      See the full request schema at [Create context](/en/reference/matcher/create-context).
    </Tip>
  </Step>

  <Step title="Create the sources">
    Each context needs two sources: one for Midaz transactions and one for the BACEN settlement extract.

    **Source A — Midaz (type `LEDGER`):**

    ```json theme={null}
    {
      "name": "Midaz - Pix Transactions",
      "type": "LEDGER",
      "config": {
        "ledger_id": "your-ledger-id",
        "currency": "BRL"
      }
    }
    ```

    When Midaz is integrated with Matcher (see [Midaz integration](/en/matcher/integrations/matcher-midaz-integration)), transactions are ingested automatically via event-driven sync. No manual upload is needed for this source.

    **Source B — BACEN SPI extract (type `CUSTOM`):**

    ```json theme={null}
    {
      "name": "BACEN SPI Settlement Extract",
      "type": "CUSTOM",
      "config": {
        "description": "Daily SPI settlement file from BACEN"
      }
    }
    ```

    The BACEN source uses type `CUSTOM` because the SPI settlement file is uploaded manually or via an automated pipeline each day.

    <Tip>
      See the full request schema at [Create source](/en/reference/matcher/create-source).
    </Tip>
  </Step>

  <Step title="Create field maps">
    Field maps tell Matcher how to translate fields from each source into the canonical fields used for matching.

    The following table shows the mapping between Matcher's canonical fields and the fields in each source:

    | Matcher field    | Midaz (automatic)     | BACEN extract (field map) |
    | ---------------- | --------------------- | ------------------------- |
    | `transaction_id` | `id`                  | `id_liquidacao`           |
    | `amount`         | `amount`              | `valor`                   |
    | `currency`       | `asset_code`          | `moeda`                   |
    | `date`           | `created_at`          | `data_liquidacao`         |
    | `reference`      | `metadata.endToEndId` | `end_to_end_id`           |

    When Midaz is integrated with Matcher, standard fields are mapped automatically. The `endToEndId` lives in transaction metadata and requires a custom field mapping override in the source settings (see [Midaz integration — custom field mapping](/en/matcher/integrations/matcher-midaz-integration#custom-field-mapping)):

    **Midaz source — field map with endToEndId override:**

    ```json theme={null}
    {
      "mapping": {
        "id": "transaction_id",
        "amount": "amount",
        "asset_code": "currency",
        "created_at": "date",
        "metadata.endToEndId": "reference"
      }
    }
    ```

    **BACEN source — field map:**

    ```json theme={null}
    {
      "mapping": {
        "id_liquidacao": "transaction_id",
        "valor": "amount",
        "moeda": "currency",
        "data_liquidacao": "date",
        "end_to_end_id": "reference"
      }
    }
    ```

    <Tip>
      See [Create field map](/en/reference/matcher/create-field-map) for the full request schema and [Midaz integration](/en/matcher/integrations/matcher-midaz-integration) for details on automatic field mapping.
    </Tip>
  </Step>

  <Step title="Create match rules">
    Match rules define how Matcher compares transactions across sources. For Pix reconciliation, two rules cover the vast majority of scenarios.

    **Rule 1 — Exact match by endToEndId (priority 1):**

    ```json theme={null}
    {
      "type": "EXACT",
      "priority": 1,
      "config": {
        "matchAmount": true,
        "matchCurrency": true,
        "matchDate": true,
        "matchReference": true,
        "datePrecision": "DAY",
        "caseInsensitive": false,
        "referenceMustSet": true,
        "matchScore": 100
      }
    }
    ```

    This rule resolves approximately 95% of cases. The `endToEndId` is unique per Pix transaction across the entire ecosystem. When the reference, amount, currency, and date all match, it is a confirmed reconciliation with maximum confidence. Note that `caseInsensitive` is set to `false` because `endToEndId` values are case-sensitive, and `referenceMustSet` is `true` to ensure both sides carry the `endToEndId` before comparing — this prevents false positives on amount and date alone.

    **Rule 2 — Date tolerance fallback (priority 51):**

    ```json theme={null}
    {
      "type": "DATE_LAG",
      "priority": 51,
      "config": {
        "maxDays": 1,
        "minDays": 0,
        "inclusive": true,
        "direction": "ABS",
        "feeTolerance": 0,
        "matchScore": 85,
        "matchCurrency": true
      }
    }
    ```

    A Pix initiated at 23:58 may settle in BACEN on the following calendar day. This rule allows a 1-day window to cover D+1 settlement scenarios. The lower `matchScore` of 85 signals that these matches should be reviewed with slightly more attention, though they are still valid. Note that this rule relies on amount and currency matching only — the `endToEndId` comparison is handled by Rule 1.

    <Tip>
      See [Create match rule](/en/reference/matcher/create-match-rule) for the full request schema and all available rule types.
    </Tip>
  </Step>

  <Step title="Activate and schedule">
    Once all configuration is in place, activate the context and create a daily schedule.

    **Activate the context:**

    ```bash theme={null}
    curl -X PATCH "https://api.matcher.example.com/v1/contexts/{contextId}" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{ "status": "ACTIVE" }'
    ```

    **Create a schedule to run daily at 07:00 UTC:**

    ```bash theme={null}
    curl -X POST "https://api.matcher.example.com/v1/contexts/{contextId}/schedules" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
       "cronExpression": "0 7 * * *",
       "enabled": true
     }'
    ```

    Running at 07:00 UTC provides enough margin for D+1 settlements to appear in the BACEN extract and for the daily file to be uploaded before the matching run executes.

    <Tip>
      See [Update context](/en/reference/matcher/update-context) and [Create schedule](/en/reference/matcher/create-schedule) for the full request schemas.
    </Tip>
  </Step>
</Steps>

## Daily operation

***

Once configured, the daily reconciliation workflow follows five steps.

<Steps>
  <Step title="Upload BACEN extract">
    Upload the previous day's SPI settlement file to the BACEN source. Matcher accepts both CSV and JSON formats.

    ```bash theme={null}
    curl -X POST "https://api.matcher.example.com/v1/imports/contexts/{contextId}/sources/{sourceId}/upload" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: multipart/form-data" \
     -F "file=@bacen_spi_2026-03-17.csv" \
     -F "format=csv"
    ```

    This step can be automated via a pipeline that fetches the SPI file and uploads it before the scheduled matching run.
  </Step>

  <Step title="Midaz data available automatically">
    With the Midaz integration configured, transactions are continuously ingested into Matcher as they are committed. No manual upload is needed for Source A. See [Midaz integration](/en/matcher/integrations/matcher-midaz-integration) for setup details.
  </Step>

  <Step title="Matcher runs at 07:00 (or manually)">
    The scheduled run executes automatically at 07:00 UTC. To run matching manually, use the run endpoint.

    ```bash theme={null}
    curl -X POST "https://api.matcher.example.com/v1/matching/contexts/{contextId}/run" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{ "mode": "DRY_RUN" }'
    ```

    Use `DRY_RUN` first to preview results without committing them. When satisfied, run again with `COMMIT`:

    ```bash theme={null}
    curl -X POST "https://api.matcher.example.com/v1/matching/contexts/{contextId}/run" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{ "mode": "COMMIT" }'
    ```
  </Step>

  <Step title="Review results">
    After the run completes, retrieve the matched groups to see the results.

    ```bash theme={null}
    curl -X GET "https://api.matcher.example.com/v1/matching/runs/{runId}/groups" \
     -H "Authorization: Bearer $TOKEN"
    ```

    Each group shows the matched Midaz transaction and its corresponding BACEN settlement entry, along with the rule that matched them and the confidence score.
  </Step>

  <Step title="Resolve exceptions">
    Unmatched transactions appear as exceptions. These require investigation — a transaction present in one source but not the other, or a mismatch in amount or date beyond the configured tolerance.

    Review exceptions, determine the root cause, and resolve them by force matching, ignoring, or correcting the underlying data.
  </Step>
</Steps>

<Tip>
  Always run a `DRY_RUN` first when testing new rules or after configuration changes. This prevents unintended matches from being committed.
</Tip>

## Practical example — one day of data

***

The following example illustrates a complete reconciliation run for March 17, 2026.

### Midaz transactions (Source A)

| ID      | endToEndId             | Amount  | Type    | Date             |
| ------- | ---------------------- | ------- | ------- | ---------------- |
| txn-001 | E123456789202603170001 | 150.00  | Pix OUT | 2026-03-17 10:15 |
| txn-002 | E987654321202603170042 | 3200.50 | Pix IN  | 2026-03-17 11:30 |
| txn-003 | E555666777202603170099 | 89.90   | Pix OUT | 2026-03-17 23:58 |
| txn-004 | E111222333202603170007 | 500.00  | Pix IN  | 2026-03-17 14:00 |

### BACEN SPI extract (Source B)

| id\_liquidacao | end\_to\_end\_id       | valor   | data\_liquidacao |
| -------------- | ---------------------- | ------- | ---------------- |
| liq-8801       | E123456789202603170001 | 150.00  | 2026-03-17       |
| liq-8802       | E987654321202603170042 | 3200.50 | 2026-03-17       |
| liq-8803       | E555666777202603170099 | 89.90   | 2026-03-18       |
| liq-8804       | E444555666202603170055 | 750.00  | 2026-03-17       |

### Match results

| endToEndId    | Midaz   | BACEN    | Result      | Rule                  |
| ------------- | ------- | -------- | ----------- | --------------------- |
| E12345...0001 | txn-001 | liq-8801 | Match       | EXACT (score: 100)    |
| E98765...0042 | txn-002 | liq-8802 | Match       | EXACT (score: 100)    |
| E55566...0099 | txn-003 | liq-8803 | Match (D+1) | DATE\_LAG (score: 85) |
| E11122...0007 | txn-004 | —        | Exception   | —                     |
| E44455...0055 | —       | liq-8804 | Exception   | —                     |

### Analysis

* **txn-001 and txn-002:** Exact match on endToEndId, amount, currency, and date. Rule 1 resolved these with confidence score 100.
* **txn-003:** Pix initiated at 23:58, settled in BACEN on 2026-03-18. Rule 2 (DATE\_LAG with 1-day window) resolved this with confidence score 85.
* **txn-004:** Present in Midaz but absent from BACEN. Possible settlement failure or SPI timeout. Investigate the transaction status via the Pix plugin.
* **liq-8804:** Present in BACEN but absent from Midaz. An inbound Pix that was not processed. Check webhook delivery or reprocess the message.

## Handling Pix exceptions

***

The following table covers the most common Pix exception scenarios and recommended actions.

| Scenario               | Probable cause                                               | Recommended action                                                         |
| ---------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- |
| In Midaz, not in BACEN | Settlement failure, SPI timeout, rejected transaction        | Check transaction status in the Pix plugin. If rejected, revert in Midaz.  |
| In BACEN, not in Midaz | Inbound Pix not processed, webhook failure                   | Reprocess the message. Create a manual transaction if needed.              |
| Amount mismatch        | Rare in Pix (no intermediate fees). Possible rounding error. | Investigate original records. Force match if the difference is acceptable. |
| Date mismatch (>1 day) | Held transaction, reprocessing                               | Verify it is the same Pix. Force match or ignore.                          |

### Force match

When you have confirmed that two records represent the same Pix transaction but Matcher could not match them automatically, use force match.

```bash theme={null}
curl -X POST "https://api.matcher.example.com/v1/exceptions/{exceptionId}/force-match" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "notes": "Confirmed same Pix via endToEndId lookup in SPI",
   "overrideReason": "D+2 settlement delay confirmed with BACEN"
 }'
```

### Ignore transaction

When a transaction should be excluded from reconciliation (for example, a duplicate entry or an already-reversed Pix), mark it as ignored.

```bash theme={null}
curl -X POST "https://api.matcher.example.com/v1/imports/contexts/{contextId}/transactions/{transactionId}/ignore" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "reason": "Pix reversal already processed — duplicate entry"
 }'
```

<Tip>
  See [Force match](/en/reference/matcher/force-match-exception) and [Ignore transaction](/en/reference/matcher/ignore-transaction) for the full request schemas.
</Tip>

## Pix refunds (devoluções)

***

Pix refunds generate reverse transactions that also need reconciliation. When a refund is processed, the Pix plugin creates a new transaction in Midaz with:

* The `originalEndToEndId` linking back to the original Pix transaction
* A new `returnIdentification` (rtrId) that uniquely identifies the refund in SPI

BACEN's settlement extract includes refund entries with both identifiers, allowing Matcher to reconcile them against the corresponding Midaz refund transactions.

For low refund volumes, these can be reconciled within the same Pix Daily Reconciliation context. For high volumes, create a separate context dedicated to refund reconciliation. This simplifies exception triage and keeps refund metrics isolated from standard Pix flow metrics.

<Note>
  The Pix plugin supports refund initiation via `POST /v1/transfers/{id}/refunds`. Each refund carries the `originalEndToEndId` and a new `returnIdentification` for end-to-end tracking.
</Note>

## Best practices

***

<AccordionGroup>
  <Accordion title="Use endToEndId as the primary reference">
    The `endToEndId` is the unique Pix identifier across the entire ecosystem — from the initiating institution through SPI to the receiving institution. Ensure it is stored in Midaz transaction metadata and present in the BACEN extract. Without it, reconciliation falls back to amount and date matching, which is far less reliable.
  </Accordion>

  <Accordion title="Run reconciliation the following day">
    Pix transactions near end of day may settle in BACEN on D+1. Scheduling Matcher for 07:00 UTC ensures all settlements from the previous day are included in the BACEN extract before matching runs. This eliminates false exceptions caused by timing.
  </Accordion>

  <Accordion title="Separate Pix IN and Pix OUT for high volume">
    When processing high Pix volumes, create two separate contexts — one for cash-out and one for cash-in. This simplifies exception triage, provides more granular metrics per flow, and allows independent scheduling if needed.
  </Accordion>

  <Accordion title="Monitor match rate">
    A healthy Pix reconciliation achieves greater than 99% automatic match rate. If the rate drops below 95%, investigate systemic issues such as plugin failures, BACEN format changes, or missing metadata in Midaz transactions.
  </Accordion>

  <Accordion title="Zero tolerance is the default">
    Pix has no intermediate fees, partial settlements, or processing charges. If amounts diverge between Midaz and BACEN, it indicates a real problem — not rounding. Keep both `feeToleranceAbs` and `feeTolerancePct` at zero.
  </Accordion>

  <Accordion title="Preview before committing">
    Always run a `DRY_RUN` before `COMMIT`, especially after rule or field map changes. This lets you review match results and catch configuration errors before they affect production data.
  </Accordion>
</AccordionGroup>

## Key metrics

***

Track these metrics to monitor the health of your Pix reconciliation process.

| Metric                     | Healthy value        | Alert threshold |
| -------------------------- | -------------------- | --------------- |
| Automatic match rate       | Above 99%            | Below 95%       |
| Daily exceptions           | Below 0.5% of volume | Above 2%        |
| Average resolution time    | Under 4 hours        | Over 24 hours   |
| Unresolved exceptions 48h+ | 0                    | More than 5     |

<Tip>
  Use Matcher's dashboard endpoints to monitor these metrics in real time. See [Dashboard metrics](/en/reference/matcher/get-dashboard-metrics).
</Tip>

## Next steps

***

<CardGroup cols={2}>
  <Card title="Contexts and sources" href="/en/matcher/configuration/matcher-contexts-and-sources">
    Learn how to configure and manage reconciliation contexts and data sources.
  </Card>

  <Card title="Match rules" href="/en/matcher/configuration/matcher-match-rules">
    Explore all available rule types and advanced matching configurations.
  </Card>

  <Card title="Midaz integration" href="/en/matcher/integrations/matcher-midaz-integration">
    Deep dive into automatic field mapping and real-time sync with Midaz Ledger.
  </Card>

  <Card title="Resolving exceptions" href="/en/matcher/daily-reconciliation/matcher-resolving-exceptions">
    Detailed guide on investigating, force matching, and managing reconciliation exceptions.
  </Card>
</CardGroup>
