# Mono API Docs — full corpus
> Official Mono API documentation. Banking infrastructure for Latin America — programmable pay-ins (PSE), payouts (ACH, Transfiya, Mono Turbo), cards, ledger primitives, and Bre-B participant integration. The content below is the complete corpus expanded inline for AI retrieval; the canonical HTML pages live at https://docs.mono.la/docs.
## Locale: en
# Who We Are (/docs/guides/who-we-are)
Mono is banking infrastructure for Latin America. We connect companies that move money, as part of their operation, to the Colombian banking network and Bre-B, the instant-payment scheme. Through a single API, our customers move money in and out of bank accounts, hold balances in a programmable ledger, issue cards, and orchestrate payments — without having to become a bank or build the integrations from scratch.
We exist because building financial products in Latin America is harder than it should be. Each rail (PSE, Turbo, ACH, Transfiya, Bre-B) has its own quirks, regulations, and operational rhythm. Mono absorbs that complexity behind one API so our customers can focus on what they want to build instead of on how to plumb it.
## What we do [#what-we-do]
| Capability | What it gives you |
| :---------------- | :------------------------------------------------------------------------------------------------------ |
| Banking | Pay-ins (PSE), payouts (ACH, Transfiya, Mono Turbo), and card issuing on the Colombian banking network. |
| Core | Programmable ledger, spending controls, payout orchestration. |
| Bre-B Participant | Programmable on-ramp to Bre-B — Colombia's 24/7 instant-payment scheme. |
## Who we serve [#who-we-serve]
Colombian companies that handle money as part of their product:
* **Fintechs** — neobanks, digital wallets, lending products, savings products.
* **Marketplaces** — buyer payments, seller payouts, escrow.
* **Embedded finance** — non-financial products that add a financial layer (gig platforms, SaaS with billing, B2B procurement).
* **EDEs and licensed entities** — institutions that need a Bre-B participant connection without building it themselves.
* **Treasury and corporate finance teams** — programmatic payouts at scale.
## How we operate [#how-we-operate]
We hold the regulatory and rail-level relationships so our customers do not have to. That means:
* **Native Bre-B coverage** as a participant, not a reseller.
* **Multi-rail bank transfers** with automatic fallback so a failed Mono Turbo retries on ACH.
* **Local regulation** — we operate under Colombian financial regulation and sustain the controls that come with it.
* **Production-grade reliability** — every API call is authenticated, idempotent, and observable; every state change is webhooked.
## Differentiators [#differentiators]
What makes Mono distinct, in the way a buyer should care:
* **One API, every rail.** Banking, Core, and Bre-B share the same authentication, error model, and operational conventions.
* **Native Bre-B.** Mono is a Bre-B participant, not a passthrough — that means lower latency, full payment-key control, and direct settlement.
* **Programmable ledger.** Core's ledger is double-entry and append-only by design; reconciliation is a query, not a project.
* **LATAM-first.** We are built for Colombian rails and Latin American product teams, not retrofitted from a US-first stack.
## Where we are [#where-we-are]
Today: **Colombia.** We are expanding regionally; reach out to your account team if you are evaluating Mono for a country we have not yet announced.
## Next steps [#next-steps]
* [Why Use Mono](/docs/guides/why-mono) — use cases and fit criteria.
* [Banking](/docs/guides/banking), [Core](/docs/guides/core), [Bre-B Participant](/docs/guides/breb-participant) — service guides.
* [Technical Support](/docs/guides/technical-support) — how to reach us.
# AI tools (MCP) (/docs/guides/ai-tools)
Mono publishes an **MCP server** for this documentation. MCP (Model Context
Protocol) is an open standard that lets AI assistants — like Claude or Cursor —
plug into outside tools and data. Connect Mono's MCP and your assistant can
search these docs, open full pages, and pull ready-made integration recipes
while you code. When you opt in, it can also run real calls against the **Bre-B**
and **Core** sandboxes — safe test environments that never move real money — so
you can try an integration end to end without leaving your editor.
It answers from the same documentation you are reading now and links back to the source, so
the guidance stays grounded and current — not invented.
## Before you start [#before-you-start]
You will need:
* An AI tool that supports MCP — for example **Claude Code** or **Cursor**.
* *For live sandbox calls only (optional):* sandbox credentials for the product
you want to test. Each product authenticates differently:
* **Bre-B** (Colombia's instant-payment network) — your sandbox `client_id`
and `client_secret` from the Mono dashboard, with the Bre-B scopes enabled
(collections, outgoing transfers, target resolutions, tenant accounts). The
MCP signs in with them and requests those scopes; if one is missing, the
sandbox calls are rejected.
* **Core** (programmable ledger and card issuing) — a Core **sandbox API
token** that Mono generates for you. The MCP forwards it on each call.
You can add Bre-B, Core, or both — the assistant gains each product's tools
when its credentials are present.
## What your assistant can do [#what-your-assistant-can-do]
Reading the docs needs nothing; running calls needs the matching sandbox
credentials.
**Read the docs (no credentials):**
* search the documentation by meaning, not just keywords;
* open any page in full;
* get a step-by-step integration guide for a use case;
* get the exact API reference for an endpoint — request and response examples,
fields, and allowed values.
**Run Bre-B sandbox calls:** create a collection, generate QR codes for it,
simulate a payment (by key or by QR), resolve a payment key, and send an
outgoing transfer.
**Run Core sandbox calls:** manage the ledger (accounts, balances, transfers)
and issue and manage cards (cardholders, cards, spending controls, activation).
## Connect to read the docs [#connect-to-read-the-docs]
This mode is hosted by Mono and needs no credentials. In **Claude Code**:
```bash
claude mcp add --transport http mono-docs https://docs.cert.monoban.co/api/mcp
```
Other MCP clients (Cursor, etc.) connect to the same URL over HTTP (also called
"Streamable HTTP"). Then just ask, in plain language:
> How do I receive my first Bre-B payment? Show me the create-collection request.
Your assistant searches the docs, pulls the integration guide and the exact
endpoint, and answers with working code and links to the source pages.
## Run live sandbox calls (optional) [#run-live-sandbox-calls-optional]
This is the same hosted MCP — you add credential headers when you connect, and
your assistant gains that product's tools on top of the docs tools. Credentials
travel on the connection itself, never inside a question, so they stay out of
your chat and out of the logs; Mono uses them to sign each call and then
discards them.
### Bre-B [#bre-b]
In **Claude Code**, add the two credential headers:
```bash
claude mcp add --transport http mono-docs https://docs.cert.monoban.co/api/mcp \
--header "X-Mono-Client-Id: " \
--header "X-Mono-Client-Secret: "
```
Then drive it in plain language. Start read-only to confirm the connection:
> List my Bre-B tenant accounts.
Then walk the full flow:
> Create a Bre-B collection for COP 50,000, simulate a payment to it, and
> confirm it is paid.
### Core [#core]
Add your Core sandbox token header:
```bash
claude mcp add --transport http mono-docs https://docs.cert.monoban.co/api/mcp \
--header "X-Mono-Core-Token: "
```
Then, again starting read-only:
> List my Core ledger accounts.
And the full flow:
> Create a cardholder, issue a card against a ledger account with a spending
> control, then show me the card.
### Both at once [#both-at-once]
To use Bre-B and Core tools on the same connection, add all three headers:
```bash
claude mcp add --transport http mono-docs https://docs.cert.monoban.co/api/mcp \
--header "X-Mono-Client-Id: " \
--header "X-Mono-Client-Secret: " \
--header "X-Mono-Core-Token: "
```
If you already connected for docs only, re-add it with these headers to enable
the sandbox tools.
**Sandbox only.** Live calls can only reach Mono's sandbox — never production, never real
money. Credentials are used per call and never stored, and any sensitive card data Core
returns in the sandbox is synthetic test data, not a real card.
## Next steps [#next-steps]
* [Bre-B collections (Recaudo)](/docs/guides/breb-participant/use-cases/collections) — receive your first payment.
* [Bre-B outgoing transfers](/docs/guides/breb-participant/use-cases/outgoing-transfers) — send a payout.
* [Issuing cards (Core)](/docs/guides/core/flows/issuing-cards) — issue and manage cards.
* [Technical support](/docs/guides/technical-support) — reach the Mono team.
# Technical Support (/docs/guides/technical-support)
Mono support is sized to the kind of question you have. Pre-sales conversations, integration questions during build, and production incidents go through different channels. This page tells you which one to use.
**Production incident?** If money is moving wrong or your customers are blocked, escalate
immediately to your account-team contact and email **[tech-support@mono.la](mailto:tech-support@mono.la)** with
**`[INCIDENT]`** in the subject line. Do not wait for business hours.
## Channels by need type [#channels-by-need-type]
| Need | Channel | Best for |
| :------------------ | :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------ |
| Pre-sales / scoping | Your account-team contact | Evaluating fit, pricing, capability questions, contract scoping. |
| Integration support | [tech-support@mono.la](mailto:tech-support@mono.la) | Build-time questions, error diagnosis, API behavior clarifications. |
| Production incident | [tech-support@mono.la](mailto:tech-support@mono.la) with `[INCIDENT]` in the subject + your account-team contact | Money moving incorrectly, integration down, customer impact. |
| Documentation issue | Open an issue on the docs repo | Typo, broken link, missing information. |
| Feature request | Your account-team contact | New rail, new endpoint, new behavior. |
## Escalating a production incident [#escalating-a-production-incident]
If your integration is failing in a way that affects real customers or moves money incorrectly:
1. **Stop the bleeding.** Pause the affected operation in your application if you can.
2. **Capture evidence.** Mono request IDs, timestamps, payloads, sample affected customer/transaction IDs.
3. **Email [tech-support@mono.la](mailto:tech-support@mono.la)** with `[INCIDENT]` in the subject line. Include the evidence.
4. **Notify your account-team contact** in parallel — by phone, message, or email.
5. **Stay reachable.** Keep the same thread open until the incident is resolved and post-mortem expectations are set.
## Next steps [#next-steps]
* [API standards: Errors and retries](/docs/guides/api-standards/errors-and-retries) — error envelope, status codes, and retry policy.
* [Banking best practices](/docs/guides/banking/best-practices) — production patterns to avoid common incidents.
* [Core best practices](/docs/guides/core/best-practices) — same, for Core.
# Getting Started (/docs/guides)
Mono is banking infrastructure for Latin America. Connect your company to the Colombian banking network, including Bre-B, the instant-payment scheme, without becoming a bank. Move money in and out, hold balances, issue cards, and track every cent.
These guides are written for everyone who works with Mono, not just engineers. Whether you are a product manager evaluating us, a finance lead figuring out reconciliation, a support agent looking up an error code, or a developer wiring up a webhook: it's all here.
## What you can achieve with Mono [#what-you-can-achieve-with-mono]
* **Pay-ins** — Accept payments from any Colombian bank account via PSE.
* **Payouts** — Send money to Colombian bank accounts via ACH, Transfiya, or Mono Turbo.
* **Virtual and physical cards** — Issue cards with a programmable balance and real-time spending controls.
* **Ledger accounts** — Hold balances for your users in a double-entry, fully auditable ledger.
* **Bre-B integrations** — Receive and send instant interbank payments via Bre-B.
## Pick your path [#pick-your-path]
Each section below answers a different question. Start where your question lives.
Mono in one page — what we do, who we serve, and how we operate.
Use cases, fit criteria, and what you can build that you could not build before.
Channels, response times, and how to escalate a production incident.
Authentication, idempotency, errors, formats, pagination — conventions that apply across
products.
## Services [#services]
Mono is organized around three services. Each one has its own guide tree.
Bank accounts, transfers (ACH, Transfiya, Mono Turbo), PSE collections, and card issuing.
Programmable ledger, card issuing, spending controls, payouts.
Send and receive Bre-B instant payments through the Mono API.
## If you want to do X, read Y [#if-you-want-to-do-x-read-y]
| You want to... | Start here |
| :---------------------------------------------------- | :--------------------------------------------------------------------------------- |
| Collect a payment via PSE on your e-commerce checkout | [Banking → PSE collection](/docs/guides/banking/flows/pse-collection) |
| Send payouts to Colombian bank accounts | [Banking → Sending transfers](/docs/guides/banking/flows/sending-transfers) |
| Build a wallet with balances and cards | [Core → Integration example](/docs/guides/core/integration-example) |
| Hold balances in a programmable ledger | [Core → Ledger](/docs/guides/core/concepts/ledger) |
| Issue virtual or physical cards | [Banking → Cards](/docs/guides/banking/concepts/cards) |
| Receive Bre-B payments via QR | [Bre-B → QR collection](/docs/guides/breb-participant/flows/qr-collection) |
| Send a Bre-B transfer | [Bre-B → Outgoing transfer](/docs/guides/breb-participant/flows/outgoing-transfer) |
| Understand authentication, errors, idempotency | [API standards](/docs/guides/api-standards/authentication) |
## Quick start [#quick-start]
From zero to your first call:
1. **Get credentials.** Sign in to the [Mono Dashboard](https://mi.cuentamono.com) and generate a sandbox API key for the service you are integrating.
2. **Read the relevant authentication page.** [Banking](/docs/guides/banking/api/authentication) and [Core](/docs/guides/core/authentication) use API keys; [Bre-B](/docs/guides/breb-participant/authentication) uses OAuth.
3. **Make a test call.** Use the curl snippet on the authentication page against the sandbox base URL.
4. **Wire a webhook.** Each service emits webhooks for state changes — register your endpoint and verify the first event.
5. **Run an end-to-end flow.** Pick the flow that matches your use case from the table above and follow it through sandbox.
## Coverage [#coverage]
Today Mono operates in **Colombia**. We are expanding regionally; reach out to your account team if you are evaluating Mono for a country we have not yet announced.
# Why Use Mono (/docs/guides/why-mono)
The answer to "why use Mono" depends on what you need. This page lays out the use cases we serve well, the products you compose to serve them, and the concrete benefits. Read this before scoping an integration — it will save discovery time later.
## Primary use cases [#primary-use-cases]
| Use case | What you build | Mono products you use |
| :----------------------------------- | :------------------------------------------------------ | :-------------------------------------------------- |
| Consumer wallet | Account, top-ups, card spending, peer-to-peer transfers | Banking (Cards + PSE) + Core (Ledger) |
| Marketplace seller payouts | Per-seller balances, scheduled payouts to bank accounts | Banking (Transfers) + Core (Ledger) |
| E-commerce checkout | PSE collection, settlement reconciliation | Banking (Collection links) |
| Corporate cards / expense management | Per-employee cards with category and limit rules | Banking (Cards) + Core (Spending Controls + Ledger) |
| Bre-B for a bank or EDE | Programmable on-ramp to Bre-B for sending and receiving | Bre-B Participant |
| Treasury automation | Programmatic transfers across accounts and rails | Banking (Transfers) + Core (Ledger) |
| Lending disbursement | Push approved loan funds to borrower bank accounts | Banking (Transfers) |
| Payroll | Mass payouts to employee bank accounts | Banking (Batch transfers) |
## Concrete benefits [#concrete-benefits]
What our customers get measurably from picking Mono:
* **Integration time in weeks, not quarters.** One API, one auth model, one error model — across Banking, Core, and Bre-B.
* **Real-time settlement where the rail allows.** Mono Turbo and Bre-B settle in seconds; ACH on the standard same-day cycle.
* **Native Bre-B.** As a Bre-B participant, Mono offers full payment-key control and direct settlement instead of a passthrough.
* **Reconciliation built in.** Core's ledger is double-entry and append-only; daily reconciliation is a query, not a custom job.
* **Multi-rail fallback.** A Mono Turbo transfer that does not land on the destination bank automatically retries on ACH — your code does not have to.
* **Production observability.** Every API call carries a request ID; every state change emits a signed webhook.
## When Mono is the right fit [#when-mono-is-the-right-fit]
You will get the most value from Mono if you:
* Operate in Colombia (today) or are planning a Colombia launch.
* Need at least one of: bank account on/off-ramp, Bre-B, programmable balances, card issuing.
* Have engineering capacity to integrate an API and operate the resulting product.
* Care about reconciliation and audit trails — not just moving money once but tracking it forever.
## Next steps [#next-steps]
* [Banking](/docs/guides/banking) — pay-ins and payouts.
* [Core](/docs/guides/core) — ledger, cards, payouts.
* [Bre-B Participant](/docs/guides/breb-participant) — instant interbank payments.
* [Technical Support](/docs/guides/technical-support) — talk to us.
# Getting started (/docs/api-reference)
Welcome to the Mono API Reference. This documentation provides comprehensive information about our APIs and services.
## Available services [#available-services]
Create and manage outgoing transfers and collections through the Bre-B system with real-time
webhook notifications
Essential financial infrastructure including ledger management, payouts, cards, and spending
controls
Banking operations including accounts, transfers, collection links, and banking cards
## Need Help? [#need-help]
If you have questions or need assistance integrating with our API, please contact our [support team](/docs/guides/technical-support).
# Integration example (/docs/guides/banking/integration-example)
This page walks through a representative Banking integration end-to-end: a **PSE collector for e-commerce**. You will see the full path from a customer clicking "pay" in your checkout to your system marking the order as paid and shipping the goods.
PSE collection is the most common Banking use case. The same pattern transfers to invoice settlement, subscription top-ups, and any payer-initiated payment.
This page is an integration-example scaffold. The full version will include code per step
(curl, Node.js, Python), a sequence diagram, error handling, and reconciliation patterns.
## What you will build [#what-you-will-build]
An e-commerce checkout that:
* Generates a PSE collection link when the customer chooses "pay with bank account".
* Redirects the customer to PSE for bank selection and authorization.
* Receives a webhook when the bank confirms the payment.
* Marks the order as paid and triggers fulfillment.
* Handles the failure cases — abandoned, expired, declined.
## Architecture [#architecture]
| Layer | Component |
| :------- | :-------------------------------------------------------------- |
| Frontend | Checkout UI, redirect to PSE, return-URL handling |
| Backend | Collection-link creation, webhook receiver, order state machine |
| Mono | Banking API + collection-link webhooks |
| External | PSE network + customer's bank |
## Walkthrough [#walkthrough]
The full walkthrough is pending. Until it is published, see:
* [Collection Links](/docs/guides/banking/concepts/collection-links)
* [PSE Collection flow](/docs/guides/banking/flows/pse-collection)
* [Collection-link webhooks](/docs/guides/banking/webhooks/collection-links)
## Next steps [#next-steps]
* [PSE collection flow](/docs/guides/banking/flows/pse-collection)
* [Sandbox](/docs/guides/banking/sandbox)
* [Best practices](/docs/guides/banking/best-practices)
# Glossary (/docs/guides/banking/glossary)
A short alphabetical reference for the terms you will encounter while integrating Banking. Each entry is a one or two sentence plain-language definition. For the full data model behind any of these, follow the link to the corresponding concept page.
This glossary is a starting point and will grow. If a term you need is missing, please open an
issue on the docs repo so we can add it.
## ACH [#ach]
The standard same-day rail used in Colombia for bank-to-bank transfers. ACH transfers are not real-time; settlement happens in cycles during banking hours.
## Bank account [#bank-account]
A funding account at a Colombian bank, identified by an account number and bank code. See [Bank accounts](/docs/guides/banking/concepts/accounts).
## Bank transfer [#bank-transfer]
A payout from your Mono account to a beneficiary's bank account, dispatched across one of the Banking rails (ACH, Transfiya, or Mono Turbo). See [Bank transfers](/docs/guides/banking/concepts/transfers).
## Card [#card]
A payment instrument issued by Mono that spends against a Mono account. Cards can be virtual or physical and can be debit (linked to the account balance) or prepaid (with their own credited balance). See [Cards](/docs/guides/banking/concepts/cards).
## Collection link [#collection-link]
A payment link your business generates and sends to a customer to receive a PSE payment. See [Collection links](/docs/guides/banking/concepts/collection-links).
## Idempotency key [#idempotency-key]
A unique label your system attaches to a write request so that Banking treats a retry as the same operation. See [Idempotency Keys](/docs/guides/api-standards/idempotency-keys).
## Mono Turbo [#mono-turbo]
Mono's real-time payout rail for transfers to participating Colombian banks. Falls back to ACH when the destination does not support real-time settlement.
## Physical card [#physical-card]
A plastic card issued by Mono. Used for payments in physical stores, ATM withdrawals, and online purchases. Requires activation before use. See [Cards](/docs/guides/banking/concepts/cards).
## Production [#production]
The live environment where Banking moves real money. Production credentials and sandbox credentials are not interchangeable. See [Authentication](/docs/guides/api-standards/authentication).
## PSE (Pagos Seguros en Línea) [#pse-pagos-seguros-en-línea]
Colombia's standard system for paying from a bank account on a website. The local equivalent to Brazil's Pix or US ACH Direct Debit. See [Collection links](/docs/guides/banking/concepts/collection-links).
## Routing [#routing]
The choice of which rail (ACH, Transfiya, Mono Turbo) Mono uses to dispatch a transfer. You can request a specific routing or let Mono decide.
## Sandbox [#sandbox]
The test environment where Banking integrations can exercise the full API without moving real money. Sandbox credentials only work against sandbox base URLs.
## Settlement [#settlement]
The point at which a transfer or collection moves from "accepted" to a final state — money credited to the destination account. Mono confirms settlement via webhook.
## Transfiya [#transfiya]
A real-time inter-bank rail in Colombia for transfers within a specific bank cluster.
## Virtual card [#virtual-card]
A card issued by Mono that exists only digitally. Available immediately and used for online purchases or digital subscriptions. See [Cards](/docs/guides/banking/concepts/cards).
## Webhook [#webhook]
An automatic HTTP notification Mono sends to your endpoint when a Banking event happens (transfer settled, collection paid, card created, etc.). See [Webhooks](/docs/guides/banking/webhooks).
# Best practices (/docs/guides/banking/best-practices)
This page collects the operational practices that separate a Banking integration that works in sandbox from one that survives production. They are how you avoid duplicate transfers, recover gracefully when a rail times out, and reconcile the books at the end of each day.
If you are deploying Banking to handle real money, treat this page as a launch checklist and as a reference during incidents.
This page is a best-practices scaffold. The full version will include concrete examples,
anti-patterns to avoid, and runbooks for the most common production failure modes.
## Idempotency on every transfer [#idempotency-on-every-transfer]
Every transfer-creation request must include an [idempotency key](/docs/guides/api-standards/idempotency-keys). Without it, a network timeout will silently double-send.
## Treat synchronous responses as "accepted", not "settled" [#treat-synchronous-responses-as-accepted-not-settled]
A `200` from a bank-transfer creation means Mono accepted the request — not that the rail confirmed credit. Wait for the webhook before marking the operation as settled in your books.
## Plan for fallbacks [#plan-for-fallbacks]
Mono Turbo can fall back to ACH automatically. Your reconciliation must handle a transfer requested as Mono Turbo that ultimately settled as ACH — the rail-level fees and timing differ. Read the rail field on the confirmation webhook.
## Reconcile daily [#reconcile-daily]
Run a daily reconciliation that compares your view of every transfer (and its terminal state) against Mono's. Surface drift the day it appears, not the week after.
## Verify webhook signatures [#verify-webhook-signatures]
Every Banking webhook is signed. Verify the signature before trusting the payload — see the webhook documentation.
## Rotate API keys [#rotate-api-keys]
Rotate API keys at least every 90 days and immediately after any suspected exposure. Keys do not expire on their own.
## Antipatterns to avoid [#antipatterns-to-avoid]
* Sending a fresh idempotency key on every retry.
* Treating PSE collection-link creation as confirmation that the customer paid.
* Hardcoding rail choices that block fallback.
* Logging API keys, signed payloads, or PII.
## Next steps [#next-steps]
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys)
* [Production checklist](/docs/guides/banking/production)
* [Webhooks](/docs/guides/banking/webhooks)
# Go to Production (/docs/guides/banking/production)
To migrate the integration done in the sandbox environment to production, you must fulfill the tests established by Mono depending on the product you are integrating. However, there are some specifications that all Mono clients must adhere to:
## General Requirements [#general-requirements]
### Mono Response Configuration [#mono-response-configuration]
You will need to configure all possible responses provided by Mono each time you make a request.
### Webhooks Configuration [#webhooks-configuration]
All response events sent by Mono must be configured. With these, you can have immediate control whenever there is a change in your Mono account.
***
## Collection Links [#collection-links]
Before moving your collection links integration to production, you will review with our integration team that the following requests are configured in the best way:
* Successfully create a collection link
* List all collection links in your Mono account
* Retrieve a collection link by its ID
***
## Bank Transfers [#bank-transfers]
Before moving your bank transfers integration to production, you will review with our integration team that the following requests are configured in the best way:
* List the banks you will use for the bank transfers
* Get bank transfers made in your account
* Successfully create a bank transfer
***
## Ledger and Cards [#ledger-and-cards]
Before moving your ledger integration to production, you will review with our integration team that the following requests are configured in the best way:
* Successfully create an account holder
* Successfully update an account holder state
* Successfully create a ledger account
* Successfully update a ledger account state
* Successfully create a card linked to the ledger account
* Successfully update a card
* Retrieve balance by ledger account
* Retrieve the operations you have made for the balance of a card
* Add money to a ledger account
* Withdraw money from a ledger account
* Retrieve the movements per ledger account
* Retrieve configuration information for your tenant account
# Banking (/docs/guides/banking)
Banking lets you receive money from a payer's bank account through PSE, send money to Colombian bank accounts through ACH, Transfiya, or Mono Turbo, and issue virtual and physical cards that spend against a Mono account balance. It is the part of Mono that connects your product to the Colombian banking network. Explore the sections below to learn the key concepts, integrate the API, handle webhook events, and test in sandbox before going to production.
Learn about accounts, transfers, collection links, and cards
Authentication and API integration guides
Configure and handle webhook events
Test your integration in the sandbox environment
Requirements and tests before migrating to production
# Architecture overview (/docs/guides/banking/architecture)
# Dashboard (/docs/guides/banking/dashboard)
The Mono Dashboard is the web interface where non-developer roles — operations, finance, customer support, compliance — interact with Banking. Everything the API exposes can be inspected from the Dashboard, and many actions (creating a one-off transfer, generating a collection link, downloading a reconciliation report) can be performed without writing code.
This page describes what each Banking-related section of the Dashboard does and which role typically uses it. It is the right starting point for someone joining the team who needs to operate Banking but does not write the integration.
This page is a dashboard scaffold. The full version will include screenshots of every section,
role-based access notes, and end-to-end examples.
## Sections [#sections]
| Section | What it shows | Who uses it |
| :--------------- | :----------------------------------------------------------- | :-------------------- |
| Bank accounts | Source accounts and their balances. | Finance, ops |
| Bank transfers | Outgoing and incoming transfers, status, rail used, retries. | Finance, ops |
| Collection links | Active and historical PSE collection links, payment status. | Ops, customer support |
| Webhooks | Endpoint configuration, recent deliveries, retry attempts. | Engineering |
| API keys | Key management, roles, rotation. | Engineering, security |
| Reports | Daily reconciliation reports, transaction exports. | Finance |
## Common operations [#common-operations]
Specific walkthroughs (issuing a manual transfer, retrying a failed transfer, regenerating a collection link, exporting a reconciliation report) will be documented here.
## Webhook configuration [#webhook-configuration]
### Creating a webhook [#creating-a-webhook]
Webhook creation is done through the user interface:
1. Go to settings.
2. Click on Personal.
3. Click on API.
4. Click new webhook.
5. Insert the HTTPS/HTTP URL that you had configured to listen the events.
6. Click on save.
If everything is working as expected, there will be an alert indicating that we successfully created your webhook, and you should be able to see the URL listed on the page.
Webhook URLs are unique — you can only create one webhook per URL. To reuse a URL, delete the existing webhook first and create a new one. A new secret will be generated.
Always verify that your endpoint is receiving events correctly.
### Viewing webhook details [#viewing-webhook-details]
Click the arrow at the end of the row where your URL is displayed to open the detail view. The detail view contains the webhook secret, which is hidden by default. Click the **show/hide eyeball** to reveal it.
Keep your webhook secret secure. Do not share it.
## Next steps [#next-steps]
* [Webhook conventions](/docs/guides/api-standards/webhooks) — signature verification, retry policy, and money fields.
* [Architecture](/docs/guides/banking/architecture)
* [Best practices](/docs/guides/banking/best-practices)
# Integration example (/docs/guides/breb-participant/integration-example)
This page walks through a representative Bre-B integration end-to-end: a **bank or licensed entity that wants to let its customers send and receive Bre-B payments from inside the bank app**. You will see the full path from token exchange to QR generation, payment receipt, target resolution, outgoing transfer, and webhook handling.
If you are building a different Bre-B use case — a marketplace acquirer, a wallet that bridges to Bre-B, a treasury system — the patterns transfer directly; only the entity names change.
This page is an integration-example scaffold. The full version will include code per step,
sequence diagrams for both the payin and payout flows, and a full reference architecture.
## What you will build [#what-you-will-build]
A Bre-B participant integration that supports:
* **Onboarding** — register a tenant account that will hold balances and receive Bre-B payments.
* **Receive payments** — generate Bre-B QR codes and reconcile collection events.
* **Send payments** — resolve a destination payment key, confirm with the user, and dispatch an outgoing transfer.
* **Lifecycle** — track outgoing transfer states and handle errors.
## Architecture [#architecture]
| Layer | Component |
| :------- | :---------------------------------------------------------------------------- |
| Frontend | Customer-facing UI for QR display and outgoing-transfer initiation |
| Backend | OAuth refresh, collection generation, resolution + dispatch, webhook receiver |
| Mono | Bre-B Participant API + webhooks |
| External | Bre-B central directory + counterparty banks |
## Walkthrough [#walkthrough]
The full walkthrough is pending. Until it is published, see:
* [Outgoing transfer flow](/docs/guides/breb-participant/flows/outgoing-transfer)
* [QR collection flow](/docs/guides/breb-participant/flows/qr-collection)
* [Outgoing transfer states](/docs/guides/breb-participant/architecture/outgoing-transfer-states)
* [Webhook payloads](/docs/guides/breb-participant/webhook-payloads)
## Next steps [#next-steps]
* [QR collection flow](/docs/guides/breb-participant/flows/qr-collection)
* [Outgoing transfer flow](/docs/guides/breb-participant/flows/outgoing-transfer)
* [Best practices](/docs/guides/breb-participant/best-practices)
# Glossary (/docs/guides/breb-participant/glossary)
A short alphabetical reference for the terms you will encounter while integrating Bre-B Participant. Each entry is a one or two sentence plain-language definition. For the full data model behind any of these, follow the link to the corresponding concept page.
This glossary is a starting point and will grow. If a term you need is missing, please open an
issue on the docs repo so we can add it.
## Bre-B [#bre-b]
Colombia's interbank instant-payment scheme — a real-time, 24/7 rail similar to Brazil's Pix or India's UPI.
## Central directory [#central-directory]
The Bre-B-operated registry that maps payment keys to holder identity and destination bank. Every resolution query goes against the central directory.
## Collection [#collection]
An incoming Bre-B payment to one of your tenant accounts. You receive collections by displaying a Bre-B QR code or otherwise sharing a payment key.
## EDE (Entidad de Depósito Electrónico) [#ede-entidad-de-depósito-electrónico]
A Colombian regulatory category for licensed deposit-taking entities. Many Bre-B participants are EDEs.
## Outgoing transfer [#outgoing-transfer]
A Bre-B payment your tenant initiates against another participant's payment key.
## Participant [#participant]
A bank or licensed entity authorized to transact on Bre-B. Mono offers Bre-B participant access through its API. In the `target` object, the receiving participant appears as `participant_nit` and `participant_name`; the name is `null` when Mono does not have it yet.
## Payment key [#payment-key]
An alias that identifies a destination on Bre-B — a phone number, document ID, email, an alphanumeric Bre-B-issued identifier, or a merchant code. Used for both sending (you specify the destination key) and receiving (your tenant has registered keys).
## QR (Bre-B QR) [#qr-bre-b-qr]
An EMVCo-compliant QR code encoding a Bre-B payment instruction. Customers scan it from their banking app to authorize a transfer.
## Resolution [#resolution]
The process of looking up a payment key against the central directory to obtain the holder's name and bank before dispatching an outgoing transfer.
## Target [#target]
The resolved destination of an outgoing transfer. Returned by a successful resolution and required to dispatch.
## Tenant account [#tenant-account]
A logical account at Mono that holds balances and is the source/destination of Bre-B operations.
## Webhook signature [#webhook-signature]
An HMAC-SHA256 signature attached to every Bre-B webhook so you can verify the payload came from Mono. See [Webhook signatures](/docs/guides/breb-participant/webhook-signatures).
# Webhook Payload Reference (/docs/guides/breb-participant/webhook-payloads)
This guide describes the envelope Mono uses for every Bre-B participant webhook
delivery and lists the outgoing-transfer and target-resolution events your endpoint can
expect.
Before trusting any payload, verify the `Mono-Signature` header. See [Webhook Signature
Verification](/docs/guides/breb-participant/webhook-signatures).
## Payload structure [#payload-structure]
All webhooks share the same envelope. The `event.type` string tells you which
event fired and implicitly which fields to expect inside `event.data`.
```ts
interface WebhookPayload {
timestamp: string; // ISO 8601 delivery time
event: {
type: string; // e.g., "outgoing_transfer.created", "outgoing_transfer.failed"
data: {
id: string;
state: string;
// ...additional fields depend on event.type
};
};
}
```
For the exact shape of each event's `data` object, follow the link in the
events table below — every event has a dedicated schema in the API reference.
## Outgoing transfer events [#outgoing-transfer-events]
| Event | Description |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `outgoing_transfer.created` | Transfer was created. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_created) |
| `outgoing_transfer.processing` | Transfer is being processed. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_processing) |
| `outgoing_transfer.target_resolved` | Target (Bre-B key) was resolved. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_target_resolved) |
| `outgoing_transfer.held` | Funds have been held on the source account. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_held) |
| `outgoing_transfer.sent_to_breb_provider` | Transfer was sent to the Bre-B provider. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_send_to_breb_provider) |
| `outgoing_transfer.successful` | Transfer completed successfully. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_successful) |
| `outgoing_transfer.failed` | Transfer failed. Inspect `state_reason` for the cause. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_failed) |
When you receive `outgoing_transfer.failed`, the `state_reason` field tells you
why — see [Outgoing Transfer Error Codes](/docs/guides/breb-participant/outgoing-transfer-errors)
for the full list and suggested handling per code.
## Target resolution events [#target-resolution-events]
Every resolution request emits these events — whether you created it with
[Resolve target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target) or a transfer by key created it for you. `event.data` is the
resolution request: the `expected_creditor` you sent (or `null`) and, once resolved, the
`target`.
| Event | Description |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target_resolution.created` | The resolution request was created. [Schema](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_created) |
| `target_resolution.resolved` | The key was resolved; `data.target` carries the destination. [Schema](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_resolved) |
| `target_resolution.failed` | The key could not be resolved. `state_reason` is `target_creditor_mismatch` when the holder did not match your `expected_creditor`; no target was persisted. [Schema](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_failed) |
# Authentication (/docs/guides/breb-participant/authentication)
The Bre-B Participant API uses **OAuth 2.0 client credentials** for authentication. Unlike Banking and Core, which also accept static API keys, Bre-B requires you to exchange a client ID and secret for a short-lived **access token**, and then attach that token to every API call. The token expires; your code refreshes it automatically before it does.
If you have read [api-standards/authentication](/docs/guides/api-standards/authentication), this page only adds the Bre-B specifics: the token-exchange endpoint, the token lifetime, and the rotation procedure.
This page is an authentication scaffold. The full version will document the token-exchange
request and response, error handling, and recommended client patterns.
## Before you start [#before-you-start]
You need:
* A Bre-B participant agreement with Mono.
* Client credentials (client ID and client secret) issued by Mono for your tenant.
* The participant base URL for your environment (sandbox or production).
## Token exchange [#token-exchange]
The full token-exchange request, response, and refresh strategy will be documented here. See [Bre-B Participant authentication API reference](/docs/api-reference/breb-participant/authentication) for the endpoint contract.
## Sending the token [#sending-the-token]
```
Authorization: Bearer
```
## Scopes [#scopes]
A scope is a single permission attached to an access token. Request the narrowest scopes your integration needs, separated by spaces in the token request. Each resource has two scopes:
* The bare scope, such as `collections`, grants every action on that resource.
* The `:readonly` scope grants reads only.
| Scope | Grants |
| :---------------------------- | :-------------------------------------------- |
| `outgoing_transfers` | All permissions for outgoing transfers |
| `outgoing_transfers:readonly` | Read outgoing transfers |
| `target_resolutions` | Resolve Bre-B keys and read their resolutions |
| `target_resolutions:readonly` | Read target resolutions |
| `target_discoveries` | Discover Bre-B keys and read the bank catalog |
| `target_discoveries:readonly` | Read the bank catalog |
| `collections` | All permissions for collections |
| `collections:readonly` | Read collections |
| `tenant_accounts` | All permissions for tenant accounts |
| `tenant_accounts:readonly` | Read tenant accounts |
A Bre-B key is an alias, such as a phone number, that points to a bank account.
### Target discovery scopes [#target-discovery-scopes]
Use `target_discoveries` to find a beneficiary's Bre-B key from their identity and bank-account data, when you do not know the exact key. Use `target_resolutions` when you already know the key and call
`POST /api/v1/targets/resolve`, and `outgoing_transfers` when you create the
payment. See the [target discovery
flow](/docs/guides/breb-participant/flows/target-discovery) for the complete
sequence.
## Failures [#failures]
| Status | Meaning |
| :----------------- | :------------------------------------------------- |
| `401 Unauthorized` | Token missing, expired, or revoked — refresh it. |
| `403 Forbidden` | Token valid but lacks the scope for this endpoint. |
## Rotating credentials [#rotating-credentials]
Tokens expire automatically; rotation in the operational sense means rotating the client secret used to obtain them. Rotate the client secret at least every 90 days and immediately after any suspected exposure.
## Next steps [#next-steps]
* [Architecture](/docs/guides/breb-participant/architecture)
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys)
* [Best practices](/docs/guides/breb-participant/best-practices)
# Best practices (/docs/guides/breb-participant/best-practices)
This page collects the operational practices that separate a Bre-B integration that works in sandbox from one that survives production. Bre-B is real-time and 24/7 — there is no overnight batch window where mistakes go unnoticed. The recommendations below are how you stay out of trouble.
If you are deploying Bre-B Participant to production, treat this page as a launch checklist and as a reference during incidents.
This page is a best-practices scaffold. The full version will include concrete examples,
anti-patterns, and runbooks for the most common production failure modes.
## Always confirm the resolved name with the payer [#always-confirm-the-resolved-name-with-the-payer]
A Bre-B payment key is an alias. Resolution returns the holder's name and the receiving institution (`participant_name`) — display both to the payer and require explicit confirmation before dispatching. This is both a fraud-prevention requirement and a regulatory expectation.
## Idempotency on every dispatch [#idempotency-on-every-dispatch]
Every outgoing-transfer request must include an [idempotency key](/docs/guides/api-standards/idempotency-keys). A network retry without one is a duplicate transfer.
## Verify webhook signatures [#verify-webhook-signatures]
Every Bre-B webhook is signed with HMAC-SHA256. Verify the signature before trusting the payload — see [Webhook signatures](/docs/guides/breb-participant/webhook-signatures).
## Treat synchronous responses as "accepted", not "settled" [#treat-synchronous-responses-as-accepted-not-settled]
A `200` from an outgoing-transfer dispatch means Mono accepted the request. The terminal state arrives via webhook. Do not credit or debit anything in your books until you receive the settlement event.
## Handle the full state machine [#handle-the-full-state-machine]
Bre-B outgoing transfers traverse multiple states — pending, sent, accepted, settled, rejected. Map every state in your application. Surface rejections to the user using the [error catalog](/docs/guides/breb-participant/outgoing-transfer-errors).
## Reconcile every day [#reconcile-every-day]
Run a daily reconciliation against Mono. Bre-B's 24/7 nature means operational issues compound quickly without it.
## Rotate the client secret [#rotate-the-client-secret]
OAuth tokens expire automatically. Rotate the client secret used to mint them at least every 90 days and immediately after any suspected exposure.
## Antipatterns to avoid [#antipatterns-to-avoid]
* Skipping name confirmation on the resolved target.
* Dispatching without an idempotency key.
* Trusting an unsigned webhook payload.
* Treating dispatch acceptance as settlement.
* Hardcoding webhook URLs without a rotation path.
## Next steps [#next-steps]
* [Webhook signatures](/docs/guides/breb-participant/webhook-signatures)
* [Outgoing transfer errors](/docs/guides/breb-participant/outgoing-transfer-errors)
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys)
# Outgoing Transfer Error Codes (/docs/guides/breb-participant/outgoing-transfer-errors)
Errors when creating and processing outgoing transfers fall into two categories
depending on **when** they're surfaced:
1. **Rejection reasons** — validation errors returned **synchronously** when you
create a batch. The rejected transfer is never persisted.
2. **State reasons** — errors that occur during **asynchronous** processing.
You learn about them via `outgoing_transfer.failed` webhooks, where the
`state_reason` field carries the code.
## Rejection reasons [#rejection-reasons]
These errors are returned in the `rejected_transfers` array of the
[create outgoing transfers](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers)
response. Rejected transfers are **not created** in the system — no webhook
will fire for them, so you must handle them from the synchronous response.
### Example response [#example-response]
```json
{
"id": "batch_123e4567-e89b-12d3-a456-426614174000",
"state": "created",
"transfers": [],
"rejected_transfers": [
{
"external_id": "transfer_001",
"reason": "amount_exceeds_max_limit"
},
{
"external_id": "transfer_002",
"reason": "target_not_found"
}
]
}
```
### Code reference [#code-reference]
| Code | Description | Cause | Suggested fix |
| -------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `amount_not_provided` | Amount not provided | The `amount` field is required but was missing or `null`. | Include `amount` with a valid value. |
| `amount_exceeds_max_limit` | Amount exceeds the maximum limit | The transfer amount exceeds the maximum allowed per transaction. | Reduce the amount or split into multiple transfers. |
| `target_amount_mismatch` | Amount does not match the target | When using an existing `target_id` with a fixed `transaction_amount` (e.g., a static QR), the sent amount must match exactly. | Send the target's amount, or omit `amount` to use the target's value. |
| `target_already_exists` | Target already used | An active (non-failed) transfer already uses this `target_id`. Each target may only be consumed once. | Use a different target or wait for the in-flight transfer to reach a terminal state. |
| `target_not_found` | Target not found | The supplied `target_id` does not exist or does not belong to the current tenant. | Verify the `target_id` is correct and owned by your tenant. |
## State reasons [#state-reasons]
These errors occur during asynchronous processing and are delivered via the
[`outgoing_transfer.failed`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_failed)
webhook, with the machine-readable code in `event.data.state_reason`.
### Example webhook payload [#example-webhook-payload]
```json
{
"timestamp": "2025-12-17T20:14:01.947520Z",
"event": {
"type": "outgoing_transfer.failed",
"data": {
"id": "bbot_031uOJ6qb0sVclNseQfItQ",
"state": "failed",
"state_reason": "target_creditor_mismatch",
"external_id": "21752385-357e-450f-94a3-d9a0984da291",
"amount": { "currency": "COP", "amount": 100 },
"expected_creditor": {
"document_type": "CC",
"document_number": "21482961"
},
"target": {
"creditor": {
"document_type": "CC",
"document_number": "123143455",
"full_name": "Juan Carlos Perez Gomez"
}
}
}
}
}
```
### Key resolution errors [#key-resolution-errors]
Raised while resolving the recipient's Bre-B key.
| Code | Description | Cause | Suggested fix |
| -------------------- | ------------------ | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `key_not_found` | Key not found | The Bre-B key does not exist in the instant payments system. | Verify the key. The recipient must have a registered key. |
| `key_suspended` | Key suspended | The recipient's Bre-B key is temporarily suspended. | Ask the recipient to regularize their key with their financial institution. |
| `invalid_key_format` | Invalid key format | The key format does not match Bre-B rules. | Verify the format against the [key types table](/docs/guides/breb-participant/concepts/payment-keys#key-types). |
### Recipient validation errors [#recipient-validation-errors]
Raised when recipient data is invalid or doesn't match the key owner.
| Code | Description | Cause | Suggested fix |
| ---------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `target_creditor_mismatch` | Creditor mismatch | The `expected_creditor` (document type + number) does not match the actual owner of the Bre-B key. | Verify recipient data before sending. This check prevents transfers to the wrong person. |
| `creditor_account_not_found` | Creditor account not found | The bank account associated with the key no longer exists or was closed. | The recipient should verify the account with their financial institution. |
| `invalid_creditor_account` | Invalid creditor account | The recipient's bank account cannot receive transfers (blocked, inactive, etc.). | The recipient should regularize their bank account. |
Send `expected_creditor` to [Resolve
target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target) and the
mismatch surfaces earlier: the call answers `400` with `target_creditor_mismatch`, no target
is persisted, and the
[`target_resolution.failed`](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_failed)
webhook carries the same `state_reason`. A `target_id` resolved this way already belongs to
the expected person.
### Funds errors [#funds-errors]
Raised during funds-hold.
| Code | Description | Cause | Suggested fix |
| ------------------------------ | ---------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------- |
| `insufficient_funds` | Insufficient funds | The source account does not have enough balance to cover the transfer. | Ensure there is available balance before creating the transfer. |
| `amount_exceeds_balance_limit` | Amount exceeds balance limit | The amount would push the recipient beyond their allowed balance limit. | Reduce the transfer amount. |
### Provider errors [#provider-errors]
Raised due to Bre-B provider or upstream issues.
| Code | Description | Cause | Suggested fix |
| ---------------------- | ------------------------ | ---------------------------------------------------------------- | ---------------------------------------------------------------- |
| `risk_control` | Rejected by risk control | The transfer was rejected by the provider's risk control system. | Review the transaction patterns. May require contacting support. |
| `breb_timeout` | Bre-B provider timeout | The Bre-B provider did not respond in time. | Retry later. If it persists, contact support. |
| `provider_unavailable` | Provider unavailable | The Bre-B provider is temporarily out of service. | Retry later. |
| `unknown` | Unknown error | An uncategorized error occurred. | Contact technical support with the transfer `id`. |
## Integration recommendations [#integration-recommendations]
### 1. Validate before sending [#1-validate-before-sending]
Before calling the create-transfers endpoint, validate:
* The amount is within the allowed limits.
* Recipient data is correct (if using `expected_creditor`).
* The source account has sufficient balance.
### 2. Handle `rejected_transfers` from the batch response [#2-handle-rejected_transfers-from-the-batch-response]
```ts
const response = await createBatch(batchData);
if (response.rejected_transfers.length > 0) {
for (const rejected of response.rejected_transfers) {
console.error(`Transfer ${rejected.external_id} rejected: ${rejected.reason}`);
// Notify the user or flag for review
}
}
```
### 3. Handle `outgoing_transfer.failed` webhooks [#3-handle-outgoing_transferfailed-webhooks]
```ts
app.post('/webhook', (req, res) => {
const { event } = req.body;
if (event.type === 'outgoing_transfer.failed') {
const { id, external_id, state_reason } = event.data;
switch (state_reason) {
case 'insufficient_funds':
// Notify the user that funds are missing
break;
case 'target_creditor_mismatch':
// Ask the user to re-verify recipient details
break;
case 'key_not_found':
// Surface that the key is invalid
break;
default:
// Fallback handling
}
}
res.status(200).send();
});
```
### 4. Retry guidance [#4-retry-guidance]
| Error | Retryable | Recommendation |
| -------------------------- | --------- | ------------------------------------------ |
| `breb_timeout` | Yes | Retry after 1–5 minutes. |
| `provider_unavailable` | Yes | Retry after 5–15 minutes. |
| `insufficient_funds` | Yes\* | Retry once funds are available. |
| `key_not_found` | No | Confirm the recipient's key with the user. |
| `target_creditor_mismatch` | No | Correct the recipient data. |
| `risk_control` | No | Contact support. |
When contacting support about a failed transfer, provide the transfer `id` (and `batch` id if
applicable), the `external_id` for correlation, and a description of the scenario and
frequency.
# Webhook Signature Verification (/docs/guides/breb-participant/webhook-signatures)
Every webhook delivered by Mono is signed so you can confirm it came from us and
hasn't been tampered with in transit. This guide walks through the signing
algorithm, a reference TypeScript implementation, and how to wire it up in
Express.js.
## Signing algorithm [#signing-algorithm]
Webhooks are signed with **HMAC-SHA256**. The signature is computed over a
concatenation of the delivery timestamp and the raw request body:
```
signed_payload = "."
signature = HMAC-SHA256(secret, signed_payload)
```
### Signature header [#signature-header]
The `Mono-Signature` header carries both the timestamp and the signature:
```
Mono-Signature: t=,v1=
```
Example:
```
Mono-Signature: t=1766002441,v1=62afda2079925823b390e1199060d793aa50d64ec9d7bf184f5b7e96c8bf411c
```
## Reference implementation (TypeScript) [#reference-implementation-typescript]
```ts
import { createHmac, timingSafeEqual } from 'crypto';
interface WebhookVerificationResult {
valid: boolean;
error?: string;
}
function parseSignatureHeader(header: string): {
timestamp: string | null;
signatures: string[];
} {
const parts = header.split(',');
let timestamp: string | null = null;
const signatures: string[] = [];
for (const part of parts) {
const [key, value] = part.split('=');
if (key === 't') timestamp = value;
else if (key === 'v1') signatures.push(value);
}
return { timestamp, signatures };
}
function computeSignature(timestamp: string, payload: string, secret: string): string {
const signedPayload = `${timestamp}.${payload}`;
return createHmac('sha256', secret).update(signedPayload).digest('hex');
}
/**
* @param signatureHeader The "Mono-Signature" header value
* @param rawBody The raw request body (must be the exact bytes received)
* @param secret The webhook signing secret (e.g., "whsec_xxx")
* @param toleranceSeconds Max age of the webhook in seconds (default: 300)
*/
export function verifyWebhookSignature(
signatureHeader: string,
rawBody: string,
secret: string,
toleranceSeconds: number = 300
): WebhookVerificationResult {
const { timestamp, signatures } = parseSignatureHeader(signatureHeader);
if (!timestamp) return { valid: false, error: 'Missing timestamp in signature header' };
if (signatures.length === 0) return { valid: false, error: 'Missing signature in header' };
const timestampNum = parseInt(timestamp, 10);
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestampNum) > toleranceSeconds) {
return { valid: false, error: 'Timestamp outside tolerance window' };
}
const expectedSignature = computeSignature(timestamp, rawBody, secret);
const expectedBuffer = Buffer.from(expectedSignature, 'hex');
for (const signature of signatures) {
const signatureBuffer = Buffer.from(signature, 'hex');
if (
expectedBuffer.length === signatureBuffer.length &&
timingSafeEqual(expectedBuffer, signatureBuffer)
) {
return { valid: true };
}
}
return { valid: false, error: 'Signature mismatch' };
}
```
## Using it in Express.js [#using-it-in-expressjs]
You must verify against the **exact bytes** of the incoming request. If Express parses and
re-serializes the JSON, the resulting string will differ (key order, whitespace) and the
signature will no longer match. Use `express.raw()` for the webhook route specifically.
```ts
import express from 'express';
import { verifyWebhookSignature } from './webhook_verification';
const app = express();
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString();
const signatureHeader = req.headers['mono-signature'] as string;
const secret = process.env.MONO_WEBHOOK_SECRET!;
const result = verifyWebhookSignature(signatureHeader, rawBody, secret);
if (!result.valid) {
return res.status(401).send(result.error);
}
const payload = JSON.parse(rawBody);
// Process payload...
res.status(200).send();
});
```
## Worked example [#worked-example]
**Inputs**
* Header: `t=1766002441,v1=62afda2079925823b390e1199060d793aa50d64ec9d7bf184f5b7e96c8bf411c`
* Secret: `whsec_1w5dFdWSaGV7qiTpf0VGqRk62rG2FSknb`
* Raw body:
```json
{
"timestamp": "2025-12-17T20:14:01.947520Z",
"event": {
"data": {
"id": "bbot_031uOJ6qb0sVclNseQfItQ",
"state": "failed",
"description": "Comfama",
"query": { "value": "@SBV567", "format": "plain_key" },
"target": {
"id": "bbtgt_031uOJ6uISEH2QbuvZlW3M",
"key_value": "@SBV567",
"key_type": "alphanumeric",
"transaction_amount": null,
"spbvi": "CRB",
"resolution_type": "plain_key",
"payment_id": null,
"creditor_account": {
"type": "savings_account",
"number": "892893",
"currency_code": "COP"
},
"creditor": {
"type": "natural",
"full_name": "Juan Carlos Perez Gomez",
"document_type": "CC",
"document_number": "123143455"
},
"participant_nit": "1431701149"
},
"amount": { "currency": "COP", "amount": 100 },
"external_id": "21752385-357e-450f-94a3-d9a0984da291::5f283bc8-9ee7-404b-8952-5082e5c181a7",
"inserted_at": "2025-12-17T20:13:57.545628Z",
"updated_at": "2025-12-17T20:13:57.589888Z",
"payment_id": null,
"state_reason": "target_creditor_mismatch",
"resolution_request_id": "bbtgr_031uOJ6qOnaxydvH1CZB3q",
"timemarks": {},
"expected_creditor": { "document_type": "CC", "document_number": "21482961" }
},
"type": "outgoing_transfer.created"
}
}
```
**Steps**
1. Extract `timestamp = "1766002441"` and `signature = "62afda20...c8bf411c"` from the header.
2. Build `signed_payload = "1766002441.{raw_body}"`.
3. Compute `HMAC-SHA256(secret, signed_payload)`.
4. Compare with the header signature using a timing-safe comparison.
The computed signature matches the header signature byte-for-byte, so the
request is valid.
## Best practices [#best-practices]
1. **Use the raw body.** Never re-serialize JSON before verifying — key order and
whitespace are not guaranteed to be preserved.
2. **Enforce a timestamp tolerance.** The default of 5 minutes is enough to
absorb clock skew while rejecting replayed webhooks. Tighten or loosen
based on your threat model.
3. **Compare with `timingSafeEqual`.** Standard equality checks leak information
through timing side-channels.
4. **Guard your secret.** The signing secret (prefixed `whsec_`) is the shared
secret between Mono and your service. Keep it out of logs and source
control, and rotate it if it's ever exposed.
# Bre-B Participant (/docs/guides/breb-participant)
Bre-B Participant is Mono's programmable on-ramp to Bre-B, Colombia's interbank instant-payment scheme. Use it to receive Bre-B payments through QR codes or registered payment keys, and to dispatch outgoing transfers against any payment key resolved through the central directory.
Learn how the Bre-B system works and how it integrates with Mono at
[breb.app](https://breb.app/).
How the Participant API connects you to the Bre-B network.
OAuth 2.0 client credentials for Bre-B Participant.
Payment keys, targets, tenant accounts, collections.
QR collection and outgoing-transfer end-to-end use cases.
Realistic walkthrough of a Bre-B participant integration.
Test your Bre-B integration with simulated payments and transfers.
State machine, transitions, and terminal states for outgoing transfers.
Rejection reasons, state reasons, and handling recommendations.
Verify the authenticity of incoming webhooks with HMAC-SHA256.
Payload envelope and outgoing-transfer event types.
Production recommendations: name confirmation, idempotency, signatures.
Definitions of Bre-B terms.
# Integration example (/docs/guides/core/integration-example)
This page walks through building a **consumer wallet** on Core end-to-end: a product where end users hold a balance, top it up, send money to other users, and spend it through a virtual card. It is the canonical example for understanding how Core's services compose into a real product.
A wallet is the most common use case for Core. If you are building something else — a corporate-card program, a marketplace seller balance, a treasury system — the patterns here transfer directly; only the entity names change.
This page is an integration-example scaffold. The full version will include a complete
narrative, code snippets per step, and a reference architecture diagram.
## What you will build [#what-you-will-build]
A wallet that supports:
* **Onboarding** — a user signs up and gets a ledger account.
* **Top-up** — the user adds funds via PSE (Banking handles this; Core records the credit).
* **Card issuing** — the user requests a virtual card spending against their ledger account.
* **Spending** — the card is authorized in real time against the user's balance and limits.
* **W2W transfer** — the user sends money to another wallet user.
* **Payout** — the user withdraws to a Colombian bank account (Banking handles the rail; Core debits the ledger).
## Architecture [#architecture]
The wallet sits on three Core services and one Banking service:
| Layer | Service | Role |
| :-------- | :------------------------------- | :------------------------------------- |
| Money in | Banking — PSE collections | Payer-initiated top-up |
| Balance | Core — Ledger | Per-user account, every cent recorded |
| Spending | Core — Cards + Spending Controls | Card issuing + real-time authorization |
| Money out | Core — Payouts → Banking | Withdrawal to external bank |
## Walkthrough [#walkthrough]
The full walkthrough — onboarding code, top-up handling, card issuing, real-time authorization, W2W transfer, and payout — will be added here.
## Next steps [#next-steps]
* [Ledger](/docs/guides/core/concepts/ledger) — the data model that anchors the wallet.
* [Cards](/docs/guides/core/concepts/cards) — issuing and lifecycle.
* [Best practices](/docs/guides/core/best-practices) — production considerations.
# Glossary (/docs/guides/core/glossary)
A short alphabetical reference for the terms you will encounter while integrating Core. Each entry is a one or two sentence plain-language definition. For the full data model behind any of these, follow the link to the corresponding concept page.
This glossary is a starting point and will grow. If a term you need is missing, please open an
issue on the docs repo so we can add it.
## Authorization [#authorization]
The real-time decision Core makes when a card is presented for payment. Core checks the cardholder's balance and the active spending controls, and either approves or declines the transaction.
## Brand [#brand]
The reusable look Mono's hosted checkout wears for you — name, button text, accent color, logo, and voucher message. A payin template points at one through `form_customization.brand_id`. See [Templates](/docs/guides/core/concepts/payins/templates#brands).
## Card [#card]
A virtual or physical payment card issued by Core that spends against a ledger account. See [Cards](/docs/guides/core/concepts/cards).
## Fee [#fee]
A charge attached to a money movement — a payout, a payin or a transfer between subaccounts. Fees with `owner: tenant` are what you charge your users or customers; fees with `owner: mono` are what Mono charges you. See [Fees](/docs/guides/core/concepts/fees).
## Fee policy [#fee-policy]
The pricing rule behind a fee: how much to charge for one kind of fee and whom it applies to (its target) — every account of your users or customers, a program, an account holder or a single account. The most specific policy wins. See [Fee policies](/docs/guides/core/concepts/fees/policies).
## Funding account [#funding-account]
The real bank account backing your program — where the money collected by payins is ultimately delivered, and where payout money comes from. See [Settlement](/docs/guides/core/concepts/payins/settlement).
## Hold [#hold]
A temporary reservation of funds on a ledger account, used to guarantee that an in-flight authorization will be settleable. Holds expire if not settled. See [Ledger](/docs/guides/core/concepts/ledger).
## Idempotency key [#idempotency-key]
A unique label your system attaches to a write request so that Core treats a retry as the same operation. See [Idempotency Keys](/docs/guides/api-standards/idempotency-keys).
## Ledger [#ledger]
Core's double-entry bookkeeping system. Every movement of money produces a balanced pair of entries (debit and credit). See [Ledger](/docs/guides/core/concepts/ledger).
## Ledger account [#ledger-account]
A logical account inside the ledger. Each end user, sub-business, or financial bucket gets its own ledger account.
## MCC (Merchant Category Code) [#mcc-merchant-category-code]
A four-digit code identifying the type of business behind a card transaction (groceries, fuel, travel, etc.). Used by spending controls to allow or block categories.
## Payin [#payin]
A single received payment: money collected into a subaccount through a payment method like Bre-B. See [Payins](/docs/guides/core/concepts/payins/payins).
## Payin intent [#payin-intent]
A single expected payment, declared before the funds arrive, with its own payment token and expiration window. See [Intents](/docs/guides/core/concepts/payins/intents).
## Payin template [#payin-template]
A reusable configuration tied to a subaccount that defines how payments are collected: methods, limits, allowed payers, expiration. See [Templates](/docs/guides/core/concepts/payins/templates).
## Payment token [#payment-token]
What a payer pays to. Each method issues its own kind — for Bre-B, a key (*llave*) or QR code issued for a template or intent. See [Payment tokens](/docs/guides/core/concepts/payins/payment-tokens).
## Payout [#payout]
A movement of funds out of a ledger account to an external destination (Colombian bank account, another participant, etc.). See [Payouts](/docs/guides/core/concepts/payouts).
## Program [#program]
A group of accounts sharing one configuration: currencies, enabled features or services, payment methods and the funding account they settle with. Every account belongs to a program. See [Programs](/docs/guides/core/concepts/program).
## Settlement [#settlement]
The final state of an authorization where the held funds are actually deducted from the ledger account. Authorizations that do not settle within the hold window are reversed.
## Settlement batch [#settlement-batch]
The single money movement that delivers the funds of many payins to a funding account. See [Settlement](/docs/guides/core/concepts/payins/settlement).
## Spending control [#spending-control]
A rule that constrains what a card can do — daily limits, category blocks, country restrictions. See [Spending Controls](/docs/guides/core/concepts/spending-controls).
## Tenant [#tenant]
Your company as a Mono customer. Every Core resource — programs, accounts, policies, fees — belongs to one tenant, and no tenant can see another's. See [Architecture](/docs/guides/core/architecture#multi-tenant-model).
# Authentication (/docs/guides/core/authentication)
Every request to the Core API must prove that it comes from your company. You can prove it with a long-lived API key, or with a short-lived access token that your server requests on its own. Mono accepts both on every Core endpoint, so you can choose the one that fits your security practices.
A credential belongs to one service. Core credentials work only on the Core API, and a Banking
credential sent to a Core endpoint is rejected.
## Before you start [#before-you-start]
You will need:
* A Mono organization with Core enabled, and access to its Mono Dashboard.
* An HTTPS client. Mono refuses requests that do not use TLS.
* A safe place for secrets, such as a secrets manager. Never commit credentials to source control.
The conventions shared by every Mono product, such as the header format and environments, are in [Authentication standards](/docs/guides/api-standards/authentication).
## Choose a credential [#choose-a-credential]
| | API key | OAuth 2.0 access token |
| :---------------- | :---------------------------- | :--------------------------------------------------- |
| How long it lasts | Until you revoke it | 30 minutes, then you request a new one |
| What it can do | What its roles allow | What its scopes allow |
| How you get it | Created in the Mono Dashboard | Requested by your server with a client ID and secret |
| Where it goes | `Authorization: Bearer ` | `Authorization: Bearer ` |
A **role** is a named set of permissions, such as "Admin" or "Viewer", attached to an API key. A **scope** is a single permission, such as `payouts:readonly`, attached to an access token. Mono maps each role permission to the scopes that grant the same action, so an endpoint accepts either credential for the same operation.
## Option 1: API keys [#option-1-api-keys]
An API key is a secret string that stays valid until you revoke it. To create or revoke keys, sign in to the [Mono Dashboard](https://mi.cuentamono.com/log_in) and open the [API key management section](https://mi.cuentamono.com/settings/api).
When you create a key, choose its roles. The key can do exactly what those roles allow.
Send the key in the `Authorization` header of every request:
```bash
curl https://api.cuentamono.com/v1/ledger/accounts \
-H "Authorization: Bearer $MONO_API_KEY"
```
Keys do not expire, so rotating them is your responsibility: create a new key, deploy it, then revoke the old one.
## Option 2: OAuth 2.0 access tokens [#option-2-oauth-20-access-tokens]
OAuth 2.0 is an industry standard for issuing temporary credentials. Mono uses its **client credentials** flow, designed for server-to-server integrations. Your server exchanges a permanent client ID and client secret for an access token that lasts 30 minutes. Think of it as trading an employee badge for a day pass: the pass expires, the badge stays in the safe.
### Create a credential [#create-a-credential]
In the Mono Dashboard, open your organization's **Credentials** section and create a credential:
1. Choose the scopes the credential may request. You can change them later.
2. Copy the client ID and client secret. The dashboard shows the secret only once.
3. Store both in your secrets manager.
You do not choose between Core and Banking: a Core organization always gets Core credentials. From the same section you can also disable, enable, or delete a credential.
### Request an access token [#request-an-access-token]
Call the Core token endpoint with `grant_type=client_credentials`, your client ID and secret, and the scopes you need, separated by spaces:
```bash
curl https://api.cuentamono.com/v1/core/oauth/token \
-d grant_type=client_credentials \
-d client_id="$MONO_CLIENT_ID" \
-d client_secret="$MONO_CLIENT_SECRET" \
-d scope="ledger:readonly payouts"
```
The endpoint accepts `application/x-www-form-urlencoded`, as above, or the same fields as JSON. In sandbox, use `https://api.sandbox.cuentamono.com`.
```json
{
"access_token": "",
"token_type": "Bearer",
"expires_in": 1800,
"refresh_token": "",
"scope": "ledger:readonly payouts"
}
```
Always send `scope`. If you leave it out, Mono still issues a token, but with no scopes, and
every endpoint then answers `403`. If you ask for a scope your credential does not have, the
whole request fails with `400 invalid_scope`; Mono never returns a token with only part of
what you asked for.
### Call the API [#call-the-api]
Send the access token in the `Authorization` header, exactly like an API key:
```bash
curl https://api.cuentamono.com/v1/ledger/accounts \
-H "Authorization: Bearer $MONO_ACCESS_TOKEN"
```
### Renew the token [#renew-the-token]
`expires_in` is the token's remaining lifetime in seconds: 1800 when it is issued. Request a new token before it runs out. You have two ways:
* **Request a new token** with your client credentials, exactly as above. This is the simplest option for a server that always holds its secret.
* **Use the refresh token.** The response also carries a `refresh_token`, valid for one day. Exchange it for a new access token:
```bash
curl https://api.cuentamono.com/v1/core/oauth/token \
-d grant_type=refresh_token \
-d client_id="$MONO_CLIENT_ID" \
-d client_secret="$MONO_CLIENT_SECRET" \
-d refresh_token="$MONO_REFRESH_TOKEN"
```
Each refresh returns a new refresh token. Use the new one next time and discard the old one.
### Check or revoke a token [#check-or-revoke-a-token]
Two more endpoints take your client ID and secret plus the token itself:
* **Introspect** (`grant_type=introspect`) tells you whether a token is still active, when it expires, and which scopes it carries. An expired or unknown token returns `{"active": false}`.
* **Revoke** (`grant_type=revoke`) invalidates an access token or a refresh token at once. It answers `200` even if the token had already expired.
Revoke tokens you no longer need, for example when you retire a server. The endpoint contracts are in the [Core authentication reference](/docs/api-reference/core/authentication).
## Scopes [#scopes]
Request the narrowest scopes your integration needs. Two rules apply across the catalog:
* A bare scope, such as `cards`, grants every action on that resource, including the specific actions listed below it.
* A `:readonly` scope grants reads only. It does not include the specific actions: `cards:readonly` does not grant `cards:details`.
| Scope | Grants |
| :--------------------------- | :-------------------------------------------------------------------------------------------------------- |
| `ledger` | All permissions for ledger resources, and reading their programs |
| `ledger:readonly` | Read ledger resources and their programs |
| `payins` | All permissions for payins, their templates, and their intents, and reading brands and settlement batches |
| `payins:readonly` | Read payins, their templates, their intents, brands, and settlement batches |
| `payouts` | All permissions for payouts, Bre-B target resolution, and the bank catalog |
| `payouts:readonly` | Read payouts and the bank catalog |
| `fees` | All permissions for fees and the policies that charge them |
| `fees:readonly` | Read fees and their policies |
| `fx_rates` | Read the exchange rates of the supported currencies |
| `fx_rates:readonly` | Read the exchange rates of the supported currencies |
| `cards` | All permissions for cards |
| `cards:readonly` | Read cards |
| `cards:details` | Read a card's sensitive details: number, security code, expiry |
| `spending_controls` | All permissions for card spending controls |
| `spending_controls:readonly` | Read card spending controls |
Bre-B is Colombia's instant payment system, operated by the central bank. Target resolution turns a Bre-B key, such as a phone number, into the account it points to.
## Failures [#failures]
The token, introspect, and revoke endpoints answer errors in the OAuth format, with `error` and `error_description` fields. For example, `400 invalid_scope` means you asked for a scope your credential does not have.
Every other endpoint answers with the standard [error envelope](/docs/guides/api-standards/errors-and-retries):
| Status | Code | What happened |
| :----- | :--------------- | :------------------------------------------------------------------------------- |
| `401` | `expired_token` | The token expired, was revoked, or belongs to another service, such as Banking. |
| `403` | `not_authorized` | The credential is valid, but its scopes or roles do not allow this endpoint. |
| `403` | `not_authorized` | A ledger endpoint was called by an organization that is not a Core organization. |
A Banking token sent to a Core endpoint gets the same `401 expired_token` as an expired token.
If a fresh token is rejected that way, check that it came from `/v1/core/oauth/token`, not
from `/v1/oauth/token`.
## Next steps [#next-steps]
* [Authentication reference](/docs/api-reference/core/authentication) — the token, introspect, and revoke endpoints in detail.
* [Architecture](/docs/guides/core/architecture) — how a request flows after authentication.
* [Best practices](/docs/guides/core/best-practices) — key rotation, scope minimization, and credential storage.
# Best practices (/docs/guides/core/best-practices)
This page collects the operational practices that separate a Core integration that works in sandbox from one that survives production. Most of them are not optional — they are how you keep the ledger consistent, prevent duplicates when the network blinks, and respond quickly when something goes wrong.
If you are deploying Core to handle real money, treat this page as a checklist before launch and as a reference during incidents.
This page is a best-practices scaffold. The full version will include concrete examples,
anti-patterns to avoid, and runbooks for the most common production failure modes.
## Idempotency on every write [#idempotency-on-every-write]
Every write to Core — creating a ledger transaction, issuing a card, triggering a payout — must include an [idempotency key](/docs/guides/api-standards/idempotency-keys). Without it, a network timeout will silently double-write.
## Reconcile against the ledger, not your cache [#reconcile-against-the-ledger-not-your-cache]
Core's ledger is the source of truth. Your application database may go out of sync; the ledger does not. Build a reconciliation job that periodically compares your view of balances against the ledger and surfaces drift.
## Webhooks are not replacements for polling [#webhooks-are-not-replacements-for-polling]
Webhooks fire async and can arrive out of order or be retried. For final-state operations (settled transactions, completed payouts), poll the API at the moment your business logic depends on the result. Use webhooks to wake your system; use the API to confirm.
## Rotate keys on a schedule [#rotate-keys-on-a-schedule]
API keys do not expire automatically. Rotate them at least every 90 days and immediately after any suspected exposure. Keep a documented rotation procedure that does not require human memory.
## Log the request ID [#log-the-request-id]
Every Core response carries a request ID. Log it on every request and include it in your error reporting. When you escalate an issue to Mono Support, the request ID is the fastest path to diagnosis.
## Antipatterns to avoid [#antipatterns-to-avoid]
* Reusing one API key across staging and production.
* Hardcoding webhook URLs without a way to rotate them.
* Treating the synchronous response of a payout as confirmation of settlement.
* Holding ledger entries in memory between processes — always write through.
## Next steps [#next-steps]
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys)
* [Architecture](/docs/guides/core/architecture)
* [Production checklist](/docs/guides/core/production)
# Go to production (/docs/guides/core/production)
Going to production with Core means taking responsibility for real money. Before you flip the switch, every item on this checklist should be true: you have rotated keys, signed agreements, validated reconciliation, configured monitoring, and rehearsed an incident.
This page is meant to be opened the day before launch and walked through item by item. If anything is unchecked, do not launch.
This page is a production-readiness scaffold. The full version will include the legal,
operational, and engineering checklist, plus the certification process Mono runs before
activating production credentials.
## Engineering checklist [#engineering-checklist]
* [ ] Production API keys generated, stored in a secrets manager, and rotated.
* [ ] Idempotency keys sent on every write request.
* [ ] Webhook endpoint deployed, signature verification in place, retry-tolerant.
* [ ] Reconciliation job runs at least daily and alerts on drift.
* [ ] Logging includes Mono request ID on every API call.
* [ ] Monitoring and alerting on `5xx` responses, webhook failures, and reconciliation drift.
* [ ] Incident runbook documented and rehearsed.
## Operational checklist [#operational-checklist]
* [ ] Customer support has access to the Mono Dashboard with appropriate scopes.
* [ ] Finance has documented the reconciliation procedure and owns the ledger close.
* [ ] Compliance has reviewed the data model and the retention policy.
## Mono certification [#mono-certification]
Mono performs a certification review before activating production credentials. Details will be documented here.
## Next steps [#next-steps]
* [Best practices](/docs/guides/core/best-practices)
* [Sandbox](/docs/guides/core/sandbox)
* [Webhooks](/docs/guides/core/webhooks)
# Core (/docs/guides/core)
Core is the part of Mono that lets you build financial products on top of programmable primitives. Where Banking handles money in and out of Colombian bank accounts, Core handles **what happens with the money once it is yours to manage**: a double-entry ledger that tracks every cent, virtual and physical card issuing, spending controls, payin collection, and payout disbursement.
Use Core to build wallets, marketplace seller balances, expense management products, neobanking experiences, and any other case that requires you to hold value, account for it, and let end users spend or move it.
How Core's services fit together and how a request flows end-to-end.
How to authenticate against the Core API.
Ledger, programs, fees, cards, spending controls, payouts, and FX rates.
Collect money into subaccounts — templates, intents, payins, and settlement.
End-to-end use cases — issuing cards, ledger accounting, payouts.
A realistic walkthrough of building a wallet on Core.
Events fired during card transactions and ledger movements.
Test Core without moving real money.
Production recommendations: idempotency, reconciliation, key rotation.
Manage cards, ledger accounts, and payouts from the Mono Dashboard.
Checklist before moving from sandbox to production.
## Use Core to: [#use-core-to]
* Hold balances for end users in a programmable, auditable ledger.
* Collect money into subaccounts — wallet top-ups and customer payments over Bre-B.
* Issue virtual or physical cards and decide which transactions to authorize in real time.
* Set spending limits per card, per user, or per category.
* Disburse payouts to internal ledger accounts or external destinations.
* Reconcile every movement against an immutable record.
## Primary use cases [#primary-use-cases]
| Use case | What you build | Core products you use |
| :----------------------------------- | :---------------------------------------------- | :--------------------------------- |
| Consumer wallet | Account, top-ups, card, transfers between users | Ledger + Cards + Payouts |
| Marketplace seller balance | Per-seller ledger, scheduled payouts | Ledger + Payouts |
| Corporate cards / expense management | Per-employee cards with category limits | Cards + Spending Controls + Ledger |
| Treasury / multi-account neobank | Multi-currency accounts with FX | Ledger + FX Rates + Payouts |
# Architecture overview (/docs/guides/core/architecture)
Core is composed of five services that work together to hold balances, issue cards, set limits, and move money in and out: a **ledger** that keeps the books, a **card issuing** service that creates and authorizes cards, a **spending controls** service that decides what cards can do, a **payins** service that brings money in, and a **payouts** service that pushes money out. Every API call passes through the same authentication, idempotency, and routing pipeline before reaching the service that owns the request.
This page describes that pipeline. It is the right starting point if you want to understand what happens between a client request and the eventual webhook your system receives.
## Services [#services]
| Service | Responsibility |
| :---------------- | :--------------------------------------------------------------------------------------------------- |
| Ledger | Double-entry bookkeeping — accounts, transactions, holds, settlements, reconciliation. |
| Cards | Virtual and physical card issuing, activation, freeze, replacement, digital-wallet provisioning. |
| Spending controls | Real-time authorization decisions per card based on limits, categories, and rules. |
| Payins | Incoming payments — collection rules, crediting subaccounts, and settlement to your funding account. |
| Payouts | Disbursement to internal ledger accounts or external destinations through Banking rails. |
## Request lifecycle [#request-lifecycle]
Every Core request follows the same five steps:
1. **Authenticate** — the API key in the `Authorization` header is validated.
2. **Check idempotency** — if `X-Idempotency-Key` is set, a cached response is returned for known keys.
3. **Route to the owning service** — based on the URL path.
4. **Execute and respond** — the service writes to the ledger, returns the result, and synchronously commits.
5. **Fire webhooks** — async events are dispatched to your registered endpoint.
## Multi-tenant model [#multi-tenant-model]
Each Mono customer is a tenant. All Core resources — ledger accounts, cards, payins, payouts — are tenant-scoped. There is no cross-tenant visibility.
## Next steps [#next-steps]
* [Authentication](/docs/guides/core/authentication) — credentials and headers.
* [Ledger](/docs/guides/core/concepts/ledger) — the foundation Core is built on.
* [Integration example](/docs/guides/core/integration-example) — realistic walkthrough.
# Dashboard (/docs/guides/core/dashboard)
The Mono Dashboard is the web interface where non-developer roles — operations, finance, customer support, compliance — interact with Core. Everything that the API exposes can be inspected from the Dashboard, and many actions (creating spending controls, freezing a card, triggering a manual payout) can be performed without touching code.
This page describes what each section of the Dashboard does and which role typically uses it. It is the right starting point for someone joining the team who needs to operate Core but does not write the integration.
This page is a dashboard scaffold. The full version will include screenshots of every section,
role-based access notes, and end-to-end examples (e.g., "how to issue an emergency card
replacement").
## Sections [#sections]
| Section | What it shows | Who uses it |
| :---------------- | :---------------------------------------------------------------- | :-------------------- |
| Ledger | Per-account balances, transaction history, holds, settlements. | Finance, ops |
| Cards | Active and inactive cards, freeze/unfreeze, replacement requests. | Customer support, ops |
| Spending controls | Limits and rules attached to cards. | Ops, compliance |
| Payouts | Scheduled and ad-hoc payouts, status, failures. | Finance, ops |
| Webhooks | Endpoint configuration, recent deliveries, retry attempts. | Engineering |
| API keys | Key management, roles, rotation. | Engineering, security |
| Credentials | OAuth 2.0 credentials: create, set scopes, disable, delete. | Engineering, security |
## Common operations [#common-operations]
Specific walkthroughs (issuing a card, freezing a card, manually triggering a payout, investigating a failed authorization) will be documented here.
## Webhook configuration [#webhook-configuration]
### Creating a webhook [#creating-a-webhook]
Webhook creation is done through the user interface:
1. Go to settings.
2. Click on Personal.
3. Click on API.
4. Click new webhook.
5. Insert the HTTPS/HTTP URL that you had configured to listen the events.
6. Click on save.
If everything is working as expected, there will be an alert indicating that we successfully created your webhook, and you should be able to see the URL listed on the page.
Webhook URLs are unique — you can only create one webhook per URL. To reuse a URL, delete the existing webhook first and create a new one. A new secret will be generated.
Always verify that your endpoint is receiving events correctly.
### Viewing webhook details [#viewing-webhook-details]
Click the arrow at the end of the row where your URL is displayed to open the detail view. The detail view contains the webhook secret, which is hidden by default. Click the **show/hide eyeball** to reveal it.
Keep your webhook secret secure. Do not share it.
## Next steps [#next-steps]
* [Webhook conventions](/docs/guides/api-standards/webhooks) — signature verification, retry policy, and money fields.
* [Architecture](/docs/guides/core/architecture)
* [Best practices](/docs/guides/core/best-practices)
# Authentication (/docs/guides/api-standards/authentication)
Authentication is how Mono confirms that a request is coming from your system and not from someone else. Every Mono product requires authentication on every request — there is no anonymous access. This page covers the conventions that apply across products. The exact credential type (API key vs. OAuth token) depends on which product you are integrating.
Mono uses two authentication schemes: **static API keys** and short-lived **OAuth 2.0
tokens**. Banking and Core accept both, on every endpoint. Bre-B Participant and Wallet use
OAuth 2.0 tokens only. Both schemes share the same transport and header described below.
## Before you start [#before-you-start]
You will need:
* An active Mono account with credentials for the environment you want to call (sandbox or production).
* HTTPS support in your client. Mono refuses non-TLS requests.
* A safe place to store credentials — a secrets manager, environment variables in a managed runtime, or an equivalent. Never commit them to source control.
## How credentials travel [#how-credentials-travel]
All authenticated requests carry the credential in the `Authorization` header using the `Bearer` scheme.
```
Authorization: Bearer
```
This is true regardless of whether the credential is a static API key or a short-lived OAuth access token. The header name and scheme are fixed; only the value changes between products.
```bash
curl https://api.cuentamono.com/v1/bank_accounts \
-H "Authorization: Bearer $MONO_API_KEY"
```
## Environments [#environments]
Mono separates production credentials from sandbox credentials. They are not interchangeable:
* **Sandbox** keys only work against sandbox base URLs and never move real money.
* **Production** keys move real money. Treat them with the same care as a banking password.
Each product page describes the exact base URL for its sandbox and production environments.
## Failures [#failures]
When authentication fails, Mono returns one of two HTTP status codes:
| Status | Meaning | Typical cause |
| :----------------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- |
| `401 Unauthorized` | The credential is missing, malformed, expired, or revoked. | Header not sent, OAuth token expired, API key revoked. |
| `403 Forbidden` | The credential is valid but does not have permission for this resource. | Role or scope does not grant the action, or the resource belongs to a different tenant. |
Both responses follow the standard [error envelope](/docs/guides/api-standards/errors-and-retries). Do not retry a `401` or `403` without first refreshing or replacing the credential — retries with the same bad credential will keep failing and may trigger rate-limit penalties.
## Rotating credentials [#rotating-credentials]
Rotate credentials on a schedule and immediately after any suspected exposure (a leaked log, an ex-employee, a compromised laptop). The rotation procedure depends on the product:
* **API keys (Banking, Core)**: generate a new key in the Mono Dashboard, deploy it, then revoke the old one. Keys do not expire automatically; rotation is your responsibility.
* **OAuth tokens (Banking, Core, Bre-B Participant, Wallet)**: tokens expire on their own. Rotation in the operational sense means rotating the underlying client secret or password used to obtain new tokens.
## Per-product specifics [#per-product-specifics]
The cross-product rules above are universal. The exact credential type, how to obtain one, and any product-specific scopes are documented on each product's authentication page:
API keys and OAuth 2.0 client credentials for Banking.
API keys and OAuth 2.0 client credentials for Core.
OAuth 2.0 client credentials for Bre-B Participant.
## Next steps [#next-steps]
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys) — how to safely retry write operations.
* [Errors and retries](/docs/guides/api-standards/errors-and-retries) — the standard error envelope returned on auth failures, plus retry policy.
* [Data Formats](/docs/guides/api-standards/data-formats) — date, money, and phone number conventions.
# Errors and retries (/docs/guides/api-standards/errors-and-retries)
Every Mono response carries an HTTP status code. Successful calls return `2xx`; failures return `4xx` (your request was wrong) or `5xx` (something went wrong on Mono's side). This page documents the status codes Mono uses, the error envelope you can expect on every failure, and the retry policy that turns transient failures into eventual success without duplicating side effects.
Rate limits are not yet enforced uniformly across every Mono endpoint, but `429` will start
appearing as we tighten them. The retry rules below describe the contract you should code
against today so that your integration already behaves correctly when limits kick in.
## HTTP status codes [#http-status-codes]
| Status | Meaning | Retryable? |
| :------------------------ | :----------------------------------------------------------------------------------------------------------------- | :----------------------- |
| 200 (OK) | Request completed successfully. | n/a |
| 201 (Created) | A new resource was created. | n/a |
| 204 (NoContent) | Request succeeded; no body to return. | n/a |
| 400 (BadRequest) | Malformed JSON, missing field, or non-compliant header. The request itself is wrong. | No |
| 401 (Unauthorized) | Missing or invalid `Authorization` header. | No (fix credential) |
| 403 (Forbidden) | Authenticated, but lacking scope for this resource. | No (fix scope) |
| 404 (NotFound) | Resource does not exist. | No |
| 408 (RequestTimeout) | Mono did not receive the full request in time. | Yes, with backoff |
| 409 (Conflict) | Idempotency-key collision or state conflict — see [Idempotency Keys](/docs/guides/api-standards/idempotency-keys). | No (application logic) |
| 422 (UnprocessableEntity) | Request structure is valid, but the content is rejected (business validation). | No |
| 429 (TooManyRequests) | Rate limit exceeded. Honor the `Retry-After` header. | Yes, after `Retry-After` |
| 500 (InternalServerError) | Unexpected server-side failure. | Yes, with backoff |
| 502, 503, 504 | Upstream/gateway transient failures. | Yes, with backoff |
## Error envelope [#error-envelope]
Every failure response carries a standard JSON envelope so clients can parse errors uniformly:
```json
{
"code": "400 Bad Request",
"errors": [
{
"error_code": "item_not_found",
"message": "Item doesn't exist",
"path": "#/item/id",
"url": "https://api.mono.co/docs#errors"
}
],
"id": "log_7MkWaFqvfosB8fzHhb1Eql",
"message": "Malformed request"
}
```
Fields:
* **`code`** — the HTTP status and reason phrase (e.g., `"404 Not Found"`).
* **`errors[]`** — one entry per validation failure. Useful when a single request has multiple invalid fields.
* `error_code` — stable, machine-readable code for this specific failure.
* `message` — short human description.
* `path` — JSON pointer into the request payload identifying the offending field.
* `url` — link to extended documentation for this error code, when available.
* **`id`** — Mono-generated request identifier. **Include this when contacting support** — it lets us locate the exact request in our logs.
* **`message`** — top-level human description suitable for surfacing to operators (not end users).
`429` and other rate-limit responses use the same envelope with `error_code: "rate_limited"`.
## When to retry [#when-to-retry]
Retry only on transient failures, never on application errors:
| Response | Retry? | Why |
| :----------------- | :----------------------------- | :-------------------------------------------------------------------------------- |
| `429` | Yes, after `Retry-After`. | Transient. Request was **not** processed; nothing changed on Mono's side. |
| `408`, `500`–`504` | Yes, with exponential backoff. | Transient. Request may or may not have been processed — idempotency key protects. |
| `400`, `422` | No. | The request itself is malformed. Retrying will not change the answer. |
| `401`, `403` | No. | Credentials or scope problem. Fix the auth, then send a fresh request. |
| `404`, `409` | No. | Resource missing or state conflict. Application logic must react. |
Four rules apply to every retry:
1. **Honor `Retry-After`.** If the header is present (always on `429`, sometimes on `503`), sleep for at least that many seconds before retrying. Do not retry sooner.
2. **Use exponential backoff with jitter when `Retry-After` is absent.** Start at 1 second, double on each subsequent failure, add 0–500 ms of random jitter, and cap the delay at 30 seconds. Jitter prevents the thundering-herd problem where many clients retry in lockstep.
3. **Cap the retry budget.** Up to 5 retries per logical operation is a reasonable default. After that, surface the failure — retrying forever just shifts the problem.
4. **Reuse the idempotency key.** Every retry must carry the same [`X-Idempotency-Key`](/docs/guides/api-standards/idempotency-keys) as the original attempt. Otherwise, a successful retry after an ambiguous `5xx` could double-charge a customer.
A minimal retry loop in Python:
```python
import random
import time
import httpx
RETRYABLE_STATUSES = {408, 429, 500, 502, 503, 504}
def request_with_backoff(client, method, url, **kwargs, max_retries=5):
delay = 1.0
for attempt in range(max_retries + 1):
response = client.request(method, url, **kwargs)
if response.status_code not in RETRYABLE_STATUSES:
return response
retry_after = response.headers.get("Retry-After")
wait = float(retry_after) if retry_after else delay + random.uniform(0, 0.5)
time.sleep(min(wait, 30.0))
delay = min(delay * 2, 30.0)
return response # last response, still failing
```
## Designing for resilience up front [#designing-for-resilience-up-front]
Even before rate limits become strict, three habits keep bursty workloads out of trouble:
* **Spread bursty work.** Batch jobs that fan out thousands of calls — payroll runs, end-of-day reconciliation, mass card issuance — should be paced from the client side rather than fired in parallel. A few requests per second sustained beats a thousand at once and a recovery window afterward.
* **Cache reads.** Reference data (catalogs, fee schedules, capability lookups) rarely changes inside a single request cycle. Cache the response and only hit Mono when the cache misses.
* **Separate sync from async paths.** A user-facing checkout call should not share its budget with a nightly export. Use separate API keys for separate workloads when possible so a runaway batch job cannot exhaust the limit a real customer needs.
## Common mistakes [#common-mistakes]
| Mistake | Symptom | Fix |
| :------------------------------------------------- | :------------------------------------------------------------ | :--------------------------------------------------------------------------- |
| Retrying immediately on `429`. | Repeated `429`s and longer total recovery time. | Sleep for at least `Retry-After` seconds before the next attempt. |
| Ignoring the `Retry-After` header. | Retries land before the limit has reset. | Treat `Retry-After` as authoritative when present. |
| Retrying without an idempotency key. | Duplicate transfers once the retry eventually succeeds. | Generate the key once, send it on every attempt. |
| Retrying `400`/`422` as if transient. | Wasted attempts; the same error returns every time. | Only retry on `408`, `429`, and `5xx`. |
| No retry cap. | Stuck workers, blocked queues, alert fatigue. | Cap at a small number of retries (5 is a reasonable default) and surface up. |
| Sharing a key across services and traffic classes. | One noisy batch job rate-limits user-facing checkout traffic. | Issue separate API keys for separate workloads. |
| Dropping the `id` field when reporting an issue. | Support cannot find the request in logs. | Always include `id` from the error envelope when you escalate. |
## Next steps [#next-steps]
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys) — required on every retried write.
* [Authentication](/docs/guides/api-standards/authentication) — credential handling for the `401` and `403` cases.
* [Webhooks](/docs/guides/api-standards/webhooks) — Mono's own retry policy for the events it sends to your system.
# Idempotency Keys (/docs/guides/api-standards/idempotency-keys)
An **idempotency key** is a unique label your system attaches to a write request so that Mono can recognize a retry as the same operation rather than a new one. It works like an order number on a paper form: if you fax the same form twice by mistake, the receiving clerk sees the order number and only processes it once. Without an idempotency key, a network glitch on a transfer request could quietly create two transfers.
Use an idempotency key on every write request that moves money or creates a card, transfer,
collection link, or payout. The cost of forgetting is a duplicated transaction; the cost of
remembering is one extra header.
## Before you start [#before-you-start]
This page assumes you have read [Authentication](/docs/guides/api-standards/authentication) and that you make `POST`, `PUT`, or `PATCH` requests against the Mono API. Idempotency does not apply to `GET` requests — those are already safe to retry by definition.
## When to send it [#when-to-send-it]
Send an idempotency key whenever a retry of the same request must not produce a second side effect. In practice, this is **every write request to Mono**. Examples:
* Creating a bank transfer.
* Creating a collection link.
* Creating a card.
* Triggering a payout.
* Submitting an outgoing Bre-B transfer.
You can also send one on requests that are theoretically safe to retry (such as updates that converge to the same state). It is never wrong to include the header.
## How to send it [#how-to-send-it]
Add the `X-Idempotency-Key` header to your request. The value is a string you generate, unique per logical operation, up to 255 characters.
```bash
curl https://api.cuentamono.com/v1/bank_transfers \
-H "Authorization: Bearer $MONO_API_KEY" \
-H "X-Idempotency-Key: payroll-2026-05-06-emp-1042" \
-H "Content-Type: application/json" \
-d '{
"amount": 1500000,
"currency": "COP",
"routing": "turbo",
"destination": { ... }
}'
```
## Choosing a good key [#choosing-a-good-key]
A good idempotency key is **deterministic for a given logical operation** and **opaque to humans**. Recommended formats:
* A UUID v4 generated once per logical operation and stored alongside the operation in your database. Persist the key before the first attempt so retries reuse it.
* A composite of identifiers your system already controls — for example `transfer:${order_id}` for a per-order transfer. Avoid timestamps unless you control them precisely; clocks drift across retries.
What to avoid:
* **Reusing the same key for different operations.** Mono will treat the second one as a duplicate and reply with the cached first response, which is almost certainly not what you want.
* **Generating a fresh key on each retry.** That defeats the entire point — retries become new operations.
* **Including secrets or PII in the key.** Keys appear in logs and dashboards.
## What Mono does with it [#what-mono-does-with-it]
When Mono receives a request with an idempotency key, it looks the key up in its short-term store:
1. **First time seen** — Mono executes the request, stores the response, and returns it.
2. **Already seen, same payload** — Mono returns the stored response without executing the request again. Your retry is safe.
3. **Already seen, different payload** — Mono rejects the request with `409 Conflict`. This protects you from a programming bug where two unrelated operations accidentally share a key.
Cached responses are retained for **24 hours** from the first request. After that the key is forgotten and a fresh request with the same key starts a new operation.
## When retries make sense [#when-retries-make-sense]
Retry on transient failures only — network timeouts, `502`, `503`, `504`. Do not retry on `400`, `401`, `403`, `404`, or `422`; those indicate the request is wrong, and retrying will not change the answer.
Use exponential backoff with jitter, and cap the retry budget. A common pattern: 3 retries, starting at 1s, doubling each time, with 0–500ms jitter.
## Common mistakes [#common-mistakes]
| Mistake | Symptom | Fix |
| :----------------------------------------------------- | :------------------------------------------------- | :-------------------------------------------------- |
| Generating a new key on each retry | Duplicate transfers, double charges. | Generate the key once, store it, reuse it on retry. |
| Reusing a key across logical operations | Second operation returns the first one's response. | Make the key unique per operation. |
| Sending the key only on retries, not the first attempt | The first attempt has no protection. | Always send the key on the first attempt. |
| Relying on idempotency for `GET` retries | No effect. | `GET` is already idempotent; the header is ignored. |
## Next steps [#next-steps]
* [Errors and retries](/docs/guides/api-standards/errors-and-retries) — how Mono signals retryable vs. terminal failures and the standard backoff policy.
* [Authentication](/docs/guides/api-standards/authentication) — credential handling and rotation.
* [Pagination and Sorting](/docs/guides/api-standards/pagination-and-sorting) — cursor conventions for read endpoints.
# Data Formats (/docs/guides/api-standards/data-formats)
## Date and Times [#date-and-times]
For our API, we use the ISO 8601. ISO 8601 is a standard for representing dates and times in a machine-readable format. It specifies a set of representations for dates, times, and intervals, including standard formats for representing dates and times with or without a time zone.
The format consists of a series of fields separated by punctuation, with the most significant fields appearing first. The basic format for representing a date and time is:
`YYYY-MM-DDTHH:MM:SSZ`
Where:
* `YYYY` is the year, represented as four digits
* `MM` is the month, represented as two digits (01-12)
* `DD` is the day of the month, represented as two digits (01-31)
* `T` is a separator indicating the start of the time portion of the date-time value
* `HH` is the hour, represented as two digits (00-23)
* `MM` is the minute, represented as two digits (00-59)
* `SS` is the second, represented as two digits (00-59)
* `Z` is the timezone
For example, the date and time `2022-12-29T12:34:56Z` would represent December 29, 2022 at 12:34:56 (12:34 PM).
The API uses UTC, or Coordinated Universal Time, for the date/time field. UTC is a standardized time reference that is the same everywhere in the world. It is the primary time standard by which the world regulates clocks and time. UTC is based on atomic clocks, which are highly accurate clocks that measure the vibrations of atoms to determine the passage of time. Because UTC is based on atomic clocks, it is extremely stable and accurate.
## Monetary Values [#monetary-values]
For our API, every monetary must be in a ISO 4217, that is an international standard for representing currencies. It specifies a three-letter code for each currency, as well as the currency's minor unit of currency. For example, the code for US dollars is "USD".
Also, the `amount` field for money is represented in cents rather than the base unit of the currency. This allows for more precise representation of the amount, as well as easier computation and storage of monetary values. For example, if you want to represent $10.50 in an API, you would store the amount as 1050 (rather than 10.50).
This would be represented like:
```json
{
"amount": 10000,
"currency": "COP"
}
```
This represents an amount of 10000 cents, or 100 Colombian pesos. The currency is specified using the ISO 4217 currency code for Colombian pesos, which is "COP".
## Telephone Numbers [#telephone-numbers]
Both domestic and international telephone numbers are represented with the E.164.
E.164 is a standard for the formatting of telephone numbers. It specifies a standardized format for telephone numbers that includes a country code, a national destination code, and a local subscriber number. The format is as follows:
```
+[country code][national destination code][local subscriber number]
```
For example, a telephone number in Colombia might be formatted as `+57 300 555 0000`, where the `+57` is the country code for Colombia, the "300" is the national destination code, and the "555 0000" is the local subscriber number.
# Pagination and Sorting (/docs/guides/api-standards/pagination-and-sorting)
## Pagination [#pagination]
Pagination is a technique used to divide numerous results into smaller chunks, or pages, for easier browsing. When using our Mono API, the client can specify two parameters: `page_size` and `page_number` in our endpoint that list objects. These parameters can be passed as query parameters in the API request.
The `page_size` parameter determines the number of results that should be returned in each page. For example, if the `page_size` is set to 10, the API will return 10 results per page.
The `page_number` parameter determines which page of results should be returned. For example, if the `page_number` is set to 2, the API will return the second page of results.
Every list of items returned by the API should include a pagination object with the following structure:
```json
{
"items": [],
"pagination": {
"page_number": 1,
"page_size": 10,
"total_items": 100,
"total_pages": 10
}
}
```
The `items` field contains the list of objects returned by the API for the current page. The `pagination` object contains metadata about the results, including the current `page_number` and `page_size`, as well as the `total_items` and `total_pages`.
The `total_items` field indicates the total number of objects in the result set, and the `total_pages` field indicates the total number of pages required to display all the results. This information can be used by the client to build UI controls for navigating through the pages of results.
## Sorting [#sorting]
Sorting is a technique used to rearrange the items in a list in a particular order, either ascending or descending. When using a sorting API, the client can specify a sort parameter in a query string.
The sort parameter should include two fields: `type` and `field`. The `type` field determines the sort order, and can have the values `asc` for ascending or `desc` for descending. The `field` field specifies the field by which the items should be sorted, the values for this field is documented by every endpoint that supports these sorting parameters.
For example, the following query string would sort the items in ascending order by the id field:
```
?sort[type]=asc&sort[field]=id
```
# Webhooks (/docs/guides/api-standards/webhooks)
A webhook is an automatic notification Mono sends to your system the moment something happens — a transfer is approved, a card is charged, a collection is credited. Instead of asking Mono repeatedly whether something changed, your server receives a single HTTPS request as soon as the event occurs.
These conventions apply to every Mono product: Banking, Core, and Bre-B Participant. Each product has its own event catalog; this page documents the shared mechanics that govern all of them.
## Before you start [#before-you-start]
You will need:
* A publicly accessible HTTPS endpoint (no `localhost`, no `127.0.0.1`).
* The ability to verify an HMAC-SHA256 signature on every incoming request.
* An idempotent event handler — Mono retries on failures, so the same event may arrive more than once.
## Envelope [#envelope]
Every webhook request body follows the same JSON envelope:
```json
{
"event": {
"data": { ... },
"type": "event_type_or_name"
},
"timestamp": "2022-12-29T15:42:08.325158Z"
}
```
* `event.type` — the stable name of the event (e.g. `bank_transfer_approved`).
* `event.data` — the event payload; its schema depends on the event type.
* `timestamp` — ISO 8601 UTC timestamp of when the event was generated.
## Signature [#signature]
Each request is signed with a **webhook secret** you obtain from the Dashboard. The signature and the timestamp travel together in a custom header called `Mono-Signature`:
```
Mono-Signature: t=1672328528,v1=662255ca79c4b21914894d32da10189a1482cd0fee56b2765a8b472662ce55ac
```
* `t` — Unix timestamp. Use it to guard against [replay attacks](https://en.wikipedia.org/wiki/Replay_attack) by rejecting requests whose timestamp falls outside your tolerance window.
* `v1` — the HMAC-SHA256 signature. Mono currently uses only version `v1`.
## Verifying signatures [#verifying-signatures]
### Step 1: Extract timestamp and signature [#step-1-extract-timestamp-and-signature]
Split `Mono-Signature` on `,` to get:
```
t=1672328528
v1=662255ca79c4b21914894d32da10189a1482cd0fee56b2765a8b472662ce55ac
```
### Step 2: Build the signed payload string [#step-2-build-the-signed-payload-string]
Concatenate the timestamp, a literal `.`, and the raw request body:
```
.
```
### Step 3: Compute the expected signature [#step-3-compute-the-expected-signature]
Use HMAC-SHA256 with your webhook secret as the key and the signed payload string as the message:
```ruby
require 'openssl'
timestamp = 1672774221
raw_payload = '{"respose_body": "example"}'
signed_payload = "#{timestamp}.#{raw_payload}"
secret = "whsec_example"
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), secret, signed_payload)
# => 652fdc1742906b4b23ce2a5f4ac417b52c264fea0207920a5e76330a87239924
```
### Step 4: Compare and validate [#step-4-compare-and-validate]
Compare the computed HMAC against the `v1` value using a **constant-time comparison** to prevent timing attacks. Then check that the difference between the current time and `t` is within your acceptable window (typically 5 minutes).
## Retry policy [#retry-policy]
Mono retries failed requests (any non-`2xx` response) up to ten times with exponential backoff. After the tenth attempt, the event is not retried again.
| Attempt | Delay from previous |
| ------- | ------------------- |
| 1 | immediately |
| 2 | 30 seconds |
| 3 | 1.5 minutes |
| 4 | 3.5 minutes |
| 5 | 7.5 minutes |
| 6 | 15.5 minutes |
| 7 | 31.5 minutes |
| 8 | 1.06 hours |
| 9 | 2.13 hours |
| 10 | 4.26 hours |
All ten attempts complete within approximately 8.4 hours.
## Money fields [#money-fields]
Monetary amounts are always represented in the smallest currency unit (cents for COP and USD):
* `240000` = 2,400.00
* `160` = 1.60
The exception is `fx_rates`, which carries the decimal exchange rate directly: `"4002.24"` = 4002.24.
## URL restrictions [#url-restrictions]
Your webhook endpoint must:
* Start with `http` or `https`.
* Not use `localhost` or `127.0.0.1` as the host.
* Be a valid, reachable HTTP/HTTPS URL.
## Next steps [#next-steps]
* [Banking webhooks](/docs/guides/banking/webhooks) — event catalog for bank transfers and collection links.
* [Core webhooks](/docs/guides/core/webhooks) — event catalog for card transactions and ledger account transactions.
* [Bre-B Participant webhooks](/docs/guides/breb-participant/webhooks) — event catalog for Bre-B outgoing transfers and collections.
# Banking API (/docs/api-reference/banking)
The Banking API enables banking operations including authentication, account management, bank transfers, collection links, card issuing and management, and spending controls.
## Endpoints [#endpoints]
Obtain, inspect, and revoke OAuth 2.0 access tokens
Manage bank accounts and balances
Create and manage bank transfers
List the banks you can send bank transfers to
Generate and manage collection links for payments
Create and track collection intents
Issue banking cards and manage their prepaid balance
Manage cards, cardholders, and digital wallet provisioning
Set up rules and controls for card spending
## Webhooks [#webhooks]
Notifications about bank transfer approval, rejection, routing, and account credit events
Notifications about bank transfer batch lifecycle events
Notifications about collection link payment intent events
Notifications about card activation and tokenization events
Notifications about card transaction authorization, adjustment, and reversal events
# Core API (/docs/api-reference/core)
The Core API provides essential financial infrastructure functionality including ledger management, payins, payouts, card operations, and spending controls.
## Endpoints [#endpoints]
Obtain, inspect, and revoke OAuth 2.0 access tokens
Manage account holders, ledger accounts, and financial transactions
List the programs your ledger accounts run under
Manage the templates that configure collections and read the resulting payins
Create and manage payouts with target resolution
List the banks you can send payouts to
List the fees charged on your accounts and manage the policies that price them
Issue cards against a ledger account
Manage cards, cardholders, and digital wallet provisioning
Set up rules and controls for card spending
## Webhooks [#webhooks]
Notifications about card activation and tokenization events
Notifications about card transaction authorization, adjustment, and reversal events
Notifications about ledger account transaction events
# Bre-B Participant API (/docs/api-reference/breb-participant)
The Bre-B Participant API enables you to create and manage outgoing transfers, resolve transfer targets, and receive real-time webhook notifications about transfer status changes.
Learn how the Bre-B system works and how it integrates with Mono at
[breb.app](https://breb.app/).
## Authentication [#authentication]
This API uses OAuth 2.0 with Client Credentials flow. Request tokens from `/api/v1/oauth/token`.
### Security Schemes [#security-schemes]
The API defines two security schemes that you'll see referenced in the endpoint documentation:
* **oauth**: Full access scheme for operations that create, update, or delete resources.
* **oauth\_readonly**: Read-only access scheme for operations that only retrieve data.
### Scopes [#scopes]
| Scope | Scheme | Description |
| ----------------------------- | ---------------- | ------------------------------------------------- |
| `tenant_accounts` | `oauth` | Allow all permissions for tenant accounts |
| `tenant_accounts:readonly` | `oauth_readonly` | Permissions to read tenant accounts |
| `outgoing_transfers` | `oauth` | Allow all permissions for outgoing transfers |
| `outgoing_transfers:readonly` | `oauth_readonly` | Permissions to read outgoing transfers |
| `target_resolutions` | `oauth` | Permission to read and resolve target resolutions |
| `target_resolutions:readonly` | `oauth_readonly` | Permission to read target resolutions |
| `collections` | `oauth` | Allow all permissions for collections |
| `collections:readonly` | `oauth_readonly` | Permissions to read collections |
## Endpoints [#endpoints]
OAuth 2.0 authentication with Client Credentials flow
Retrieve and list tenant accounts associated with your tenant
Create and manage outgoing transfers through the Bre-B system
Create and manage collections for receiving payments through Bre-B
Test payment flows in the sandbox environment
## Webhooks [#webhooks]
Notifications about transfer target resolution events
Notifications about outgoing transfer status changes
Notifications about outgoing transfer batch events
Notifications about collection lifecycle and payment attempts
# Lifecycle overview (/docs/guides/banking/lifecycle)
# Bank transfer lifecycle (/docs/guides/banking/lifecycle/bank-transfer-states)
# Bank Transfers (/docs/guides/banking/concepts/transfers)
A bank transfer in Mono is like sending a package from one place to another, but with money. It's a financial transaction in which funds are moved from one bank account to another.
Bank transfers are commonly used to pay invoices, transfer money between accounts, make payroll payments or send money to others. They can be made between accounts at the same bank or at different banks.
With Mono, you can create bank transfers through API, upload an Excel file through the Mono Dashboard, or by manually filling a form.
Currently, we only support bank transfers within the Colombian banking network.
## Routing [#routing]
Mono supports three types of bank transfers, each with different behaviors, pros and cons. To provide a high-level understanding, the following diagram illustrates how money moves depending on the bank transfer routing:

### Turbo-transferencias [#turbo-transferencias]
A Turbo-transferencia is a real-time bank transfer from your Mono account to any individual's account opened in the following banks:
* Bancolombia
* Nequi
* Davivienda
* Daviplata
* Banco AV Villas
* Banco de Bogota
* Bancolombia ahorro a la mano
* Movii
* Dale!
You can initiate a `turbo` bank transfer using the Create bank transfer endpoint by adding `turbo` in the `routing` field.
Once you initiate a turbo, you should expect a webhook response, notifying you if the bank transfer was successfully credited to the payee's account.
This routing of bank transfer operates on the new ACH Colombia rails, and its acceptance is expected to increase over time. There may be cases where a `turbo` bank transfer fails to send successfully because the payee's account does not yet support this type of transaction. (See below for fallback attempts)
### Transfiya [#transfiya]
[Transfiya](https://www.transfiya.com.co/home) is a bank transfer service developed in Colombia that allows users to send and receive money in real-time between different financial institutions using only the recipient's phone number. This service is part of the ACH Network.
#### How to make a Transfiya transfer in Mono [#how-to-make-a-transfiya-transfer-in-mono]
You can initiate a Transfiya transfer using the create bank transfer endpoint, and adding `transfiya` in the `routing` field. When making a transfiya transfer, you don't need to include the payee bank account schema since the transfer will be made using the payee's phone number.
Once you initiate a Transfiya transfer the payee will receive a text message notifying the transfer, and he will have 12 hours to accept the bank account he prefers.
#### Difference between Turbo and Transfiya Transfers [#difference-between-turbo-and-transfiya-transfers]
Both Turbo and Transfiya Transfer are real time transfers, however there is a difference between them.
Turbo transfers are made in real time to a bank account, and the payee doesn't have to accept the transfers; they just receive the money instantly. On the other hand, Transfiya transfers are made in real time using the payee's phone number. The payee will receive a message and must accept the transfer in the bank of their choice.
### ACH [#ach]
ACH Colombia and ACH CENIT are clearing houses that manage the interoperability of Colombian banks, enabling communication and settlement between them. When you create a bank transfer by selecting `ACH` in the `routing` field, the money will be deducted from your account and sent to the payee through ACH Colombia or ACH CENIT cycles, which are not instantaneous. Whether the bank transfer goes through ACH Colombia or ACH CENIT depends on the destination account's bank. Most transactions will go through ACH Colombia, except for some banks listed below that use ACH CENIT.
#### ACH Colombia transaction speed [#ach-colombia-transaction-speed]
ACH transactions in Colombia are not conducted in real-time. They can take a few hours or up to 1 business day to be credited to the destination account, and even longer to receive confirmation.
At Mono, we have developed an implicit confirmation system for ACH bank transfers. This system works as follows: if we do not receive a rejection response for a transfer within a certain period, the transfer is considered approved.
**ACH cycles without implicit confirmation:**
| Transaction time | Money accredited | Implicit response |
| --------------------------- | ---------------------------------------- | ---------------------------------------------- |
| From 00:00 and before 08:28 | 10:20 to 11:00 | Next business day between 15:10 to 15:45 |
| From 08:28 and before 10:58 | 12:45 to 13:00 | Next business day between 15:10 to 15:45 |
| From 10:58 and before 12:43 | 15:15 to 16:00 | Next business day between 15:10 to 15:45 |
| From 12:43 and before 15:38 | 17:10 to 18:00 | Next business day between 15:10 to 15:45 |
| From 15:38 to the next day | Next business day between 10:20 to 11:00 | Subsequent business day between 15:10 to 15:45 |
**ACH cycles with implicit confirmation:**
| Transaction time | Money accredited | Implicit response |
| ------------------------ | ---------------------------------------- | ----------------------------- |
| From 00:00 to 8:25 am | 10:20 to 11:00 | 8:00 pm on the same day |
| From 8:26 am to 10:25 am | 12:45 to 13:00 | 10:00 pm on the same day |
| From 10:26 am to 1:25 pm | 15:15 to 16:00 | Next business day at 11:00 am |
| From 1:26 pm to 3:40 pm | 17:10 to 18:00 | Next business day at 1:00 pm |
| From 3:41 am to 11:59 pm | Next business day between 10:20 to 11:00 | Next business day at 8:00 pm |
#### ACH CENIT transaction speed [#ach-cenit-transaction-speed]
**CENIT cycles without implicit confirmation:**
| Transaction time | Money accredited | Implicit response |
| --------------------------- | ---------------------------------------- | ---------------------------------------------- |
| From 00:00 and before 09:58 | 10:30 to 11:00 | Next business day between 15:10 to 15:45 |
| From 09:58 and before 12:28 | 13:00 to 13:30 | Next business day between 15:10 to 15:45 |
| From 12:28 and before 14:28 | 15:00 to 15:45 | Next business day between 15:10 to 15:45 |
| From 14:28 and before 15:43 | 17:00 to 18:00 | Next business day between 15:10 to 15:45 |
| From 15:43 to the next day | Next business day between 10:30 to 11:00 | Subsequent business day between 15:10 to 15:45 |
**CENIT cycles with implicit confirmation:**
| Transaction time | Money accredited | Implicit response |
| --------------------------- | ---------------------------------------- | ---------------------------------------- |
| From 00:00 and before 09:55 | 10:30 to 11:00 | Next business day between 15:10 to 15:45 |
| From 09:55 and before 12:25 | 13:00 to 13:30 | Next business day between 15:10 to 15:45 |
| From 12:26 and before 14:25 | 15:00 to 15:45 | Next business day between 15:10 to 15:45 |
| From 14:26 and before 15:40 | 17:00 to 18:00 | Next business day between 15:10 to 15:45 |
| From 15:41 to 11:59 pm | Next business day between 10:30 to 11:00 | Next business day at 10:00 pm |
There is a possibility, although very rare, that after providing you with a successful
response, the transaction may later be declined. In such cases, we will notify you immediately
via Webhook and we will also send you an email.
**Banks that operate through ACH CENIT:**
* BANCO FALABELLA
* BANCO FINANDINA
* BANCO GNB SUDAMERIS
* BANCO MUNDO MUJER
* BANCO PROCREDIT
* BANCO SANTANDER DE NEGOCIOS
* BBVA
* CFA FINANCIERA ANTIOQUIA
* COOFINEP
* BANCOOMEVA
* COTRAFA ENTIDAD FINANCIERA
* FINANCIERA JURISCOOP
* J.P. MORGAN COLOMBIA
* MIBANCO
## Fallback Attempts [#fallback-attempts]
Considering that Transfiya bank transfers are not fully adopted by all accounts in Colombia, you might encounter cases where the transaction is rejected. To address this issue, we have implemented fallback attempts that automatically retry the transaction using alternative routes.
To use these fallbacks attempts in the API requests, you can use the field `fallback_routing` and set with the corresponding value.
### Turbo to Transfiya [#turbo-to-transfiya]
If a Turbo transfer is rejected and the fallback routing is Transfiya, we will automatically send the transfer this way. This means that the payee will receive a message notifying that he can receive the transfer and what they need to do in order to receive the money instantly. The transfiya fallback transfer will be made using the payees phone number, so make sure to include it in the petition.
### Turbo to ACH [#turbo-to-ach]
If a Turbo bank transfer is rejected, and the fallback routing is ACH, we will automatically attempt to make an ACH bank transfer. This means that if a Turbo bank transfer fails, we will redirect the transaction using ACH rails.
### Transfiya to ACH [#transfiya-to-ach]
If a Transifya Turbo is rejected, and the fallback routing is ACH, we will automatically send the transfer this way. This means that if the transfiya transfer fails, we will redirect the transaction using ACH rails.
### No fallback [#no-fallback]
Also, you can set up transfers to do nothing in case they fail.
## Bank Transfer States [#bank-transfer-states]
Bank transfers can have the following states:
| State | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `created` | Indicates that the transfer has been created in our system, but it has not yet been sent to the bank for processing. |
| `in_progress` | Indicates that the transfer is currently being processed by the bank. This includes steps such as verifying the account information, transferring the funds between accounts, and updating the account balances. |
| `approved` | Indicates that the transfer has been approved by the bank and has been successfully executed. The funds have been transferred from the source account to the destination account. |
| `declined` | Indicates that the transfer has been rejected by the bank and will not be executed. This may occur due to insufficient funds, invalid account information, or regulatory restrictions. These reasons are stored in the `declination_reason` field. |
| `cancelled` | Indicates that the transfer has been canceled by a user before it has been sent to the bank. |
| `duplicated` | Indicates that the transfer has already been received in our system, and the repeated record will not be considered. This could be because the transfer was sent multiple times with the same `entity_id` field. |
## Bank Transfers Batch [#bank-transfers-batch]
A batch is a group of bank transfers that are processed together at the same time.
Bank transfers batches are useful in a number of situations, such as when making payroll payments to multiple employees, when transferring funds between multiple accounts, or when making regular payments to the same recipient. Bank transfers batches can save time and effort by allowing multiple transfers to be made at once, rather than having to initiate each transfer separately.
In Mono, every sending of transfers will create a batch, also in cases when you create transfers individually.
### Batch States [#batch-states]
| State | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `created` | Indicates that the batch has been received and not being authorized or processed yet. Every bank transfer will be in state `created`. |
| `pending_otp` | Indicates that the batch needs to be authorized by an Administrator user. |
| `verified_otp` | Indicates that the batch was authorized and it's in preparation to be processed. |
| `canceled` | Indicates that the batch was canceled by an Administrator user during preparation. Cancel a batch cancels its transfers. Every bank transfer will be in state `canceled`. It will send a webhook event called `batch_canceled`. |
| `processing_transactions` | Indicates that the batch is in the process to apply for its transfers. Every bank transfer will be in state `in_progress`. It will send a webhook event called `batch_sent`. |
| `partially_approved` | Indicates that some of the batch transfers were approved by the destination banks. Some transfers will mark as `approved` or `declined`. |
| `declined` | Indicates that all batch transfers were declined. Transfers will also mark as `declined`. |
| `approved` | Indicates that all batch transfers were approved by the destination banks. Transfers will also mark as `approved`. |
| `duplicated` | Indicates that all batch transfers were marked as duplicated because they were received before in our system. Every bank transfer will be in state `duplicated`. It will send a webhook event called `batch_duplicated`. |
# Collection Links (/docs/guides/banking/concepts/collection-links)
Collection links are a simple and convenient method for requesting and processing payments swiftly through PSE. Your clients or suppliers can pay you from any Colombian bank where PSE is activated.
## Types of Collection Links [#types-of-collection-links]
In Mono, there are two types of Collection Links:
### Mono Collection Links [#mono-collection-links]
This type of link redirects the payer to the Mono payment link form, where they can make the payment. It has the following customizable fields that you need to define:
* Amount
* Link name
* Use type
* Payer's details:
* Document
* Email
* Payer's name
* Phone
* Notes
* Reference
* Redirection URL
* Link type (reusable or single-use)
This type of link can be generated via API or the Mono app.
#### API Generation [#api-generation]
Once you complete the authentication process, you can use the collection intent endpoint to generate the necessary collection links. The API response will provide you with the payment link, which you will share with the payer.
### Custom Collection Links [#custom-collection-links]
The difference between Mono Collection Links and Custom Collection Links is that with Custom Collection Links, you can define the front end; you decide how your payment link form will look, and it is not redirected to the Mono link.
This type of link can be generated via API or the Mono app.
#### API Generation [#api-generation-1]
Creation of Custom Collection Links: This type of link can only be generated via API. After completing the authentication process, you can use the collection link endpoint.
## How can I receive notification for payment link events? [#how-can-i-receive-notification-for-payment-link-events]
Regardless of the method used to create your links, you can configure webhooks to receive payment link events in two ways:
| Event | Description |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Collection Intent Credited** | If the payment made using the payment link is successful, you will receive an event with the payer's information, reference data, the time it was made, the amount, and the link status. |
| **Collection Intent Confirmed** | If the payment made using the payment link is successful, when the payment is confirmed from our provider but it has not been credited to your account you will receive this event with the payer's information, reference data, the time it was made, the amount, and the link status. |
| **Collection Intent Failed** | In case the payment is not successful, you will receive information about the payer's link, reference, the time of the attempt, and the rejected status. |
# Bank Account (/docs/guides/banking/concepts/accounts)
A bank account is the place where you can manage and hold your organization's money.
Your organization can manage multiple accounts, each one with its own independent balance and state depending on your goals and projects. When you open an account in Mono, you are also opening an electronic deposit in Banco Cooperativo Coopcentral, that will hold your money.
## Account States [#account-states]
Accounts in Mono have the following states:
| State | Description |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `active` | Account in good standing and can be used for transfers. |
| `canceled` | Account closed by the organization's member or by Mono by inactivity. Once an account is canceled, it cannot be reactivated. |
# Cards (/docs/guides/banking/concepts/cards)
Cards are a payment method that allows you to make payments for products and services. Mono's cards are in partnership with Visa and they can be debit or prepaid.
## Types of Cards [#types-of-cards]
### Debit [#debit]
These cards work with the balance associated with your Mono account. You don't need to reload the card beforehand; you just need to have money in your account.
### Prepaid [#prepaid]
These cards work with the balance you decide to credit. If you don't put money on the card, it won't be activated. To load or withdraw money from the card, you should use the topup/withdrawal cards endpoint.
## Card Presentation [#card-presentation]
### Physical Cards [#physical-cards]
These can be used to make payments in physical stores, withdraw money from ATMs, and even make online purchases. Our physical cards can include your logo or have the classic Mono design.
#### How can you obtain them? [#how-can-you-obtain-them]
You can request physical cards in batches or individually. Request your physical cards from the Mono Web App by going to the Cards > Physical Card section. Contact our sales team to request cards in batches.
#### How do I activate a physical card? [#how-do-i-activate-a-physical-card]
Once you receive the cards, log in to the Mono Web App, go to the Cards section, and you will see the message: `You have 1 card on the way. Has it arrived? Activate it now`. There, you need to enter the card alias, the desired card limit, and a PIN.
### Virtual Cards [#virtual-cards]
These are available immediately and can be used for purchases and payments on digital platforms. With a CVV, they are perfect for paying for services like Facebook, Google, Slack, advertising, among others. Our virtual cards can be customized with the design you request or have the classic Mono design. Similar to physical cards, we guide you through the process to ensure everything meets your preferences.
#### How can you obtain them? [#how-can-you-obtain-them-1]
You can request virtual cards from the Mono web application or via API.
#### Creating a card via API [#creating-a-card-via-api]
After the authentication process, you can use the create card endpoint to generate the virtual cards you need. To generate one (or multiple) virtual cards in Mono via API, you need to define the following aspects:
* Account ID to which the card will be associated (you can find your account ID in the accounts endpoint).
* Cardholder information.
* Initial amount if the card is prepaid.
* Card alias and limit.
* Virtual cards are issued in an `active` state and can be used immediately. Physical cards are issued in a `created` state and must be activated before they can be used.
## Card States [#card-states]
Your Mono cards can be in one of the following states:
| State | Description |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `created` | This state only applies to physical cards. The state your card is in before being activated in the Mono app. If your card is in this state, it won't work at any store. |
| `active` | This is the only state in which your card will work for payments. |
| `frozen` | In this state, your card is "suspended" for normal operation. You can re-activate it via API using the endpoint or from your Mono app. |
| `canceled` | Once you cancel a card, you can't use or activate it again. Use it when you no longer want to use this card. |
# Concepts (/docs/guides/banking/concepts)
Understand the building blocks of the Banking API. These concepts explain how bank accounts, transfers, collection links, and cards compose to move money in and out of the Colombian banking network and to spend it through Mono-issued cards.
Understanding bank accounts and their states
Routing options, fallbacks, and transfer states
Virtual and physical cards — types, presentation, and states
Request and process payments through PSE
# Collection Links (/docs/guides/banking/sandbox/collection-links)
Use these test cases to validate how your integration handles collection link events — successful payments, expirations, and declines.
This page is a sandbox scaffold. The full version will include deterministic test cases
(specific field values that trigger each collection link outcome) and the corresponding
webhook events.
## Test scenarios [#test-scenarios]
| Scenario | Description | Test case |
| :----------------- | :------------------------------------------------------------------------------- | :--------------- |
| Successful payment | The collection link is paid and a `collection_intent_credited` webhook is fired. | To be documented |
| Link expired | The collection link reaches its expiry time without payment. | To be documented |
| Payment declined | The payer's bank declines the PSE transaction. | To be documented |
## Next steps [#next-steps]
* [PSE collection flow](/docs/guides/banking/flows/pse-collection) — the full collection sequence in production.
* [Banking webhooks](/docs/guides/banking/webhooks) — events fired for each collection outcome.
# PSE Collection (/docs/guides/banking/sandbox/pse-collection)
Use these test cases to validate how your integration handles PSE collection intents — bank selection, successful payments, user rejections, and network timeouts.
This page is a sandbox scaffold. The full version will include the list of available sandbox
banks, deterministic test cases for each outcome, and the webhook events fired at each step.
## Sandbox banks [#sandbox-banks]
PSE sandbox provides a set of test banks that simulate different response behaviors. The full catalog of sandbox bank codes will be documented here.
## Test scenarios [#test-scenarios]
| Scenario | Description | Test case |
| :----------------- | :------------------------------------------------------------- | :--------------- |
| Successful payment | The PSE transaction is accepted and funds are credited. | To be documented |
| User rejection | The account holder rejects the payment in their bank's portal. | To be documented |
| Timeout | The PSE transaction expires without a response from the bank. | To be documented |
| Bank unavailable | The selected bank is temporarily unavailable in the sandbox. | To be documented |
## Next steps [#next-steps]
* [PSE collection flow](/docs/guides/banking/flows/pse-collection) — the full collection sequence in production.
* [Collection Links](/docs/guides/banking/sandbox/collection-links) — test the collection link that wraps the PSE intent.
# Bank Transfers (/docs/guides/banking/sandbox/bank-transfers)
In the sandbox environment, you can simulate successful and declined bank transfers. To develop your system correctly, keep in mind that two validations occur before the money is credited to the payee's account.
1. To simulate ACH or Turbo cases, set the corresponding value in the `routing` field. 2. The
simulation evaluates the `payee.document_number` field. 3. For `document_type: CC`,
`payee.document_number` must be **5–10 digits**. Use a value in that range that matches the
pattern (e.g. `8881423000`). Values longer than 10 digits are rejected with a `422` validation
error before the scenario runs.
## ACH [#ach]
These cases are available to simulate for ACH:
### Origin bank validations [#origin-bank-validations]
The first validation is done by our bank partner to ensure you have enough balance, including the GMF tax, and that you haven't exceeded the origin account's transactional limits (any admin user can adjust these limits directly in the user interface). Origin bank declinations in production are usually informed in less than 10 minutes through a webhook.
**Test case**: `payee.document_number` starts with `888` and ends with `000` (e.g., **888**1423**000**)
### Destination bank validations [#destination-bank-validations]
The second validation is performed by the destination bank to check the account information and status. In production, these declinations take up to three ACH cycles, or around 12 working hours, to be received. In the sandbox, these are simulated in 2 minutes.
**Test case**: `payee.document_number` starts with `999` and ends with `000` (e.g., **999**1423**000**)
### Successful ACH bank transfer [#successful-ach-bank-transfer]
The transfer should be approved.
**Test case**: `payee.document_number` does not meet the criteria for any of the other test cases (e.g., `1234567890`).
***
## Turbo [#turbo]
These cases are available to simulate for Turbo:
| Scenario | Description | Test case |
| ----------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Global limit exceeded | The transfer exceeds the global limit allowed for the account. | Starts with `777`, ends with `000` |
| Insufficient funds | The origin account doesn't have sufficient funds to complete the transfer. | Starts with `777`, ends with `111` **or** starts with `111`, ends with `000` |
| Turbo not supported | The destination bank doesn't support Turbo transfers (signer not found). | Starts with `777`, ends with `222` |
| Transaction deadline exceeded | The transfer was rejected by the destination bank due to timeout. | Starts with `777`, ends with `333` **or** starts with `100`, ends with `001` |
| Disabled account | The destination account is disabled. | Starts with `777`, ends with `444` |
| Successful Turbo transfer | The transfer should be approved. | Does not meet any other criteria |
For a successful Turbo transfer, use any valid `CC` `payee.document_number` (5–10 digits) that matches none of the patterns above, e.g. `1234567890`.
***
## Transfiya [#transfiya]
These cases are available to simulate for Transfiya:
| Scenario | Description | Test case |
| ----------------------------- | -------------------------------------------------------------------------- | ---------------------------------- |
| Global limit exceeded | The transfer exceeds the global limit allowed for the account. | Starts with `555`, ends with `000` |
| Disabled account | The destination account is disabled. | Starts with `555`, ends with `444` |
| Insufficient funds | The origin account doesn't have sufficient funds to complete the transfer. | Starts with `555`, ends with `111` |
| Transaction deadline exceeded | The transfer was rejected by the destination bank due to timeout. | Starts with `555`, ends with `222` |
| Transaction not completed | The user rejected or didn't complete the transaction through Transfiya. | Starts with `555`, ends with `333` |
| Successful Transfiya transfer | The transfer should be approved. | Does not meet any other criteria |
"global\_limit\_exceeded" is the only case that allows routing simulation with a permitted error
for fallbacks.
***
## CoopCentral Bre-B [#coopcentral-bre-b]
CoopCentral Bre-B provides instant transfers through the Bre-B network. The sandbox allows you to simulate different scenarios using specific patterns in the **Bre-B key** (for key lookups) and **description** field (for transaction operations).
### Bre-B Transfer Process [#bre-b-transfer-process]
A Bre-B transfer follows a three-step process:
1. **Key Lookup**: First, the system validates the Bre-B key and retrieves the associated account information from the network.
2. **Transaction Initiation**: Once the key is validated, the transfer is initiated with the destination participant.
3. **Status Verification**: Finally, the transaction status is checked to confirm whether it was completed successfully or failed.
The simulation sections below correspond to each of these steps, allowing you to test different scenarios at each stage of the transfer process.
For successful Bre-B transfers in sandbox, when sending identity document information, you must use:
* `document_type`: `CC`
* `document_number`: `12345678`
**Important**: If you send document identity information and it differs from these values, the transfer will generate errors. You can also choose not to send the document identity information to skip this validation.
### Bre-B Key Lookup Simulation [#bre-b-key-lookup-simulation]
These cases are available to simulate Bre-B key lookup by using specific **Bre-B key patterns**:
| Scenario | Description | Test case |
| ----------------------- | ------------------------------------------------------------------- | ----------------------------------- |
| Key not found | The Bre-B key doesn't exist in the network. | Key contains `key_not_found` |
| Participant unavailable | The destination participant/bank is temporarily unavailable. | Key starts with `key_unavailable` |
| Key blocked or inactive | The Bre-B key is blocked or inactive in the network. | Key contains `key_blocked` |
| Timeout | The key lookup request timed out. | Key contains `key_timeout` |
| Successful lookup | The Bre-B key lookup should succeed and return account information. | Key doesn't match any error pattern |
### Bre-B Transaction Initiation Simulation [#bre-b-transaction-initiation-simulation]
These cases are available to simulate Bre-B transaction initiation by using specific **description patterns**:
| Scenario | Description | Test case |
| ----------------------------- | ------------------------------------------------------------- | ---------------------------------------------- |
| Timeout | The transaction initiation request timed out. | Description contains `tx_timeout` |
| Disabled account | The destination account is disabled. | Description contains `tx_disabled` |
| Invalid Bre-B key | The provided Bre-B key is invalid. | Description contains `tx_invalid_key` |
| Daily amount limit exceeded | The transfer exceeds the daily amount limit. | Description contains `tx_limit_daily_amount` |
| Daily count limit exceeded | The account has exceeded the daily transaction count limit. | Description contains `tx_limit_daily_count` |
| Fraud detected | The transaction was flagged as fraudulent. | Description contains `tx_fraud` |
| Insufficient funds | The origin account doesn't have sufficient funds. | Description contains `tx_insufficient` |
| Monthly amount limit exceeded | The transfer exceeds the monthly amount limit. | Description contains `tx_limit_monthly_amount` |
| Monthly count limit exceeded | The account has exceeded the monthly transaction count limit. | Description contains `tx_limit_monthly_count` |
| Invalid account | The destination account information is invalid. | Description contains `tx_invalid_account` |
| Account blocked | The destination account is blocked. | Description contains `tx_blocked` |
| Unknown error | An unknown error occurred during processing. | Description contains `tx_error` |
| Successful initiation | The transaction should be initiated successfully. | Description doesn't match any error pattern |
### Bre-B Transaction Status Simulation [#bre-b-transaction-status-simulation]
These cases are available to simulate Bre-B transaction status checks by using specific **Bre-B key patterns**:
The transaction status simulation uses the same Bre-B key that was used during the key lookup
step. The system validates the pattern of the Bre-B key to determine which status to return.
| Scenario | Description | Test case | Status code |
| ----------------------- | ------------------------------------------------- | ----------------------------------- | ----------- |
| Transaction not found | The transaction ID doesn't exist. | Key contains `status_not_found` | - |
| Timeout | The status check request timed out. | Key contains `status_timeout` | - |
| Participant unavailable | The participant is unavailable for status checks. | Key contains `status_unavailable` | - |
| Failed transaction | The transaction has failed. | Key contains `status_failed` | `91` |
| Pending transaction | The transaction is still pending. | Key contains `status_pending` | - |
| Successful transaction | The transaction has completed successfully. | Key doesn't match any error pattern | `00` |
# Sandbox (/docs/guides/banking/sandbox)
Use the sandbox environment to test your Banking integration end-to-end without processing real transactions. The sandbox simulates the full lifecycle of transfers, collection links, cards, and webhooks so you can validate your implementation before going to production.
## Base URL [#base-url]
```
https://api.sandbox.cuentamono.com
```
## Test scenarios [#test-scenarios]
Simulate successful and declined bank transfers
Simulate collection link payments and expirations
Simulate PSE bank selection, acceptance, and rejections
Simulate card creation and controls (Core sandbox)
## Next steps [#next-steps]
* [Webhook conventions](/docs/guides/api-standards/webhooks) — signature verification for sandbox webhook events.
* [Production checklist](/docs/guides/banking/production) — steps to take before going live.
# Collection Links (/docs/guides/banking/webhooks/collection-links)
When a person initiates a payment using a collection link, a webhook request is generated based on the state of the payment intent.
## Event Types [#event-types]
| Event | Description |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `collection_intent_credited` | Triggered when an intent of payment has been successfully credited to the recipient's bank account. It signifies a successful transaction where the funds have been successfully transferred. |
| `collection_intent_failed` | Triggered when an intent of payment could not be processed by the payment provider and, as a result, the payment was declined. |
## Payload Example [#payload-example]
```json
{
"event": {
"data": {
"account_id": "acc_223RxfTSugBz8KfLSWoEnT",
"amount": {
"amount": 10000000,
"currency": "COP"
},
"collection_link_id": "clink_3QVH4A9L4QQzAajaK4VMrB",
"external_id": "b3852866-33b2-452c-b269-33591a38c4d0",
"id": "cint_5dHsMj4uwhhujcVUTuw1OP",
"inserted_at": "2023-01-04T17:16:32.812292Z",
"note": "a Note",
"payer": {
"document_number": "1033711400",
"document_type": "CC",
"name": "peter rosas"
},
"payment": {
"provider": "pse",
"transaction_id": "3052043"
},
"reference": "REF #132ABC",
"state": "account_credited",
"updated_at": "2023-01-04T17:16:58.071790Z"
},
"type": "collection_intent_credited"
},
"timestamp": "2023-01-04T17:17:01.101130Z"
}
```
The `external_id` field can be useful to associate with some identifier in your system. You
can set this field when creating a collection link via the API.
# Bank Transfers (/docs/guides/banking/webhooks/bank-transfers)
When bank transfers are created and then either approved, rejected, or retried with a fallback routing, a webhook event will be sent to a specified URL with details of the transfer and its current state. This allows clients to receive real-time updates on the status of their transfers and take appropriate actions based on the events received.
## Event Types [#event-types]
The following event types are related to bank transfers:
| Event | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------ |
| `bank_transfer_approved` | Triggered when a bank transfer is approved. |
| `bank_transfer_rejected` | Triggered when a bank transfer is declined. |
| `bank_transfer_fallback_routing` | Triggered when a bank transfer is retried with a different routing. |
| `bank_transfer_change_final_state` | Triggered when a transfer already in a final state moves to a different final state. |
| `account_credited` | Triggered when an incoming transfer is successfully credited to your account. |
## Payload Example [#payload-example]
```json
{
"event": {
"data": {
"amount": {
"amount": 1600000,
"currency": "COP"
},
"batch": {
"account_id": "acc_16ktUqSO7G0qTHDz8I3qrG",
"id": "bat_2DpSchlriwoCuyGMOoIuwp",
"inserted_at": "2022-12-28T16:19:52.471575Z",
"origin": "api",
"state": "declined",
"total_amount": {
"amount": 1600000,
"currency": "COP"
},
"updated_at": "2022-12-28T16:40:06.822840Z"
},
"declination_reason": null,
"description": "some description",
"entity_id": "123456789",
"id": "trn_2PVWOx9dZKJMBZw7opjrrs",
"inserted_at": "2022-12-28T16:19:52.530158Z",
"payee": {
"bank_account": {
"bank_code": "007",
"number": "000000009",
"type": "savings_account"
},
"document_number": "1033711400",
"document_type": "CC",
"email": "someone@gmail.com",
"name": "patricio rothschild"
},
"recipient_handle": null,
"reference": "ref # 1231",
"state": "approved",
"updated_at": "2022-12-28T16:40:06.812742Z",
"routing": "ach",
"fallback_routing": []
},
"type": "bank_transfer_approved"
},
"timestamp": "2022-12-28T16:54:38.660650Z"
}
```
## Rejected Transfer [#rejected-transfer]
When the event type is `bank_transfer_rejected`, the field `declination_reason` will be a string indicating the reason for the rejection:
```json
{
"event": {
"data": {
"declination_reason": "insufficient_funds",
"state": "declined"
},
"type": "bank_transfer_rejected"
},
"timestamp": "2022-12-28T16:54:38.660650Z"
}
```
***
## Batch Events [#batch-events]
A webhook request is sent related to a batch when:
* The batch was created with all its transfers as duplicated (with previously registered `entity_id`)
* The batch was requested for authorization
* The batch was sent to process
* The batch was canceled
### Batch Payload Example [#batch-payload-example]
```json
{
"event": {
"data": {
"account_id": "acc_16ktUqSO7G0qTHDz8I3qrG",
"id": "bat_5d60uR36JfH4J4uN9ZEYhL",
"inserted_at": "2022-12-28T18:01:21.549268Z",
"origin": "api",
"state": "duplicated",
"total_amount": {
"amount": 100000,
"currency": "COP"
},
"transfers": [
{
"amount": {
"amount": 100000,
"currency": "COP"
},
"declination_reason": null,
"description": "eaque qui dolorem",
"entity_id": "d31d1c0d-395b-43f4-b57b-a3ae4b8c34d6",
"id": "trn_5NYRqKnE17nE0MswylZzq1",
"inserted_at": "2022-12-28T18:01:21.602854Z",
"payee": {
"bank_account": {
"bank_code": "007",
"number": "123456789",
"type": "savings_account"
},
"document_number": "123456789",
"document_type": "CC",
"email": "someone@gmail.com",
"name": "Ginger Gutmann"
},
"recipient_handle": "573004366648",
"reference": "quasi sint inventore",
"state": "duplicated",
"updated_at": "2022-12-28T18:01:21.602854Z"
}
],
"updated_at": "2022-12-28T18:01:21.765972Z"
},
"type": "batch_duplicated"
},
"timestamp": "2022-12-28T18:01:36.586019Z"
}
```
The webhook event type will vary based on what happened to the batch:
| Event | Description |
| ------------------------------- | --------------------------------------------------------------------------------- |
| `batch_duplicated` | All transfers in the batch were already received (duplicate `entity_id`). |
| `batch_authorization_requested` | The batch requires OTP authorization from an administrator before it can be sent. |
| `batch_sent` | The batch was authorized and dispatched for processing. |
| `batch_canceled` | The batch was canceled by an administrator before being processed. |
***
## Account Credited [#account-credited]
When a transfer is successful and the money is in your account, a webhook request will be sent.
### Payload Example [#payload-example-1]
```json
{
"event": {
"data": {
"account": {
"id": "acc_16ktUqSO7G0qTHDz8I3qrG",
"number": "095000036"
},
"amount": {
"amount": 105000,
"currency": "COP"
},
"description": "some description, abcde...",
"extra": {
"entity_id": null,
"reference": "some ref: #123..."
},
"id": "tx_3RJ9CbZLYq7r1MxH8vAm7Y",
"payer": {
"document_number": "9013980684",
"name": "MONO COLOMBIA S.A.S.",
"bank": {
"name": "BANCO COOPERATIVO COOPCENTRAL",
"code": "066"
}
},
"transaction_at": "2023-03-08T17:20:55.809787Z",
"type": "bank_transfer_mono"
},
"type": "account_credited"
},
"timestamp": "2023-03-08T17:21:01.673116Z"
}
```
### Transfer Types [#transfer-types]
There are two types of transfers, which can be identified by the `type` field in the `data` object:
| Type | Description |
| -------------------- | ----------------------------------------------------- |
| `bank_transfer_mono` | A real-time transfer made via Mono's internal system. |
| `incoming_wire` | A wire transfer made via ACH. |
If the type is `incoming_wire`, the `extra` field in the `data` object will be empty.
### Extra Object Fields [#extra-object-fields]
The `extra` object in the `data` object contains the following fields:
* `entity_id`: The ID of the entity for the transfer.
* `reference`: The reference of the transfer.
These fields are set when you create a bank transfer using the API.
# Webhooks (/docs/guides/banking/webhooks)
Webhooks notify your system the moment a Banking event occurs — a transfer is approved, a collection link is paid. For the shared mechanics that govern all Mono webhooks (signature verification, retry policy, money fields, URL restrictions), see [Webhook conventions](/docs/guides/api-standards/webhooks).
## Webhook Events [#webhook-events]
Transfer approvals, rejections, fallback routing, and batches
Collection link payment events
## Next steps [#next-steps]
* [Webhook conventions](/docs/guides/api-standards/webhooks) — signature verification, retry policy, and money fields.
* [Dashboard](/docs/guides/banking/dashboard) — configure webhook endpoints and inspect recent deliveries.
# Architecture overview (/docs/guides/banking/architecture/index)
Banking is Mono's bridge between your product and the Colombian banking network. When you call a Banking endpoint, the request passes through authentication, idempotency check, and routing before being dispatched to one of the available payout rails: **ACH**, **Transfiya**, or **Mono Turbo**. Collections (money in) follow the **PSE** rail.
This page is the right starting point if you want to understand the path a request takes through Banking, the rail it lands on, and why a single transfer call may end up using a different rail than the one you requested (Mono falls back automatically).
## Rails [#rails]
| Rail | Type | Typical use |
| :--------- | :--------------- | :----------------------------------------------------- |
| Mono Turbo | Real-time payout | Real-time transfers to participating Colombian banks |
| ACH | Standard payout | Same-day transfers to any Colombian bank |
| Transfiya | Real-time payout | Transfers within a specific bank cluster |
| PSE | Payin | Customer-initiated bank transfer (e-commerce checkout) |
## Request lifecycle [#request-lifecycle]
1. **Authenticate** — the API key in the `Authorization` header is validated.
2. **Check idempotency** — `X-Idempotency-Key` is looked up against the cache.
3. **Route** — the request lands on the rail you requested or its fallback.
4. **Dispatch** — Mono submits the operation to the rail.
5. **Confirm** — webhooks fire as the rail confirms or rejects.
## Fallbacks [#fallbacks]
Mono Turbo can fall back to ACH when the destination does not support real-time settlement. The fallback decision tree will be documented here.
## Lifecycle references [#lifecycle-references]
Once a request enters the platform it moves through a deterministic state machine, and every transition emits a webhook. The pages below document each state, its transitions, and the corresponding event — read these alongside the architecture above to understand what your integration should be listening for.
* [Bank transfer lifecycle](/docs/guides/banking/architecture/bank-transfer-states) — states, webhook events, and terminal outcomes for transfers and batches.
## Next steps [#next-steps]
* [Authentication](/docs/guides/banking/api/authentication)
* [Bank Transfers](/docs/guides/banking/concepts/transfers)
* [Sandbox](/docs/guides/banking/sandbox)
# Bank transfer lifecycle (/docs/guides/banking/architecture/bank-transfer-states)
A bank transfer is a payout: money leaves your Mono account and lands in a beneficiary's Colombian bank account over one of three rails — ACH, Transfiya, or Mono Turbo. Every transfer you create through `POST /transfers` is automatically grouped into a **batch**, and both the transfer and the batch move through their own state machines in parallel. This page explains what each state means, what causes each transition, and which webhook event fires at each step.
Understanding both state machines matters because your webhook handler will receive batch events (was the batch authorized? sent?) and transfer events (was the individual transfer approved or rejected?) in an interleaved stream.
## Transfer state machine [#transfer-state-machine]
### Transfer states at a glance [#transfer-states-at-a-glance]
| State | Terminal? | Webhook event |
| ------------- | --------- | ---------------------------------------------------------------------------------------------------------------- |
| `created` | No | — (synchronous API response) |
| `in_progress` | No | [`bank_transfer_fallback_routing`](/docs/guides/banking/webhooks/bank-transfers) fires if a routing retry occurs |
| `approved` | **Yes** | [`bank_transfer_approved`](/docs/guides/banking/webhooks/bank-transfers) |
| `declined` | **Yes** | [`bank_transfer_rejected`](/docs/guides/banking/webhooks/bank-transfers) |
| `cancelled` | **Yes** | — (user-initiated before dispatch; no async event) |
| `duplicated` | **Yes** | — (surfaced via [`batch_duplicated`](/docs/guides/banking/webhooks/bank-transfers) at the batch level) |
The `bank_transfer_change_final_state` event fires in the rare case where a transfer already
in `approved` or `declined` is moved to a different final state by the rail. Always handle
this event idempotently — update your local record to the new state even if you already
considered it settled.
## Batch state machine [#batch-state-machine]
Every call to `POST /transfers` creates a batch, even when you submit a single transfer. The batch is the unit that goes through admin authorization (OTP) if your account requires it.
### Batch states at a glance [#batch-states-at-a-glance]
| State | Terminal? | Webhook event |
| ------------------------- | --------- | ------------------------------------------------------------------------------- |
| `created` | No | — (synchronous API response) |
| `pending_otp` | No | [`batch_authorization_requested`](/docs/guides/banking/webhooks/bank-transfers) |
| `verified_otp` | No | — (internal; transitions immediately to `processing_transactions`) |
| `processing_transactions` | No | [`batch_sent`](/docs/guides/banking/webhooks/bank-transfers) |
| `approved` | **Yes** | — (individual transfers emit `bank_transfer_approved`) |
| `partially_approved` | **Yes** | — (mix of `bank_transfer_approved` and `bank_transfer_rejected` per transfer) |
| `declined` | **Yes** | — (individual transfers emit `bank_transfer_rejected`) |
| `duplicated` | **Yes** | [`batch_duplicated`](/docs/guides/banking/webhooks/bank-transfers) |
| `canceled` | **Yes** | [`batch_canceled`](/docs/guides/banking/webhooks/bank-transfers) |
## Detailed walkthrough [#detailed-walkthrough]
### Transfer: `created` [#transfer-created]
Initial state. The transfer row exists in the system with an `id` you can use for lookups. Mono has accepted the request but has not yet submitted it to the rail. Every transfer in the batch enters this state together.
**What happens next:** once the batch clears any OTP gate and reaches `processing_transactions`, each transfer moves to `in_progress`.
### Transfer: `in_progress` [#transfer-in_progress]
The transfer has been dispatched to the selected rail — ACH, Mono Turbo, or Transfiya. Funds are in transit; the source account balance has been debited but the destination has not yet confirmed credit.
If the primary rail rejects the attempt and you set a `fallback_routing`, Mono retries automatically. This fires `bank_transfer_fallback_routing` without changing the transfer state — the transfer stays `in_progress` through the retry.
**Next states:**
* → `approved` when the rail confirms the credit.
* → `declined` when the rail rejects and no fallback succeeds.
### Transfer: `approved` (terminal) [#transfer-approved-terminal]
The destination bank has confirmed the credit. Funds are in the beneficiary's account. The `bank_transfer_approved` webhook fires with the final routing and amount. No further transitions occur under normal circumstances.
### Transfer: `declined` (terminal) [#transfer-declined-terminal]
The transfer was rejected. The `declination_reason` field on the webhook payload carries the rejection reason (for example, `insufficient_funds` or `invalid_account_information`). The `bank_transfer_rejected` webhook fires. No further transitions occur under normal circumstances.
### Transfer: `cancelled` (terminal) [#transfer-cancelled-terminal]
The transfer was canceled by a user before the batch reached `processing_transactions`. Canceling a batch cancels all its transfers. No async webhook fires for individual transfers — the batch emits `batch_canceled`.
### Transfer: `duplicated` (terminal) [#transfer-duplicated-terminal]
The `entity_id` on this transfer was already used in a previous batch. The transfer is not processed. The batch emits `batch_duplicated`.
***
### Batch: `created` [#batch-created]
The batch row exists in the system. All its transfers are in `created`. This state is synchronous — you get the batch `id` in the API response.
**What happens next:** Mono checks whether the batch requires OTP authorization.
### Batch: `pending_otp` [#batch-pending_otp]
The batch requires explicit authorization from an Administrator user before it can be processed. The `batch_authorization_requested` webhook fires. No transfers move until an admin authorizes.
**Next states:**
* → `verified_otp` when an admin completes authorization.
* → `canceled` when an admin cancels.
### Batch: `verified_otp` [#batch-verified_otp]
The admin has authorized. Mono prepares the batch for dispatch. This state is transient — it moves to `processing_transactions` within seconds and no webhook fires for `verified_otp` itself.
### Batch: `processing_transactions` [#batch-processing_transactions]
The batch is being dispatched. Each transfer moves to `in_progress`. The `batch_sent` webhook fires. From this point, individual transfer webhooks (`bank_transfer_approved` / `bank_transfer_rejected`) drive reconciliation.
### Batch: `approved` (terminal) [#batch-approved-terminal]
All transfers in the batch were approved. Individual `bank_transfer_approved` events have already fired for each transfer.
### Batch: `partially_approved` (terminal) [#batch-partially_approved-terminal]
Some transfers were approved and some were declined. Each transfer has already emitted its own `bank_transfer_approved` or `bank_transfer_rejected` event. Reconcile transfer by transfer using the individual events.
### Batch: `declined` (terminal) [#batch-declined-terminal]
All transfers in the batch were declined. Individual `bank_transfer_rejected` events have already fired.
### Batch: `duplicated` (terminal) [#batch-duplicated-terminal]
Every transfer in the batch had an `entity_id` that already existed. No transfers are processed. `batch_duplicated` fires. Individual transfers move to `duplicated`.
### Batch: `canceled` (terminal) [#batch-canceled-terminal]
An admin canceled the batch before it reached `processing_transactions`. All transfers move to `cancelled`. `batch_canceled` fires.
## Working with these state machines in your integration [#working-with-these-state-machines-in-your-integration]
1. **Key off webhooks, not polling.** Every meaningful transition emits a webhook. Subscribe to both transfer and batch events — they carry different information and arrive in interleaved order.
2. **Handle out-of-order delivery.** Webhook deliveries are at-least-once and may arrive out of order. If you receive `bank_transfer_approved` before `batch_sent`, keep the later state and ignore the earlier one. A safe rule: if a transfer is already in a terminal state locally, ignore non-terminal webhooks for it.
3. **Build for the OTP gate.** If your Mono account requires batch authorization, your batch will sit in `pending_otp` until an admin acts. Plan your UX around this: expose the batch state to operators so they know when action is required.
4. **Distinguish transfer state from batch state.** A batch in `partially_approved` does not tell you which transfers succeeded — you must inspect individual transfer events. Reconcile transfer by transfer, not batch by batch.
5. **Handle `bank_transfer_change_final_state`.** It is rare, but a transfer already in `approved` or `declined` can move to a different final state. Update your local record when this event arrives; do not treat a terminal state as immutable until you stop receiving events for that transfer.
6. **Use `entity_id` for idempotency.** Submitting the same `entity_id` twice results in `duplicated`. This is your safety net against double-submission — but it also means you must generate a fresh `entity_id` for every genuinely new transfer.
## Next steps [#next-steps]
* [Webhooks: bank transfers](/docs/guides/banking/webhooks/bank-transfers) — the full payload schemas for every event above.
* [Sending transfers flow](/docs/guides/banking/flows/sending-transfers) — the end-to-end sequence that moves a transfer through these states.
* [Sandbox: bank transfers](/docs/guides/banking/sandbox/bank-transfers) — simulate the full lifecycle without moving real money.
# Authentication (/docs/guides/banking/api/authentication)
Every request to the Banking API must prove that it comes from your company. You can prove it with a long-lived API key, or with a short-lived access token that your server requests on its own. Mono accepts both on every Banking endpoint, so you can choose the one that fits your security practices.
A credential belongs to one service. Banking credentials work only on the Banking API, and a
Core credential sent to a Banking endpoint is rejected.
## Before you start [#before-you-start]
You will need:
* Access to the Mono Dashboard for your organization.
* An HTTPS client. Mono refuses requests that do not use TLS.
* A safe place for secrets, such as a secrets manager. Never commit credentials to source control.
The conventions shared by every Mono product, such as the header format and environments, are in [Authentication standards](/docs/guides/api-standards/authentication).
## Choose a credential [#choose-a-credential]
| | API key | OAuth 2.0 access token |
| :---------------- | :---------------------------- | :--------------------------------------------------- |
| How long it lasts | Until you revoke it | 30 minutes, then you request a new one |
| What it can do | What its roles allow | What its scopes allow |
| How you get it | Created in the Mono Dashboard | Requested by your server with a client ID and secret |
| Where it goes | `Authorization: Bearer ` | `Authorization: Bearer ` |
A **role** is a named set of permissions, such as "Admin" or "Viewer", attached to an API key. A **scope** is a single permission, such as `transfers:readonly`, attached to an access token. Mono maps each role permission to the scopes that grant the same action, so an endpoint accepts either credential for the same operation.
## Option 1: API keys [#option-1-api-keys]
An API key is a secret string that stays valid until you revoke it. To create or revoke keys, sign in to the [Mono Dashboard](https://mi.cuentamono.com/log_in) and open the [API key management section](https://mi.cuentamono.com/settings/api).
When you create a key, choose its roles. The key can do exactly what those roles allow. For example, a key with the "Viewer" role can read data but not move money.
Send the key in the `Authorization` header of every request:
```bash
curl https://api.cuentamono.com/v1/accounts \
-H "Authorization: Bearer $MONO_API_KEY"
```
Keys do not expire, so rotating them is your responsibility: create a new key, deploy it, then revoke the old one.
## Option 2: OAuth 2.0 access tokens [#option-2-oauth-20-access-tokens]
OAuth 2.0 is an industry standard for issuing temporary credentials. Mono uses its **client credentials** flow, designed for server-to-server integrations. Your server exchanges a permanent client ID and client secret for an access token that lasts 30 minutes. Think of it as trading an employee badge for a day pass: the pass expires, the badge stays in the safe.
### Get a credential [#get-a-credential]
Banking OAuth credentials are not self-service yet: the Mono Dashboard cannot create them for Banking organizations. To get one, contact your Mono account team and tell them which scopes your integration needs.
Mono gives you a client ID and a client secret. Store both in your secrets manager as soon as you receive them. To change the scopes later, or to disable or delete the credential, contact the same team.
### Request an access token [#request-an-access-token]
Call the token endpoint with `grant_type=client_credentials`, your client ID and secret, and the scopes you need, separated by spaces:
```bash
curl https://api.cuentamono.com/v1/oauth/token \
-d grant_type=client_credentials \
-d client_id="$MONO_CLIENT_ID" \
-d client_secret="$MONO_CLIENT_SECRET" \
-d scope="accounts:readonly transfers"
```
The endpoint accepts `application/x-www-form-urlencoded`, as above, or the same fields as JSON. In sandbox, use `https://api.sandbox.cuentamono.com`.
```json
{
"access_token": "",
"token_type": "Bearer",
"expires_in": 1800,
"refresh_token": "",
"scope": "accounts:readonly transfers"
}
```
Always send `scope`. If you leave it out, Mono still issues a token, but with no scopes, and
every endpoint then answers `403`. If you ask for a scope your credential does not have, the
whole request fails with `400 invalid_scope`; Mono never returns a token with only part of
what you asked for.
### Call the API [#call-the-api]
Send the access token in the `Authorization` header, exactly like an API key:
```bash
curl https://api.cuentamono.com/v1/accounts \
-H "Authorization: Bearer $MONO_ACCESS_TOKEN"
```
### Renew the token [#renew-the-token]
`expires_in` is the token's remaining lifetime in seconds: 1800 when it is issued. Request a new token before it runs out. You have two ways:
* **Request a new token** with your client credentials, exactly as above. This is the simplest option for a server that always holds its secret.
* **Use the refresh token.** The response also carries a `refresh_token`, valid for one day. Exchange it for a new access token:
```bash
curl https://api.cuentamono.com/v1/oauth/token \
-d grant_type=refresh_token \
-d client_id="$MONO_CLIENT_ID" \
-d client_secret="$MONO_CLIENT_SECRET" \
-d refresh_token="$MONO_REFRESH_TOKEN"
```
Each refresh returns a new refresh token. Use the new one next time and discard the old one.
### Check or revoke a token [#check-or-revoke-a-token]
Two more endpoints take your client ID and secret plus the token itself:
* **Introspect** (`grant_type=introspect`) tells you whether a token is still active, when it expires, and which scopes it carries. An expired or unknown token returns `{"active": false}`.
* **Revoke** (`grant_type=revoke`) invalidates an access token or a refresh token at once. It answers `200` even if the token had already expired.
Revoke tokens you no longer need, for example when you retire a server. The endpoint contracts are in the [Banking authentication reference](/docs/api-reference/banking/authentication).
## Scopes [#scopes]
Request the narrowest scopes your integration needs. Two rules apply across the catalog:
* A bare scope, such as `transfers`, grants every action on that resource, including the specific actions listed below it.
* A `:readonly` scope grants reads only. It does not include the specific actions: `cards:readonly` does not grant `cards:details`.
| Scope | Grants |
| :--------------------------- | :---------------------------------------------------------------------------------------- |
| `accounts` | All permissions for accounts |
| `accounts:readonly` | Read accounts |
| `accounts:balances` | Read the balance of an account |
| `transfers` | All permissions for bank transfers, and reading the bank catalog |
| `transfers:readonly` | Read bank transfers and the bank catalog |
| `transfers:prepare` | Create bank transfers that wait for approval |
| `collection_links` | All permissions for collection links and collection intents, and reading the bank catalog |
| `collection_links:readonly` | Read collection links, collection intents, and the bank catalog |
| `cards` | All permissions for cards |
| `cards:readonly` | Read cards |
| `cards:details` | Read a card's sensitive details: number, security code, expiry |
| `spending_controls` | All permissions for card spending controls |
| `spending_controls:readonly` | Read card spending controls |
## Failures [#failures]
The token, introspect, and revoke endpoints answer errors in the OAuth format, with `error` and `error_description` fields. For example, `400 invalid_scope` means you asked for a scope your credential does not have.
Every other endpoint answers with the standard [error envelope](/docs/guides/api-standards/errors-and-retries):
| Status | Code | What happened |
| :----- | :--------------- | :--------------------------------------------------------------------------- |
| `401` | `expired_token` | The token expired, was revoked, or belongs to another service, such as Core. |
| `403` | `not_authorized` | The credential is valid, but its scopes or roles do not allow this endpoint. |
A Core token sent to a Banking endpoint gets the same `401 expired_token` as an expired token.
If a fresh token is rejected that way, check that it came from `/v1/oauth/token`, not from
`/v1/core/oauth/token`.
## Next steps [#next-steps]
* [Authentication reference](/docs/api-reference/banking/authentication) — the token, introspect, and revoke endpoints in detail.
* [Authentication standards](/docs/guides/api-standards/authentication) — header, environments, and rotation rules shared by every product.
* [Errors and retries](/docs/guides/api-standards/errors-and-retries) — the error envelope and when to retry.
# API (/docs/guides/banking/api)
Learn how to authenticate and interact with the Banking and Core APIs. These guides cover the credentials and headers you need to make authorized requests.
Authenticate your API requests using API keys
# PSE collection (/docs/guides/banking/flows/pse-collection)
**PSE (Pagos Seguros en Línea)** is Colombia's standard system for paying from a bank account on a website — the local equivalent to Pix in Brazil or ACH Direct Debit in the United States. A PSE collection is the flow your business runs when you want a customer to pay you from their bank account: you generate a payment link, the customer follows it to their bank, the bank authorizes the payment, and Mono notifies you of the result.
This flow is the core of Banking's payin capability. Use it for e-commerce checkout, invoice settlement, subscription top-ups, or any scenario where the payer initiates the payment.
This page is a flow scaffold and will be expanded with code samples per step, expected
webhooks, and common errors. Track progress in the documentation roadmap.
## Before you start [#before-you-start]
You will need:
* A Mono account with Banking enabled and an API key for the environment you are calling. See [Authentication](/docs/guides/api-standards/authentication).
* A receiving bank account configured in your Mono dashboard.
* A webhook endpoint that can receive [collection-link events](/docs/guides/banking/webhooks/collection-links).
* Familiarity with the [collection-links concept](/docs/guides/banking/concepts/collection-links).
## Sequence overview [#sequence-overview]
1. **Create a collection intent** — your backend calls Mono to generate a payment intent with the amount, currency, and a reference you control. Mono returns a redirect URL pointing to the PSE-backed payment form.
2. **Redirect the payer** — your frontend sends the customer to that URL. PSE handles bank selection and authorization.
3. **Receive the result** — Mono fires a `collection_intent_credited` webhook to your endpoint when the payment settles, or `collection_intent_failed` if it is rejected.
4. **Reconcile** — your system updates the order and credits the customer.
## Steps [#steps]
The full step-by-step (with curl, Node.js, and Python examples) is pending. Until it is published, see:
* The [collection-links concept page](/docs/guides/banking/concepts/collection-links) for the data model and lifecycle.
* The [collection-links webhook page](/docs/guides/banking/webhooks/collection-links) for the `collection_intent_credited` and `collection_intent_failed` event payloads.
## Next steps [#next-steps]
* [Sending transfers](/docs/guides/banking/flows/sending-transfers) — the payout counterpart of this flow.
* [Banking webhooks](/docs/guides/banking/webhooks) — the events that drive reconciliation.
* [Sandbox](/docs/guides/banking/sandbox) — test the flow without moving real money.
# Card issuance (/docs/guides/banking/flows/cards)
A **card** is a debit card — virtual or physical — that Mono provisions against one of your Banking accounts in partnership with Visa. Card issuance is the flow your business runs when you want to give an account holder a way to spend their balance at any Visa-enabled merchant directly from Mono, without requiring a separate transfer.
Use this flow for employee expense cards, prepaid customer cards, marketplace seller spending cards, or any case where an account holder needs a card credential tied to their Mono account.
This page is a flow scaffold. The full version will include code samples for creation,
activation, and card controls (freeze, limit updates), expected webhooks, and common errors.
## Before you start [#before-you-start]
You will need:
* A Mono account with Banking (Cards) enabled and an API key. See [Authentication](/docs/guides/api-standards/authentication).
* A Banking account already created to which the card will be linked.
* A webhook endpoint subscribed to [card transaction events](/docs/guides/core/webhooks/card-transactions) for spend activity after the card is issued.
* An [idempotency key](/docs/guides/api-standards/idempotency-keys) per card creation request to make retries safe.
## Sequence overview [#sequence-overview]
1. **Request a card** — your operator or application triggers card creation for a specific account holder and card type (virtual or physical).
2. **Call Mono** — your backend submits the card creation request with the account id, holder details, and an idempotency key.
3. **Mono provisions with the card network** — Mono requests credentials from Visa and receives a tokenized card number, expiry, and CVV.
4. **Receive the card** — Mono returns the card id and masked PAN synchronously. Virtual cards come back in `active` state and can be used immediately. Physical cards come back in `created` state and must be activated by the holder in the Mono app before they work.
5. **Deliver to the holder** — your backend surfaces the card details to the operator or account holder. For physical cards, also communicate the activation step.
## Steps [#steps]
The full step-by-step (with curl, Node.js, and Python examples for creation, PIN setting, and card controls) is pending. Until it is published, see:
* The [card transactions webhook page](/docs/guides/core/webhooks/card-transactions) for spend and authorization event payloads.
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys) for safe retries on creation requests.
## Next steps [#next-steps]
* [PSE collection](/docs/guides/banking/flows/pse-collection) — accept payments into the account that funds the card.
* [Banking webhooks](/docs/guides/banking/webhooks) — the events that track card lifecycle and spend activity.
* [Sandbox](/docs/guides/banking/sandbox) — issue a test card without activating real card network credentials.
# Flows (/docs/guides/banking/flows)
A **flow** is a complete, end-to-end use case that composes several Banking concepts together. While the [Concepts](/docs/guides/banking/concepts) section answers "what is this entity?", flows answer "how do I get money in or out using these entities?".
Flows are grouped by commercial capability:
* **Payin** — getting money into your account from a customer.
* **Payout** — sending money from your account to a beneficiary.
* **Cards** — issuing debit cards linked to a Banking account.
## Available flows [#available-flows]
Generate a payment link, redirect the payer to their bank, and reconcile the settlement
event.
Dispatch one-off or batch transfers across ACH, Transfiya, and Mono Turbo rails with state
and confirmation handling.
Provision a virtual or physical debit card against a Banking account and track its lifecycle
via webhooks.
## Next steps [#next-steps]
* [Banking concepts](/docs/guides/banking/concepts) — the entities each flow composes.
* [Banking webhooks](/docs/guides/banking/webhooks) — events you receive during a flow.
* [Sandbox](/docs/guides/banking/sandbox) — try a flow without moving real money.
# Sending transfers (/docs/guides/banking/flows/sending-transfers)
A **bank transfer** is a payout: money leaves your Mono account and lands in a beneficiary's bank account. Mono routes transfers across three rails — **ACH** for standard same-day transfers, **Transfiya** for transfers within a specific bank cluster, and **Mono Turbo** for real-time transfers to participating banks — and falls back automatically when a rail does not support a destination.
Use this flow for payroll runs, vendor settlements, marketplace seller payouts, refunds, or any case where your business pushes money out to a known bank account.
This page is a flow scaffold and will be expanded with code samples for one-off and batch
transfers, expected webhooks, common errors, and reconciliation guidance.
## Before you start [#before-you-start]
You will need:
* A Mono account with Banking enabled and an API key. See [Authentication](/docs/guides/api-standards/authentication).
* Sufficient balance in the source account to cover the transfer plus any fees.
* A webhook endpoint that can receive [bank-transfer events](/docs/guides/banking/webhooks/bank-transfers).
* Familiarity with the [transfers concept](/docs/guides/banking/concepts/transfers).
* An [idempotency key](/docs/guides/api-standards/idempotency-keys) per transfer to make retries safe.
## Sequence overview [#sequence-overview]
1. **Prepare the transfer** — collect the destination account, amount, currency, and routing preference (`turbo`, `transfiya`, or `ach`). Generate an idempotency key.
2. **Submit** — call `POST /transfers`. Mono creates the transfer in `created` state and groups it into a batch. The response confirms acceptance, not credit.
3. **Receive confirmation** — Mono fires `bank_transfer_approved` when the rail credits the destination, or `bank_transfer_rejected` if it is declined. The terminal transfer states are `approved` and `declined`.
4. **Handle fallbacks** — if the primary rail fails, Mono retries using the `fallback_routing` you specified and fires `bank_transfer_fallback_routing` before the final result.
## Steps [#steps]
The full step-by-step (one-off and batch transfers) is pending. Until it is published, see:
* The [transfers concept page](/docs/guides/banking/concepts/transfers) for routing options, fallbacks, and states.
* The [bank-transfer webhook page](/docs/guides/banking/webhooks/bank-transfers) for the event payloads.
## Next steps [#next-steps]
* [PSE collection](/docs/guides/banking/flows/pse-collection) — the payin counterpart of this flow.
* [Banking webhooks](/docs/guides/banking/webhooks) — the events that drive confirmation and fallback handling.
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys) — required for safe retries on every transfer.
# Collection use cases (Recaudo) (/docs/guides/breb-participant/use-cases/collections)
A **collection** is how your business receives money over Bre-B: you create it, share its payment key or QR, and Mono notifies you as payers pay. The same entity covers a one-off sale, a monthly plan, an open donation, or a marketplace order — what changes is the configuration. This page lists the common scenarios and the exact fields to set for each.
Read the [Collections concept](/docs/guides/breb-participant/concepts/collections) first if these terms are new. Amounts below are in **centavos** (1 COP = 100 centavos), the unit the API expects.
These recipes name only fields that exist on the create-collection request. For the complete
request and response, see [Create
collection](/docs/api-reference/breb-participant/collections/create_collections).
## Before you start [#before-you-start]
You will need:
* A registered tenant account (`tenant_account_id`) to receive the funds.
* A webhook endpoint to receive collection events — see [Webhook payloads](/docs/guides/breb-participant/webhook-payloads).
* Familiarity with the [collection lifecycle](/docs/guides/breb-participant/architecture/collection-states) (`ready → minimum_paid → paid`).
## Quick reference [#quick-reference]
| Scenario | `usage_mode` | Amount control | Sharing |
| --------------------- | -------------- | ---------------------------------- | ------------------------------- |
| Product purchase | `single_use` | fixed total | dynamic QR |
| Shopping cart | `single_use` | total max only | no QR (dynamic) |
| Subscription | `multiple_use` | per-payment = installment | none (recurring) |
| Free donation | `multiple_use` | none (open) | static QR |
| Flexible installments | `multiple_use` | total + per-payment min/max | none |
| Client invoice | `single_use` | fixed total | dynamic QR + `expected_payers` |
| Flash sale | `single_use` | fixed total | dynamic QR, short `expires_in` |
| Gift card / top-up | `multiple_use` | total cap + per-payment min/max | static QR |
| Variable debt | `multiple_use` | per-payment max, updated via PATCH | static QR |
| Marketplace split | `single_use` | fixed total | dynamic QR, split in `metadata` |
## UC1 — Product purchase (fixed price) [#uc1--product-purchase-fixed-price]
One customer pays one fixed amount once. Set the total floor and ceiling equal so the collection closes on the first successful payment.
```json
{
"usage_mode": "single_use",
"total_minimum_amount": 5000000,
"total_maximum_amount": 5000000,
"expires_in": 3600,
"metadata": { "product_id": "SKU-128", "product_name": "Wireless keyboard" }
}
```
`5000000` centavos = $50,000 COP. Share as a **dynamic QR** carrying this amount and expiry. The collection reaches `paid` on the first successful payment.
## UC2 — Shopping cart (dynamic amount) [#uc2--shopping-cart-dynamic-amount]
The total is whatever the cart sums to, computed at checkout. Set only `total_maximum_amount`; omit the minimum.
```json
{
"usage_mode": "single_use",
"total_maximum_amount": 12000000,
"metadata": { "order_id": "ORD-9931", "item_count": "4" }
}
```
Because the exact charge is decided at pay time, share the payment key rather than a fixed-amount QR.
## UC3 — Subscription / payment plan (fixed installments) [#uc3--subscription--payment-plan-fixed-installments]
A plan paid in equal installments. Cap the lifetime total and pin each payment to the installment amount.
```json
{
"usage_mode": "multiple_use",
"total_maximum_amount": 60000000,
"minimum_attempt_amount": 5000000,
"maximum_attempt_amount": 5000000
}
```
Twelve installments of $50,000 COP toward a $600,000 plan. The collection progresses `ready → minimum_paid → paid` as installments arrive; no QR, since it recurs.
## UC4 — Free donation (open-ended) [#uc4--free-donation-open-ended]
Accept any amount, any number of times, with no end. Omit both total amounts so the collection never reaches `paid` and stays `ready`.
```json
{
"usage_mode": "multiple_use",
"custom_key_value": "REFOREST",
"custom_merchant_name": "Fundación VerdeVida"
}
```
`custom_key_value` makes the key readable (e.g. `@MN{random}REFOREST`) and is **immutable** once created. Share as a **static QR** on flyers or social media.
## UC5 — Flexible installments (min/max bounds) [#uc5--flexible-installments-minmax-bounds]
The payer chooses how much to pay each time, within bounds, toward a goal with a minimum deposit.
```json
{
"usage_mode": "multiple_use",
"total_maximum_amount": 300000000,
"total_minimum_amount": 50000000,
"minimum_attempt_amount": 10000000,
"maximum_attempt_amount": 100000000
}
```
A $3,000,000 goal with a $500,000 minimum deposit; each payment between $100,000 and $1,000,000. Payments below `minimum_attempt_amount` or above `maximum_attempt_amount` are rejected; the collection reaches `minimum_paid` once the floor is met, then `paid` at the ceiling.
## UC6 — Client invoice (restricted payer) [#uc6--client-invoice-restricted-payer]
An invoice payable only by the named client. Use `expected_payers` to restrict who can pay and `reference` to carry the invoice number.
```json
{
"usage_mode": "single_use",
"total_minimum_amount": 18000000,
"total_maximum_amount": 18000000,
"expected_payers": [{ "document_type": "CC", "document_number": "1023711432" }],
"custom_merchant_name": "Distribuciones Andinas S.A.S.",
"reference": "FAC-2024-001",
"expires_in": 86400
}
```
Share as a **dynamic QR** attached to the invoice (24-hour expiry above).
## UC7 — Flash sale (short window) [#uc7--flash-sale-short-window]
A time-boxed offer. Use a very short `expires_in`; if no one pays in time, the collection moves to `discarded`.
```json
{
"usage_mode": "single_use",
"total_minimum_amount": 2500000,
"total_maximum_amount": 2500000,
"expires_in": 30
}
```
Render the **dynamic QR** with a matching countdown.
## UC8 — Gift card / top-up (capped, reusable) [#uc8--gift-card--top-up-capped-reusable]
A balance loaded over several payments up to a cap, payable by anyone holding the card.
```json
{
"usage_mode": "multiple_use",
"custom_key_value": "GIFT4MARIA",
"total_maximum_amount": 50000000,
"minimum_attempt_amount": 1000000,
"maximum_attempt_amount": 20000000
}
```
Print the **static QR** on the card. The collection reaches `paid` when the cap is filled.
## UC9 — Variable debt (dynamic cap) [#uc9--variable-debt-dynamic-cap]
A revolving balance — e.g. a credit line — where the per-payment ceiling changes over time. Keep it open (no totals) and update `maximum_attempt_amount` as the balance changes.
```json
{
"usage_mode": "multiple_use",
"custom_key_value": "CARD-7781",
"maximum_attempt_amount": 80000000
}
```
Adjust the ceiling later with [Update collection](/docs/api-reference/breb-participant/collections/update_collection) (PATCH); the change fires a `collection.updated` webhook with the previous values. The key itself is immutable.
## UC10 — Marketplace split [#uc10--marketplace-split]
One charge that your backend later splits across several recipients. Encode the split rules as **flat** `metadata` (Bre-B metadata cannot nest), then run the payouts after settlement.
```json
{
"usage_mode": "single_use",
"total_minimum_amount": 10000000,
"total_maximum_amount": 10000000,
"metadata": {
"split_1_recipient_key": "@MNVENDEDOR1",
"split_1_type": "percentage",
"split_1_value": "80",
"split_2_recipient_key": "@MNPLATFORM",
"split_2_type": "percentage",
"split_2_value": "10",
"split_3_recipient_key": "shipping@logistics.com",
"split_3_type": "fixed",
"split_3_value": "500000"
}
}
```
When the `collection.paid` webhook arrives, read the split rules from `metadata` and execute the payouts — see the post-sale split recipe in [Outgoing transfer use cases](/docs/guides/breb-participant/use-cases/outgoing-transfers).
## Next steps [#next-steps]
* [Outgoing transfer use cases](/docs/guides/breb-participant/use-cases/outgoing-transfers) — pay recipients (and complete the marketplace split).
* [Sandbox: collections](/docs/guides/breb-participant/sandbox/collections) — simulate payments against any recipe above.
* [Create collection](/docs/api-reference/breb-participant/collections/create_collections) — the full request and response schema.
# Outgoing transfer use cases (Dispersiones) (/docs/guides/breb-participant/use-cases/outgoing-transfers)
An **outgoing transfer** is how your business sends money over Bre-B: you name a recipient by their payment key (or a pre-resolved target), Mono routes the funds, and webhooks track the result. The same endpoint covers a single seller payout, a verified refund, or a batch of hundreds — what changes is the configuration. This page lists the common scenarios and the fields to set for each.
Read [Payment keys](/docs/guides/breb-participant/concepts/payment-keys) and [Targets](/docs/guides/breb-participant/concepts/targets) first if these terms are new. Amounts below are in **centavos** (1 COP = 100 centavos).
These recipes name only fields that exist on the create-transfer request. For the complete
request and response, see [Create outgoing
transfers](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers).
## Before you start [#before-you-start]
You will need:
* A funded tenant account (`tenant_account_id`) to send from.
* A webhook endpoint for transfer and target-resolution events — see [Webhook payloads](/docs/guides/breb-participant/webhook-payloads).
* Familiarity with the [outgoing-transfer lifecycle](/docs/guides/breb-participant/architecture/outgoing-transfer-states) and the [error reference](/docs/guides/breb-participant/outgoing-transfer-errors).
## How a transfer names its recipient [#how-a-transfer-names-its-recipient]
Every transfer in the `transfers` array identifies its recipient one of two ways:
* **By key** — a `query` Mono resolves for you: `{ "query": { "value": "3001234567" } }`. The transfer passes through `target_resolved` before settling.
* **By target** — a `target_id` you already resolved (see UC-OT4 below): `{ "target_id": "bbtgt_..." }`. This skips the resolution step.
Optionally add `expected_creditor` (`document_type` + `document_number`) to refuse the transfer if the resolved owner doesn't match. When you transfer by target, send it on the resolve call instead (see UC-OT4): the `target_id` you get back already points to the expected person, and the transfer carries the same `expected_creditor`.
## UC-OT1 — Seller payment (single transfer) [#uc-ot1--seller-payment-single-transfer]
Pay one seller by their key. A one-item batch is still a batch.
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"description": "Seller payout — order ORD-9931",
"transfers": [
{
"query": { "value": "@MNVENDEDOR1" },
"amount": { "amount": 8000000, "currency": "COP" },
"external_id": "payout-ORD-9931"
}
]
}
```
`8000000` centavos = $80,000 COP. The transfer walks the full state machine `created → processing → target_resolved → ... → successful`, emitting webhooks at each step. This is the payout that completes a [marketplace split](/docs/guides/breb-participant/use-cases/collections).
## UC-OT2 — Client refund (identity check) [#uc-ot2--client-refund-identity-check]
Return funds to a specific person, refusing to pay if the key now belongs to someone else. Add `expected_creditor`.
```json
{
"query": { "value": "3001234567" },
"amount": { "amount": 5000000, "currency": "COP" },
"expected_creditor": { "document_type": "CC", "document_number": "1023711432" },
"external_id": "refund-INV-552"
}
```
If the resolved owner matches, the transfer settles. If not, it fails with `target_creditor_mismatch` — see [recipient validation errors](/docs/guides/breb-participant/outgoing-transfer-errors). In sandbox, force the mismatch with the key `sandbox@key_target_creditor_mismatch.err`.
## UC-OT3 — Post-sale split (batch) [#uc-ot3--post-sale-split-batch]
Pay several recipients from one settled sale, mixing key types in a single batch. Pair this with the [marketplace split collection](/docs/guides/breb-participant/use-cases/collections): read the split rules from the `collection.paid` webhook, then send.
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"description": "Split for order ORD-9931",
"transfers": [
{
"query": { "value": "@MNVENDEDOR1" },
"amount": { "amount": 8000000, "currency": "COP" },
"external_id": "split-1-vendor"
},
{
"query": { "value": "@MNPLATFORM" },
"amount": { "amount": 1000000, "currency": "COP" },
"external_id": "split-2-platform"
},
{
"query": { "value": "shipping@logistics.com" },
"amount": { "amount": 500000, "currency": "COP" },
"external_id": "split-3-shipping"
}
]
}
```
Each transfer is tracked independently by its `external_id`; one failing does not roll back the others.
## UC-OT4 — Pre-resolution (resolve → confirm → transfer) [#uc-ot4--pre-resolution-resolve--confirm--transfer]
When you want the payer to confirm the recipient before any money moves, resolve the key first, show the resolved name, then transfer by `target_id`.
1. **Resolve** — call [Resolve target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target) with the key. If you already know who should own it, add `expected_creditor`: Mono resolves only when the holder's document matches, and otherwise answers `400` with `target_creditor_mismatch` and no `target_id`.
2. **Confirm** — show the returned holder name, institution (`participant_name`), and masked account to your user.
3. **Transfer** — create the transfer with the `target_id` from step 1 (no `query`).
Step 1 request:
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"format": "plain_key",
"value": "3001234567",
"expected_creditor": { "document_type": "CC", "document_number": "1023711432" }
}
```
Step 3 request:
```json
{
"transfers": [
{
"target_id": "bbtgt_5tgliBmzjZ6mpQPRbQjfKj",
"amount": { "amount": 12000000, "currency": "COP" },
"external_id": "confirmed-transfer-1"
}
]
}
```
Because the recipient is already resolved, the transfer skips `target_resolved` and goes straight to settlement. When the resolution carried an `expected_creditor`, the transfer copies it, so its API response and webhooks show the same `expected_creditor` as a transfer by key.
## UC-OT5 — Batch supplier payments (mixed results) [#uc-ot5--batch-supplier-payments-mixed-results]
A larger batch where some entries succeed and others are rejected. The create response returns accepted transfers and a `rejected_transfers` list; resolution failures (like a bad key) arrive later as `outgoing_transfer.failed` webhooks.
Handle both: reconcile the synchronous `rejected_transfers` (e.g. `amount_exceeds_max_limit`, duplicates) and subscribe to failure webhooks for per-transfer outcomes. See [Handling rejected transfers](/docs/guides/breb-participant/outgoing-transfer-errors).
## UC-OT6 — Every key type [#uc-ot6--every-key-type]
A transfer can target any of the five [key types](/docs/guides/breb-participant/concepts/payment-keys). Use the right `query.value` format:
| Key type | `query.value` example |
| ------------- | --------------------- |
| Cellphone | `3001234567` |
| Document | `21482961` |
| Email | `vendedor@tienda.com` |
| Alphanumeric | `@MNVENDEDOR1` |
| Merchant code | `0012345678` |
Mono resolves each to the same kind of target; the resolved data shown back differs by type.
## UC-OT7 — Error handling (sandbox) [#uc-ot7--error-handling-sandbox]
Exercise every failure path deterministically in sandbox before going live:
* **Resolution errors** — set `query.value` to a trigger key such as `sandbox@key_not_found.err`, `sandbox@key_suspended.err`, or `sandbox@key_target_creditor_mismatch.err`. The same keys work as the `value` of [Resolve target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target): with an `expected_creditor`, the mismatch key answers `400 target_creditor_mismatch` and a key that isn't a sandbox trigger resolves to the creditor you expect.
* **Settlement errors** — encode the trigger in the transfer `description` (e.g. timeout, risk-control, creditor-account-not-found).
* **Creation rejections** — malformed requests (missing amount, unknown target, amount over the limit) are rejected synchronously with no webhooks.
The full list of `state_reason` codes, their meanings, and recommended handling is in the [outgoing-transfer error reference](/docs/guides/breb-participant/outgoing-transfer-errors). Walk the triggers end to end in [Sandbox: outgoing transfers](/docs/guides/breb-participant/sandbox/outgoing-transfers).
## Next steps [#next-steps]
* [Collection use cases](/docs/guides/breb-participant/use-cases/collections) — the receiving side (and the split that feeds UC-OT3).
* [Outgoing transfer flow](/docs/guides/breb-participant/flows/outgoing-transfer) — the end-to-end sequence and state machine.
* [Sandbox: outgoing transfers](/docs/guides/breb-participant/sandbox/outgoing-transfers) — simulate any recipe above.
# Use cases (/docs/guides/breb-participant/use-cases)
Bre-B Participant gives you a small set of fields that, combined, cover most ways a business receives or sends money. A **use case** is a recommended combination of those fields for a concrete scenario — a one-off product purchase, a subscription, a donation, a supplier payout. This section turns "what do I configure for X?" into a recipe you can copy.
There is no special "subscription" or "donation" endpoint. You always create a collection (to receive) or an outgoing transfer (to send); the *configuration* you choose is what makes it behave like a subscription or a donation.
Every field used here is part of the live API. The recipes name the fields and their effect —
for the full request and response, follow the API-reference link on each page.
## The decisions that define a collection [#the-decisions-that-define-a-collection]
When you receive money (Recaudo), four choices shape the behavior:
* **How many times can it be paid?** `usage_mode` is `single_use` (accepts one successful payment, then closes) or `multiple_use` (keeps accepting payments until a cap is reached, or forever).
* **How much, in total?** `total_maximum_amount` and `total_minimum_amount` bound the lifetime of the collection. Set them equal for a fixed price; omit both to let it stay open indefinitely.
* **How much per payment?** `minimum_attempt_amount` and `maximum_attempt_amount` bound each individual payment — useful for installments and top-ups.
* **How is it shared?** A **dynamic QR** carries a fixed amount and a short expiry (one checkout). A **static QR** has no amount and can be printed or posted for many payers.
Two optional fields brand the payment: `custom_key_value` (a readable suffix on the payment key, e.g. `@MN{random}REFOREST`) and `custom_merchant_name` (the name the payer sees).
## The decisions that define an outgoing transfer [#the-decisions-that-define-an-outgoing-transfer]
When you send money (Dispersiones), the choices are about the recipient and trust:
* **Who receives it?** Provide a `query.value` (a payment key Mono resolves for you) or a pre-resolved `target_id`.
* **Do you need to confirm identity first?** `expected_creditor` makes Mono fail the transfer — or the target resolution, when you resolve first — if the resolved recipient doesn't match who you expected. Essential for refunds.
* **One or many?** A single request can carry one transfer or a batch of many, each tracked independently.
## Pick your rail [#pick-your-rail]
Product purchase, shopping cart, subscription, donation, installments, invoice, flash sale,
gift card, variable debt, marketplace split.
Seller payment, refund with identity check, batch payouts, pre-resolution, mixed-result
batches, every key type, error handling.
## Next steps [#next-steps]
* [Collections concept](/docs/guides/breb-participant/concepts/collections) — the entity these recipes configure.
* [Collection states](/docs/guides/breb-participant/architecture/collection-states) — how `ready → minimum_paid → paid` works.
* [Sandbox: collections](/docs/guides/breb-participant/sandbox/collections) — try a recipe end to end without a real bank.
# Payins (/docs/guides/core/webhooks/payins)
Every entity in payins reports its lifecycle through webhooks: 7 template events, 7 intent events, 5 payin events, and 3 settlement batch events (22 in all). Each payload carries the full resource, in the same shape the API returns it. This page places every event on the timeline; the exact payloads live in the [Payin Webhooks API reference](/docs/api-reference/core/payin-webhooks). For signature verification, retries, and money-field conventions shared by all Mono webhooks, see [Webhook conventions](/docs/guides/api-standards/webhooks).
## The happy path, in order [#the-happy-path-in-order]
For a collection through an intent, a single payment produces this sequence:
Collections without intents skip the four intent events; the template events fire once per template, not per payment.
## Template events [#template-events]
| Event | Arrives when |
| :----------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`payin_template_created`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_created) | The template is accepted and persisted. Its payment tokens are still being set up (`payment_tokens` is null). It cannot receive payments yet. |
| [`payin_template_ready`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_ready) | Token setup finished: the template can receive payments, and `payment_tokens` carries one entry per method that issues a token (for Bre-B, the key and QR). |
| [`payin_template_updated`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_updated) | Every successful update. Carries `previous_values` with the fields that changed, including a state transition caused by moving the totals, which does **not** fire the separate state events. |
| [`payin_template_minimum_paid`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_minimum_paid) | Cumulative `paid_amount` first reaches `total_minimum_amount`. The template keeps accepting payments. |
| [`payin_template_paid`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_paid) | `total_maximum_amount` is reached, or a `single_use` template received its payment. No further payins are accepted. |
| [`payin_template_discarded`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_discarded) | The template was deleted, expired, or swept for inactivity; its tokens were torn down. `prune_reason` says which. |
| [`payin_template_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_failed) | Token setup exhausted its retries. `state_reason` explains (e.g. `key_already_registered`). |
## Intent events [#intent-events]
| Event | Arrives when |
| :--------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`payin_intent_created`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_created) | The intent is accepted and persisted. Its token is still being prepared. The payer cannot pay yet. |
| [`payin_intent_ready_to_pay`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_ready_to_pay) | The intent becomes payable: `payment_tokens` and `expires_at` are set. Also re-sent when the intent re-arms after a failed attempt. |
| [`payin_intent_awaiting_payment`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_awaiting_payment) | A payin for the intent was authorized; the payment is in flight. |
| [`payin_intent_attempt_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_attempt_failed) | A payment attempt failed after authorization but the intent survives: it is back in `ready_to_pay` for a retry. The attempt itself arrives as `payin_rejected` or `payin_failed`. |
| [`payin_intent_successful`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_successful) | The intent was spent by its successful payment. Final: tokens torn down. |
| [`payin_intent_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_failed) | The intent fails for good: token registration failed, or a payment failed with no re-arm. `state_reason` explains. |
| [`payin_intent_expired`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_expired) | The window closed with nobody paying. The `external_id` is freed for a new intent. |
## Payin events [#payin-events]
| Event | Arrives when |
| :--------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`payin_created`](/docs/api-reference/core/payin-webhooks/webhook_payin_created) | A payment is first persisted, before provider confirmation. Carries who paid (`instrument.payer`) and on which key (`recipient_data`). |
| [`payin_approved_in_provider`](/docs/api-reference/core/payin-webhooks/webhook_payin_approved_in_provider) | The provider confirmed the payment. Money exists on the provider side; the subaccount is not credited yet. |
| [`payin_subaccount_credited`](/docs/api-reference/core/payin-webhooks/webhook_payin_subaccount_credited) | The subaccount was credited for `subaccount_amount`. For most integrations, the "payment complete" signal. |
| [`payin_rejected`](/docs/api-reference/core/payin-webhooks/webhook_payin_rejected) | A business, configuration, or payer condition stopped the payment. `state_reason` explains (e.g. `amount_exceeds_maximum`, `payer_not_allowed`). The payer's funds, if any, are returned. |
| [`payin_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_failed) | A system or provider failure: `lock_timeout` or `unknown`. |
## Settlement batch events [#settlement-batch-events]
| Event | Arrives when |
| :----------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------- |
| [`payin_settlement_batch_created`](/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_created) | Pending payins were grouped into a batch. `payins` lists what each contributes; the money has not moved yet. |
| [`payin_settlement_batch_processing`](/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_processing) | The delivery provider accepted the batch transfer. |
| [`payin_settlement_batch_completed`](/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_completed) | The funds reached your funding account. |
## What is not notified [#what-is-not-notified]
* A payin's brief `awaiting_payment` state has no event of its own; the intent's `payin_intent_awaiting_payment` covers that moment.
* Template state transitions caused by an **update** travel inside `payin_template_updated`, not as separate state events.
* Settlement batch `retryable` and `failed` are readable via the API but never notified: Mono is handling the delivery, and you do not need to act.
## Next steps [#next-steps]
* [Webhook conventions](/docs/guides/api-standards/webhooks): signatures, retries, and delivery rules.
* [Payin Webhooks API reference](/docs/api-reference/core/payin-webhooks): the exact payload of each event.
* [Flows](/docs/guides/core/flows/payins): the events in the context of each use case.
# Card Transactions (/docs/guides/core/webhooks/card-transactions)
When an operation involving a card transaction is done, a webhook request is generated based on the transaction type.
## Event Types [#event-types]
| Event | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `authorization_approved` | Triggered when a card transaction has been authorized. A charge will be reflected on the account's balance. |
| `authorization_declined` | Triggered when a card transaction has not been authorized. Includes a `declination_reason`. No change on the account's balance. |
| `withdrawal` | Triggered when a withdrawal has been authorized (e.g., ATM). Includes a `withdrawal_fee` detail. |
| `credit_adjustment` | Triggered when an authorization is updated by a partial refund. A refund will be reflected in the account balance. |
| `debit_adjustment` | Triggered when an authorization is updated with a new amount. A new charge is done to update the previous authorization. |
| `force_debit` | Triggered when a transaction is made without a previous authorization. No `original_transaction_id` nor `previous_values`. |
| `authorization_reversed` | Triggered when a transaction is reversed and the value of the initial charge is returned fully to the account. |
| `refund` | Triggered when a refund is made to the account. The value can be related or not to a previous authorization. |
| `reconciliation` | Triggered after final settlement when the authorized amount is adjusted to the amount actually settled by the card network. |
## Nullable Fields [#nullable-fields]
Depending on the type of the notification, the following fields could be null:
| Field | When it's null |
| ------------------------- | --------------------------------------------------------------------- |
| `withdrawal_fee` | When there was no fee (i.e., the card transaction was not in an ATM). |
| `fx_rates` | When the amount was charged in COP. |
| `declination_reason` | When the operation was successful. |
| `original_transaction_id` | When the operation does not involve a previous transaction. |
| `previous_values` | When there are no previous values to compare. |
***
## Payload Examples [#payload-examples]
### authorization\_approved [#authorization_approved]
```json
{
"event": {
"type": "card_transaction",
"data": {
"type": "authorization_approved",
"id": "ctx_5tgliBmzj6thQPRbQjfKj",
"inserted_at": "2023-06-15T10:30:00.000Z",
"state": "approved",
"card": {
"id": "car_5tgliBmlpZ6mpQPRbQjfKj",
"last_four": "1234"
},
"cardholder_id": "cah_7MkWDrqvfosB8fzHhb1Eql",
"declination_reason": null,
"original_transaction_id": null,
"merchant": {
"id": "merchant_789012345",
"name": "Nombre del Comercio",
"mcc": "string",
"amount": {
"amount": 1600,
"currency": "USD"
}
},
"amount": {
"amount": 1600,
"currency": "USD"
},
"settlement_amount": {
"amount": 1600,
"currency": "USD"
},
"withdrawal_fee": null,
"fx_rates": {
"USD": "1",
"COP": "4002,24"
},
"previous_values": null
},
"timestamp": "2023-06-15T10:35:00.000Z"
}
}
```
### authorization\_declined [#authorization_declined]
```json
{
"event": {
"type": "card_transaction",
"data": {
"type": "authorization_declined",
"id": "ctx_5tgliBmzjZ6mpQPRbQjfKj",
"inserted_at": "2023-06-15T10:30:00.000Z",
"state": "declined",
"card": {
"id": "car_5tgliBmzjZ6mpQPRbQjfKj",
"last_four": "1234"
},
"cardholder_id": "cah_7MkWaFqvfosB8fzHhb1Eql",
"declination_reason": "insufficient_funds",
"original_transaction_id": null,
"merchant": {
"id": "merchant_789012345",
"name": "Nombre del Comercio",
"mcc": "string",
"amount": {
"amount": 160,
"currency": "USD"
}
},
"amount": {
"amount": 160,
"currency": "USD"
},
"settlement_amount": {
"amount": 160,
"currency": "USD"
},
"withdrawal_fee": null,
"fx_rates": {
"USD": "1",
"COP": "4002,24"
},
"previous_values": null
},
"timestamp": "2023-06-15T10:35:00.000Z"
}
}
```
### withdrawal [#withdrawal]
```json
{
"event": {
"type": "card_transaction",
"data": {
"type": "withdrawal",
"id": "ctx_5tgliBrejZ6mpQPRbQjfKj",
"inserted_at": "2023-06-15T10:30:00.000Z",
"state": "approved",
"card": {
"id": "car_5tgliBmzjZ6mpQPRbQjfKj",
"last_four": "1234"
},
"cardholder_id": "cah_7MkAqFqvfosB8fzHhb1Eql",
"declination_reason": null,
"original_transaction_id": null,
"withdrawal_fee": {
"amount": 240000,
"currency": "COP"
},
"merchant": {
"id": "merchant_789012345",
"name": "Nombre del Comercio",
"mcc": "string",
"amount": {
"amount": 5000000,
"currency": "COP"
}
},
"amount": {
"amount": 5000000,
"currency": "COP"
},
"settlement_amount": {
"amount": 5000000,
"currency": "COP"
},
"fx_rates": null
},
"timestamp": "2023-06-15T10:35:00.000Z"
}
}
```
### credit\_adjustment [#credit_adjustment]
```json
{
"event": {
"type": "card_transaction",
"data": {
"type": "credit_adjustment",
"id": "ctx_5tgliBmzjZ6mpQSDbQjfKj",
"inserted_at": "2023-06-15T10:30:00.000Z",
"state": "approved",
"card": {
"id": "car_5tgliBmzjZ6mpQPRbQjfKj",
"last_four": "1234"
},
"cardholder_id": "cah_7MkWaFqvfusB8fzHhb1Eql",
"declination_reason": null,
"original_transaction_id": "ctx_5tgliBmzjZ9kpQPRbQjfKj",
"merchant": {
"id": "merchant_789012345",
"name": "Nombre del Comercio",
"mcc": "0000",
"amount": {
"amount": 160000,
"currency": "COP"
}
},
"amount": {
"amount": 160000,
"currency": "COP"
},
"settlement_amount": {
"amount": 160000,
"currency": "COP"
},
"withdrawal_fee": null,
"fx_rates": null,
"previous_values": {
"amount": {
"amount": 1000000,
"currency": "COP"
},
"merchant_amount": {
"amount": 1000000,
"currency": "COP"
}
}
},
"timestamp": "2023-06-15T10:35:00.000Z"
}
}
```
### debit\_adjustment [#debit_adjustment]
```json
{
"event": {
"data": {
"amount": {
"amount": 808000,
"currency": "COP"
},
"card": {
"id": "car_02v8ucNWMnt0qvOIG1Cx9Y",
"last_four": "4952"
},
"cardholder_id": "carh_02v8ucNVmEQWOhFmA8KJX4",
"declination_reason": null,
"fx_rates": null,
"id": "ctx_02vSpSMwSwIETjc7di98zU",
"inserted_at": "2024-01-16T20:06:18.397776Z",
"merchant": {
"amount": {
"amount": 200,
"currency": "USD"
},
"id": "111111111111111",
"mcc": "5045",
"name": "Computer Software"
},
"original_transaction_id": null,
"previous_values": {
"amount": {
"amount": 404000,
"currency": "COP"
},
"merchant_amount": {
"amount": 100,
"currency": "USD"
}
},
"settlement_amount": {
"amount": 100,
"currency": "USD"
},
"state": "approved",
"type": "debit_adjustment",
"withdrawal_fee": null
},
"type": "card_transaction"
},
"timestamp": "2024-01-16T20:07:31.979159Z"
}
```
### force\_debit [#force_debit]
```json
{
"event": {
"data": {
"amount": {
"amount": 404000,
"currency": "COP"
},
"card": {
"id": "car_02v8ucNWMnt0qvOIG1Cx9Y",
"last_four": "4952"
},
"cardholder_id": "carh_02v8ucNVmEQWOhFmA8KJX4",
"declination_reason": null,
"fx_rates": null,
"id": "ctx_02vSmf8pSW4HqcViMjaC6a",
"inserted_at": "2024-01-16T18:12:27.393051Z",
"merchant": {
"amount": {
"amount": 100,
"currency": "USD"
},
"id": "111111111111111",
"mcc": "5045",
"name": "Computer Software"
},
"original_transaction_id": null,
"previous_values": null,
"settlement_amount": {
"amount": 100,
"currency": "USD"
},
"state": "approved",
"type": "force_debit",
"withdrawal_fee": null
},
"type": "card_transaction"
},
"timestamp": "2024-01-16T18:12:40.364194Z"
}
```
### authorization\_reversed [#authorization_reversed]
```json
{
"event": {
"data": {
"amount": {
"amount": 400224,
"currency": "COP"
},
"card": {
"id": "car_02v8ucNWMnt0qvOIG1Cx9Y",
"last_four": "4952"
},
"cardholder_id": "carh_02v8ucNVmEQWOhFmA8KJX4",
"declination_reason": null,
"fx_rates": {
"USD": "1",
"COP": "4002,24"
},
"id": "ctx_02vSq0LFdgOjYzv7vtfbr9",
"inserted_at": "2024-01-16T20:28:38.067403Z",
"merchant": {
"amount": {
"amount": 100,
"currency": "USD"
},
"id": "111111111111111",
"mcc": "5045",
"name": "Computer Software"
},
"original_transaction_id": null,
"previous_values": null,
"settlement_amount": {
"amount": 100,
"currency": "USD"
},
"state": "reversed",
"type": "authorization_reversed",
"withdrawal_fee": null
},
"type": "card_transaction"
},
"timestamp": "2024-01-16T20:29:35.474393Z"
}
```
### refund [#refund]
```json
{
"event": {
"data": {
"amount": {
"amount": 3500000,
"currency": "COP"
},
"card": {
"id": "car_02v8ucNWMnt0qvOIG1Cx9Y",
"last_four": "4952"
},
"cardholder_id": "carh_02v8ucNVmEQWOhFmA8KJX4",
"declination_reason": null,
"fx_rates": null,
"id": "ctx_02vSq0LFdgOjYzv7vtfbr9",
"inserted_at": "2024-01-16T20:28:38.067403Z",
"merchant": {
"amount": {
"amount": 3500000,
"currency": "COP"
},
"id": "111111111111111",
"mcc": "5045",
"name": "Computer Software"
},
"original_transaction_id": null,
"previous_values": null,
"settlement_amount": {
"amount": 2715000,
"currency": "COP"
},
"state": "approved",
"type": "refund",
"withdrawal_fee": null
},
"type": "card_transaction"
},
"timestamp": "2024-01-16T20:29:35.474393Z"
}
```
# Ledger account transactions (/docs/guides/core/webhooks/ledger-account-transactions)
When a new ledger account transaction is created a webhook event will be sent to a specified URL with the transaction details and its origin context. This allows clients to receive real-time updates on ledger movements within their subaccounts.
## Event types [#event-types]
1. `ledger_account_transaction_created`: This event is triggered when a new ledger account transaction is recorded for a subaccount. It includes the transaction details and the origin entity that caused the transaction.
## Account [#account]
`account_id` is the ID of the subaccount the transaction was recorded on, in the same format as `id` on the ledger account endpoints. It is always present, so a consumer can attribute the event to an account without reading `details`.
## Details by origin [#details-by-origin]
The `details` field contains the origin entity that caused the ledger transaction. Its structure varies depending on the `origin` value:
| Origin | Description |
| -------------------------- | ------------------------------------------------------------------------------ |
| `subaccount_operation` | A subaccount operation (topup, withdrawal, account-to-account transfer, etc.). |
| `payout` | A payout to an external bank account. |
| `card_transaction` | A card transaction (purchase, ATM withdrawal, etc.). |
| `card_presentment_payment` | Final settlement of an international card transaction. |
| `fee` | A fee charge. |
| `synced_bank_transaction` | A synced bank transaction from an external bank. |
When the origin is unknown, `details` will be `null`.
## Nullable fields [#nullable-fields]
The following fields can be null depending on the transaction:
* `reverts_id`: null when the transaction does not revert another transaction.
* `reverted_by_id`: null when the transaction has not been reverted.
* `details`: null when the origin entity is unknown or not available.
## Payload example [#payload-example]
### Subaccount operation (credit): [#subaccount-operation-credit]
```json
{
"event": {
"data": {
"id": "ltx_5tgliBmzjZ6mpQPRbQjfKj",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "credit",
"origin": "subaccount_operation",
"id_in_origin": "sao_3RJ9CbZLYq7r1MxH8vAm7Y",
"amount": {
"value": "150000000",
"currency": "COP"
},
"transaction_at": "2025-01-15T14:30:00.000000Z",
"origin_transaction_at": "2025-01-15T14:29:55.000000Z",
"description": "Transfer to savings account",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "sao_3RJ9CbZLYq7r1MxH8vAm7Y",
"state": "successful",
"state_reason": null,
"operation_type": "topup",
"origin_account_id": "lac_7MkWDrqvfosB8fzHhb1Eql",
"destination_account_id": null,
"amount": {
"value": "150000000",
"currency": "COP"
},
"source_amount": {
"value": "150000000",
"currency": "COP"
},
"target_amount": null,
"fx_rate": null,
"external_id": "ext-transfer-12345",
"description": "Transfer to savings account",
"reference": "REF-2025-001",
"metadata": {},
"calculated_fees": []
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-15T14:30:05.000000Z"
}
```
### Payout (debit): [#payout-debit]
```json
{
"event": {
"data": {
"id": "ltx_2PVWOx9dZKJMBZw7opjrrs",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "debit",
"origin": "payout",
"id_in_origin": "pay_5NYRqKnE17nE0MswylZzq1",
"amount": {
"value": "50000000",
"currency": "COP"
},
"transaction_at": "2025-01-15T16:00:00.000000Z",
"origin_transaction_at": "2025-01-15T15:59:50.000000Z",
"description": "Payout to external bank",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "pay_5NYRqKnE17nE0MswylZzq1",
"account_id": "lac_7MkWDrqvfosB8fzHhb1Eql",
"external_id": "ext-payout-789",
"state": "approved",
"state_reason": null,
"method": "co_ach",
"source_amount": {
"value": "50000000",
"currency": "COP"
},
"target_amount": {
"value": "50000000",
"currency": "COP"
},
"description": "Payout to external bank",
"reference": "PAY-2025-001",
"metadata": {},
"calculated_fees": [],
"inserted_at": "2025-01-15T15:59:50.000000Z",
"updated_at": "2025-01-15T16:00:00.000000Z"
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-15T16:00:05.000000Z"
}
```
### Card transaction (debit): [#card-transaction-debit]
```json
{
"event": {
"data": {
"id": "ltx_4kRtaBmzjZ6mpQPRbQjfKj",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "debit",
"origin": "card_transaction",
"id_in_origin": "ctx_6WnYqKnE17nE0MswylZzq1",
"amount": {
"value": "25000000",
"currency": "COP"
},
"transaction_at": "2025-01-15T18:00:00.000000Z",
"origin_transaction_at": "2025-01-15T17:59:45.000000Z",
"description": "Card purchase",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "ctx_6WnYqKnE17nE0MswylZzq1",
"type": "authorization_approved",
"state": "approved",
"amount": {
"value": "25000000",
"currency": "COP"
},
"merchant_amount": {
"value": "6250",
"currency": "USD"
},
"declination_reason": null,
"transaction_at": "2025-01-15T17:59:45.000000Z",
"card_id": "car_5tgliBmzjZ6mpQPRbQjfKj",
"merchant_name": "Almacenes Exito",
"merchant": {
"id": "merchant_789012345",
"name": "Almacenes Exito",
"mcc": "5411",
"country": "COL"
},
"card": {
"id": "car_5tgliBmzjZ6mpQPRbQjfKj",
"last_four": "4952"
}
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-15T18:00:05.000000Z"
}
```
### Card presentment payment (debit): [#card-presentment-payment-debit]
```json
{
"event": {
"data": {
"id": "ltx_8rTsaBmzjZ6mpQPRbQjfKj",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "debit",
"origin": "card_presentment_payment",
"id_in_origin": "cpp_3WnYqKnE17nE0MswylZzq1",
"amount": {
"value": "25000000",
"currency": "COP"
},
"transaction_at": "2025-01-16T10:00:00.000000Z",
"origin_transaction_at": "2025-01-15T18:00:00.000000Z",
"description": "Card presentment settlement",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "cpp_3WnYqKnE17nE0MswylZzq1",
"fx_rate": "4000.00",
"paid_at": "2025-01-16T10:00:00.000000Z"
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-16T10:00:05.000000Z"
}
```
### Fee (debit): [#fee-debit]
```json
{
"event": {
"data": {
"id": "ltx_7pQWOx9dZKJMBZw7opjrrs",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "debit",
"origin": "fee",
"id_in_origin": "fee_2DpSchlriwoCuyGMOoIuwp",
"amount": {
"value": "1500000",
"currency": "COP"
},
"transaction_at": "2025-01-15T18:00:01.000000Z",
"origin_transaction_at": "2025-01-15T18:00:00.000000Z",
"description": "Transaction fee",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "fee_2DpSchlriwoCuyGMOoIuwp",
"type": "transaction",
"amount": {
"value": "1500000",
"currency": "COP"
},
"processing_type": "card",
"processing_subtype": "authorization",
"tx_description": "Transaction fee",
"state": "successful",
"transaction_at": "2025-01-15T18:00:00.000000Z",
"origin": "card_transaction",
"id_in_origin": "ctx_6WnYqKnE17nE0MswylZzq1"
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-15T18:00:05.000000Z"
}
```
### Synced bank transaction (credit): [#synced-bank-transaction-credit]
```json
{
"event": {
"data": {
"id": "ltx_6kRtaBmzjZ6mpQPRbQjfKj",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "credit",
"origin": "synced_bank_transaction",
"id_in_origin": "sbt_4NYRqKnE17nE0MswylZzq1",
"amount": {
"value": "200000000",
"currency": "COP"
},
"transaction_at": "2025-01-15T20:00:00.000000Z",
"origin_transaction_at": "2025-01-15T19:55:00.000000Z",
"description": "External bank deposit",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "sbt_4NYRqKnE17nE0MswylZzq1"
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-15T20:00:05.000000Z"
}
```
# Webhooks (/docs/guides/core/webhooks)
Webhooks notify your system the moment a Core event occurs — a card transaction is authorized, a ledger entry settles, a payment is collected, a payout completes. For the shared mechanics that govern all Mono webhooks (signature verification, retry policy, money fields, URL restrictions), see [Webhook conventions](/docs/guides/api-standards/webhooks).
## Webhook Events [#webhook-events]
Authorizations, withdrawals, and refunds
Ledger account transaction events
The 22 events of templates, intents, payins, and settlement batches
## Next steps [#next-steps]
* [Webhook conventions](/docs/guides/api-standards/webhooks) — signature verification, retry policy, and money fields.
* [Dashboard](/docs/guides/core/dashboard) — configure webhook endpoints and inspect recent deliveries.
# Spending Control (/docs/guides/core/concepts/spending-controls)
Spending control is a feature that will help you create cards that work according to your needs. This feature allows you to control the monthly, weekly, or daily spending on the card, and allows you to control the specific types of permitted transactions. You can create spending controls for both physical and virtual cards.
You can create spending controls with multiple rules and then associate them with your cards. These rules don't have to be applied to all of your cards though a spending control, and they can be changed whenever you want. For example, you could create a spending control that blocks your cards from making ATM withdrawals; this could be applied to cards designated for purchase purposes.
## Spending Controls Rules [#spending-controls-rules]
Spending controls are defined by a set of rules that provide a structured approach to managing your card expenses.
### Velocity [#velocity]
This rule is designed to limit expenditure within a predefined time frame, offering the ability to set maximum spending limits on a daily, weekly, or monthly basis. Setting a velocity rule can restrict your spending to a specified amount within your defined time frame, helping you gain better control over your company's or employees' finances.
### Card Usage [#card-usage]
This rule restricts card transactions to specific types, enhancing the security and customization of your spending control. For example, you can limit your card usage to exclude online transactions, providing an additional layer of fraud prevention.
The types of card usage that can be controlled are:
* Online purchase
* Physical purchase
* ATM withdrawal
### Velocity Withdrawal [#velocity-withdrawal]
Similar to the velocity rule, this rule focuses on limiting the amount that can be withdrawn from ATMs within a set period. By enforcing a withdrawal limit, you gain control over cash outflows, safeguarding against excessive withdrawals and managing cash flow efficiently.
### Merchant Category Code [#merchant-category-code]
This rule focuses on limiting the category of merchants where your cards can be used. For example, you can restrict the cards to only be used at merchants related to a specific sector, like gas stations, thereby gaining more control over your finances.
### Merchant ID [#merchant-id]
This rule focuses on limiting one or different specific merchants where your cards can be used. These limits are blockers or allowers, so for example you can allow in your cards only Facebook Ads, or let your clients use every merchant but Facebook Ads. In order to get the merchant ids that you need for your new rule, you can contact our integration team.
# Payouts (/docs/guides/core/concepts/payouts)
A **payout** is a movement of funds out of a Core ledger account. The destination can be another ledger account inside Core (an internal transfer), or an external Colombian bank account (in which case Core hands the rail-level execution to Banking). Either way, the payout produces a balanced pair of ledger entries: a debit on the source account and a credit on the destination — the same double-entry pattern every Core movement follows.
Use payouts whenever your product needs to push money out of a balance — withdrawals, marketplace seller settlements, payroll, refunds, or treasury sweeps.
## What it is [#what-it-is]
A payout has a source ledger account, a destination, an amount, a currency, and a state. The state machine is:
```
pending → processing → settled
└→ failed
```
The exact transitions and terminal states will be documented in the full version of this page.
## Internal vs. external payouts [#internal-vs-external-payouts]
| Type | Destination | Rail |
| :------- | :-------------------------- | :------------------------------------------------ |
| Internal | Another Core ledger account | Same-tenant ledger entry — instant. |
| External | Colombian bank account | Banking handles the rail (Turbo, ACH, Transfiya). |
External payouts inherit Banking's routing, fallback, and confirmation behavior.
## Fees [#fees]
A payout to a bank account can carry two fees, both priced by your [fee policies](/docs/guides/core/concepts/fees/policies) and returned in the payout's `calculated_fees`:
| Fee type | What it is |
| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------- |
| `bank_transfer` | Your charge for the transfer — a fixed amount, a percentage, or both. |
| `bank_transfer_gmf` | The pass-through of the GMF — Colombia's tax on financial movements, 0.4% of the amount (the "4 × 1000") — when it applies to the payout. |
The fees come out of the source subaccount together with the payout: if you fix the amount the destination must receive, the source pays that amount plus the fees; if you fix the source amount, the destination receives it minus the fees. Set a general price in your base policies and override it whenever you want to charge differently per program, per account holder or for a single account.
The same two fee types exist on Mono's side: for each payout, Mono charges you its own `bank_transfer` and `bank_transfer_gmf` on your accounts, as agreed in your commercial terms. The [fees listing](/docs/api-reference/core/fees/list_fees) shows both sides, told apart by `owner`. See [Fees](/docs/guides/core/concepts/fees).
## Data model [#data-model]
The full schema lives in the [Core Payouts API reference](/docs/api-reference/core/payouts).
## Webhook events [#webhook-events]
Payouts emit events on every state transition. See [Core webhooks](/docs/guides/core/webhooks).
## Next steps [#next-steps]
* [Ledger](/docs/guides/core/concepts/ledger) — the accounts payouts move money between.
* [Banking transfers](/docs/guides/banking/concepts/transfers) — the rail-level mechanics for external destinations.
* [Payout disbursement flow](/docs/guides/core/flows/payout-disbursement) — end-to-end use case.
* [Fee policies](/docs/guides/core/concepts/fees/policies) — how to price the fees of your payouts.
# Ledger and Cards (/docs/guides/core/concepts/ledger)
Ledger is a Mono product that allows you to create and manage virtual balances in multiple currencies, as well as associate them with different payment methods such as cards and transfers. Additionally, Ledger Mono provides you with the flexibility to adapt your financial operations according to the specific needs of your business or products. From launching digital wallets to issuing branded cards, Ledger Mono provides you with the necessary tools to manage your financial transactions efficiently and securely.
Mono Ledger is only available via API.
To complete your integration, you need to manage Ledger actors:
## Tenant Account [#tenant-account]
The tenant account serves as your primary account, responsible for managing all transactions to your clients' accounts (subaccounts). Additionally, it emulates the balance that you hold in your real-money account. Your tenant account supports different currencies, which will be set according to your requirements.
We will create your tenant account, and using the tenant configuration endpoint, you can look up your tenant configuration, which includes the supported currencies and your card configuration groups.
## Account Holders [#account-holders]
The account holders represent your users, the ones that you want to be created in Mono Ledger. Each individual must be registered as an account holder, granting them the ability to possess one or multiple balances, cards, and conduct transfers between accounts.
To create account holders, you can use the account holder endpoint, and you will need to define the following aspects:
**Card Holder Personal Information:**
* Country
* Document Number and Type
* First and Last Name
* Person Type
* Phone Number
* Address
* Email
## Accounts [#accounts]
In these accounts, you will be able to hold and manage your cardholders' virtual money. Each account holder can have one or more accounts, but these accounts can have only one currency. These are not real bank accounts; they are virtual accounts where you will be able to deposit virtual money and review transactions.
To create an account, you will need to use the account endpoint and define the following aspects:
* Account Holder ID
* Currency Code
* Name you want to give to your account
## Cards [#cards]
You can create and link virtual cards to each one of the accounts, and they can be used for services like Facebook Ads, Google Workplace, among others. These cards can be customised with your own design, or they can have the classic Mono design.
Cards in Mono Ledger are debit cards, that means that they will work with the money of the accounts they are linked to, they will always be linked to this account and will use the money available there.
You can use the card endpoint to create Ledger virtual cards, and you will need to define the following aspects:
* Account id you want your card to be linked to.
* Card Holder Personal Information.
* Card Configuration Group
* Card Nickname.
## Bank Transfers [#bank-transfers]
With Mono Ledger Colombia, you will be able to make bank transfers from your ledger accounts, to any account in the Colombian financial system. They could be via Turbo transfer or normal ACH transfers.

## How does your Ledger work? [#how-does-your-ledger-work]
We will create your tenant account and give you access to your API Keys. In order to create the account you will need to define the following aspects:
* Currencies you want your account to have.
* Tenant Fees for national and international purchases, this is the percentage you'll charge for each purchase made with the cards linked to your accounts.
At the same time, we will create you an account for your real money, your virtual money account will act as a mirror to that account, and will have available the same amount of money.
## Virtual Money vs Real Money [#virtual-money-vs-real-money]
In order to use your ledger, you will need to understand the difference between real and virtual money:
### Real Money [#real-money]
Money you will have in your real money account, the account that you are going to fund. Money will come out from this account only when a real transaction is made, for example a card purchase.
### Virtual Money [#virtual-money]
Money you use to make topups and withdrawals to your ledger accounts, there is no movement of real money when you make one of these operations.
The money in your Ledger is virtual money, that means that all the topups you make to ledger accounts, or the transfers between ledger accounts will not make real transactions from your real money account. So, when will this money be discounted? The moment one of your account holders makes a transaction outside your ledger, for example: a card purchase, or a bank transfer outside Mono. This is a great thing for you, because you will be able to study the way your users are spending their money, and based on that charge your real account, it does not need to be 1:1 with the money you put in the ledger accounts.
# Cards (/docs/guides/core/concepts/cards)
Cards are a payment method that allows you to make payments for products and services. Mono's cards are in partnership with Visa and they can be debit or prepaid.
## Types of Cards [#types-of-cards]
### Debit [#debit]
These cards work with the balance associated with your Mono account. You don't need to reload the card beforehand; you just need to have money in your account.
### Prepaid [#prepaid]
These cards work with the balance you decide to credit. If you don't put money on the card, it won't be activated. To load or withdraw money from the card, you should use the topup/withdrawal cards endpoint.
## Card Presentation [#card-presentation]
### Physical Cards [#physical-cards]
These can be used to make payments in physical stores, withdraw money from ATMs, and even make online purchases. Our physical cards can include your logo or have the classic Mono design.
#### How can you obtain them? [#how-can-you-obtain-them]
You can request physical cards in batches or individually. Request your physical cards from the Mono Web App by going to the Cards > Physical Card section. Contact our sales team to request cards in batches.
#### How do I activate a physical card? [#how-do-i-activate-a-physical-card]
Once you receive the cards, log in to the Mono Web App, go to the Cards section, and you will see the message: `You have 1 card on the way. Has it arrived? Activate it now`. There, you need to enter the card alias, the desired card limit, and a PIN.
### Virtual Cards [#virtual-cards]
These are available immediately and can be used for purchases and payments on digital platforms. With a CVV, they are perfect for paying for services like Facebook, Google, Slack, advertising, among others. Our virtual cards can be customized with the design you request or have the classic Mono design. Similar to physical cards, we guide you through the process to ensure everything meets your preferences.
#### How can you obtain them? [#how-can-you-obtain-them-1]
You can request virtual cards from the Mono web application or via API.
#### Creating a card via API [#creating-a-card-via-api]
After the authentication process, you can use the create card endpoint to generate the virtual cards you need. To generate one (or multiple) virtual cards in Mono via API, you need to define the following aspects:
* Account ID to which the card will be associated (you can find your account ID in the accounts endpoint).
* Cardholder information.
* Initial amount if the card is prepaid.
* Card alias and limit.
* Cards created in Mono are in an active state at the moment of creation.
## Card States [#card-states]
Your Mono cards can be in one of the following states:
| State | Description |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `created` | This state only applies to physical cards. The state your card is in before being activated in the Mono app. If your card is in this state, it won't work at any store. |
| `active` | This is the only state in which your card will work for payments. |
| `frozen` | In this state, your card is "suspended" for normal operation. You can re-activate it via API using the endpoint or from your Mono app. |
| `canceled` | Once you cancel a card, you can't use or activate it again. Use it when you no longer want to use this card. |
# Programs (/docs/guides/core/concepts/program)
A **program** groups accounts that operate under the same configuration: which currencies they can hold, which features or services are enabled for them (payins, payouts, transfers between subaccounts, cards), which payment methods each one can use, and which funding account they settle with. Every account belongs to a program, so the program is the level at which you shape how a whole set of accounts behaves.
Think of a program as a product line inside your organization. A wallet in COP for retail users, a USD treasury for corporate clients and a premium plan with different pricing can each be a program of their own — with their own rules and fees — under a single Mono integration.
Programs are set up with Mono when you onboard, or when you need a new one — there is no
self-service endpoint to create or edit them. What you do configure yourself, per program, is
pricing: see [Fee policies](/docs/guides/core/concepts/fees/policies).
## What a program defines [#what-a-program-defines]
| Setting | What it decides |
| :------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Currencies** | The currencies the program's accounts can hold. Each account has exactly one currency, chosen from this list. |
| **Enabled features or services** | Which Core features or services the accounts can use: payins, payouts, transfers between subaccounts (with or without currency conversion) and cards. |
| **Methods** | Within each feature or service, the payment methods available — which payout rails, which payin methods — and their settings. |
| **Funding account** | The real bank account the program settles with: where the money collected by payins is delivered and where the money of payouts comes from. |
| **Default** | One of your programs is the default one: accounts created without a program go there. |
## Programs and accounts [#programs-and-accounts]
When you create a ledger account you can pass its `program_id` (a `pro_` identifier). If you leave it empty, the account joins your default program. The account inherits everything the program defines: it cannot hold a currency the program does not support, nor use a feature or service the program has not enabled.
Account holders are not tied to a single program — the same holder can have accounts in different programs.
## Why split your operation into programs [#why-split-your-operation-into-programs]
Use several programs whenever two groups of accounts should behave differently:
* **By currency** — a COP program and a USD program, each settling with its own funding account.
* **By product or plan** — a free plan and a premium plan with different fees.
* **By client or segment** — a program per enterprise client, or per region, to keep their configuration and pricing apart.
Programs are also one of the levels at which fee policies apply: a policy targeting a program prices every account in it, unless a more specific policy — for an account holder or for a single account — overrides it. See [who a policy applies to](/docs/guides/core/concepts/fees/policies#who-a-policy-applies-to).
## Where programs show up in the API [#where-programs-show-up-in-the-api]
* `program_id` when [creating a ledger account](/docs/api-reference/core/ledger/create_ledger_account) — pick the program, or omit it for the default one.
* `target_type: program` and `target_id` on a [fee policy](/docs/api-reference/core/fees), to price every account of a program at once.
* Your [tenant configuration](/docs/api-reference/core/ledger/get_tenant_config), which lists the currencies your programs support.
## Next steps [#next-steps]
* [Fees](/docs/guides/core/concepts/fees) — what you charge your users, and what Mono charges you.
* [Fee policies](/docs/guides/core/concepts/fees/policies) — pricing at the global, program, account holder or account level.
* [Ledger](/docs/guides/core/concepts/ledger) — the accounts a program groups.
# Concepts (/docs/guides/core/concepts)
Core's API is built around a handful of entities. Each one has its own data model and lifecycle, and most have their own webhooks. Understanding them in isolation is the prerequisite for combining them in a [flow](/docs/guides/core/flows).
Collecting money into subaccounts is a whole sub-area of its own — [Payins](/docs/guides/core/concepts/payins) — nested in this section with its own concept pages and methods; its flows and webhooks live in Core's [Flows](/docs/guides/core/flows/payins) and [Webhooks](/docs/guides/core/webhooks/payins) sections.
Double-entry bookkeeping for accounts, transactions, holds, and settlements.
Group accounts under one configuration — currencies, features or services, methods, funding
account.
What you charge your users and what Mono charges you — and the policies that set the price.
Money in — templates, intents, payins, and settlement batches.
Money out — to internal accounts or external destinations.
Virtual and physical cards that spend against ledger accounts.
Real-time rules that limit what a card can do.
Reference rates and conversion mechanics for multi-currency ledgers.
# FX rates (/docs/guides/core/concepts/fx-rates)
An **FX rate** is the exchange rate Core uses to convert between currencies inside the ledger. If your product holds balances in more than one currency — for example, COP and USD — every cross-currency operation needs a rate to compute the equivalent amount in the destination currency. Core publishes reference rates at a fixed cadence and applies them to internal conversions on your ledger.
You will care about FX rates if you build a multi-currency wallet, a treasury product, or any case where money moves between accounts that use different ISO 4217 currencies.
## What it is [#what-it-is]
An FX rate is a tuple `(base_currency, quote_currency, rate, valid_from, valid_until)`. Core stores the active rates and applies them automatically when you perform a cross-currency ledger operation.
## When it is applied [#when-it-is-applied]
* A cross-currency ledger transaction (e.g., debit USD account, credit COP account).
* A cross-currency payout.
* A cross-currency authorization on a card spending against an account in a different currency than the merchant.
## Rounding and fees [#rounding-and-fees]
Converted amounts are rounded to the decimals of each currency. On transfers between subaccounts, your spread over the reference rate and a fixed conversion charge are fees you price with your fee policies — see [Transfers between subaccounts](/docs/guides/core/flows/account-to-account-transfers) and [Fee policies](/docs/guides/core/concepts/fees/policies).
## Data model [#data-model]
The full schema lives in the [Core FX rates API reference](/docs/api-reference/core).
## Next steps [#next-steps]
* [Ledger](/docs/guides/core/concepts/ledger)
* [Payouts](/docs/guides/core/concepts/payouts)
# Outgoing transfer (/docs/guides/breb-participant/flows/outgoing-transfer)
An **outgoing transfer** is a Bre-B payout: funds leave your tenant account at Mono and arrive at a destination identified by a payment key — an alias such as a phone number, document ID, email, an alphanumeric Bre-B identifier, or a merchant code. Unlike a card or ACH transfer, Bre-B transfers settle in real time, 24/7, and the destination is resolved at runtime against the central directory.
Use this flow when your business pushes money to an end user — refunds, payroll over Bre-B, marketplace seller payouts, treasury sweeps to another participant, or customer withdrawals.
This page is a flow scaffold. The full version will include code samples for each step and
reconciliation patterns. The sequence, lifecycle, and error catalog are already documented in
detail — see below and the linked pages.
## Before you start [#before-you-start]
You will need:
* A Bre-B participant account with Mono and OAuth client credentials. See [Authentication](/docs/guides/api-standards/authentication).
* A tenant account with sufficient balance to cover the transfer.
* The appropriate scopes: `target_resolutions` for a known key,
`target_discoveries` when the key is unknown, and `outgoing_transfers` to send
the payment.
* A webhook endpoint subscribed to [outgoing-transfer events](/docs/api-reference/breb-participant/outgoing-transfer-webhooks) and [target-resolution events](/docs/api-reference/breb-participant/target-resolution-webhooks), with [signature verification](/docs/guides/breb-participant/webhook-signatures) in place.
* An [idempotency key](/docs/guides/api-standards/idempotency-keys) per transfer.
## Sequence overview [#sequence-overview]
1. **Obtain the target** — if you know the exact payment key, [resolve
it](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target).
If you know the beneficiary and account but not the key, [discover the
target](/docs/guides/breb-participant/flows/target-discovery). Both paths
return the holder and destination data for confirmation. If you already know
who should own the key, send `expected_creditor` when resolving: the request
fails with `target_creditor_mismatch` instead of returning a target that
belongs to someone else.
2. **Confirm with the payer** — show the resolved name to the user and obtain explicit confirmation before dispatching.
3. **Dispatch the transfer** — call the outgoing-transfer endpoint with the resolved target, amount, currency, and idempotency key.
4. **Track the lifecycle** — the transfer moves through pending, sent, accepted, settled, or rejected states. Webhooks fire on every transition.
5. **Reconcile** — on settlement, credit the operation in your books; on rejection, surface the failure reason to the user using the [error catalog](/docs/guides/breb-participant/outgoing-transfer-errors).
## Steps [#steps]
The full step-by-step (with curl, Node.js, and Python samples) is pending. Until it is published, see:
* [Outgoing transfer states](/docs/guides/breb-participant/architecture/outgoing-transfer-states) — the complete state machine, transitions, and terminal states.
* [Target discovery](/docs/guides/breb-participant/flows/target-discovery) — find a key from beneficiary and account data before sending.
* [Outgoing transfer errors](/docs/guides/breb-participant/outgoing-transfer-errors) — rejection reasons and recommended handling.
* [Webhook payloads](/docs/guides/breb-participant/webhook-payloads) — the envelope and event schemas.
* [Webhook signatures](/docs/guides/breb-participant/webhook-signatures) — HMAC-SHA256 verification.
* [Sandbox: outgoing transfers](/docs/guides/breb-participant/sandbox/outgoing-transfers) — simulate the full lifecycle without real money.
* [Sandbox: target discovery](/docs/guides/breb-participant/sandbox/target-discovery) — obtain deterministic single-use targets before sending.
## Next steps [#next-steps]
* [QR collection](/docs/guides/breb-participant/flows/qr-collection) — the payin counterpart of this flow.
* [Outgoing transfer states](/docs/guides/breb-participant/architecture/outgoing-transfer-states) — the lifecycle every outgoing transfer follows.
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys) — required for safe retries on every dispatch.
# Target discovery (/docs/guides/breb-participant/flows/target-discovery)
Use target discovery when you know the beneficiary's identity and destination
account but do not know their exact Bre-B key. Mono tests a bounded set of keys
derived from the data you provide and returns a usable target only when it can
confirm the same person, bank, and account.
If you already know the exact key, call [Resolve
target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target) instead.
Discovery is for finding a key from beneficiary data, not for listing every key a person owns.
Use the deterministic `101`–`104` document suffixes to exercise exact, alternative,
cached-empty, and unavailable outcomes. See [target discovery in
sandbox](/docs/guides/breb-participant/sandbox/target-discovery).
## Before you start [#before-you-start]
* Authenticate with OAuth 2.0 client credentials.
* Confirm that Mono has enabled target discovery for your tenant.
* Use `target_discoveries` for `POST /api/v1/targets/discover`.
* Use either `target_discoveries` or `target_discoveries:readonly` for the bank
catalog GET endpoints.
* Have the `tenant_account_id` that will originate the outgoing transfer. It
must belong to the authenticated tenant.
See [Bre-B Participant authentication](/docs/guides/breb-participant/authentication)
for the token flow.
## 1. Select the canonical bank code [#1-select-the-canonical-bank-code]
Discovery accepts a three-digit Colombian ACH code in `account.bank_code`. Do
not derive that code from a bank name yourself. Search the catalog first:
```http
GET /api/v1/banks?q=bancolombia&limit=5
```
An exact ACH code, canonical name, or alias produces a confident `match` and no
suggestions:
```json
{
"catalog_version": 1,
"match": {
"ach_code": "007",
"name": "BANCOLOMBIA"
},
"data": []
}
```
A partial or approximate query produces suggestions instead:
```http
GET /api/v1/banks?q=davi&limit=5
```
```json
{
"catalog_version": 1,
"match": null,
"data": [
{ "ach_code": "051", "name": "DAVIVIENDA" },
{ "ach_code": "551", "name": "DAVIPLATA" }
]
}
```
Only a non-null `match` is safe to select automatically. Every item in `data`
is a suggestion—even when there is only one—so show the choices to the payer
and use the `ach_code` they select. A search with no results is still `200`,
with `match: null` and `data: []`.
You can also list the entire catalog or look up an exact code. See [List
banks](/docs/api-reference/breb-participant/target-discovery/list_banks) and
[Get bank](/docs/api-reference/breb-participant/target-discovery/get_bank).
## 2. Discover the target [#2-discover-the-target]
Send the beneficiary data you already know. Names, phone, and email are
optional hints; identity, bank, and account are required.
```http
POST /api/v1/targets/discover
Content-Type: application/json
Authorization: Bearer
```
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"person": {
"document_type": "CC",
"document_number": "1192793827",
"first_name": "Diego",
"middle_name": null,
"first_surname": "Sánchez",
"second_surname": null,
"phone": "+573104838791",
"email": "diego@example.com"
},
"account": {
"bank_code": "013",
"number": "0013123456789",
"type": "savings_account"
}
}
```
See [Discover target](/docs/api-reference/breb-participant/target-discovery/discover_target)
for every field and enum.
## 3. Handle the result [#3-handle-the-result]
Both normal outcomes return `200`. Check `state`; do not treat every `200` as a
discovered target.
| `state` | Meaning |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `discovered` | `target` and `resolution_id` are present. The target matches the requested person, bank, and account. |
| `not_discovered` | `target` and `resolution_id` are `null`. `state_reason` is `no_exact_target`; this does not mean the person has no other key. |
### Exact target found [#exact-target-found]
```json
{
"state": "discovered",
"state_reason": null,
"attempts_count": 2,
"resolution_id": "bbtgr_5tgliBmzjZ6mpQPRbQjfKj",
"target": {
"id": "bbtgt_5tgliBmzjZ6mpQPRbQjfKj",
"key_type": "identification",
"key_value": "1192793827",
"resolution_type": "plain_key",
"spbvi": "CRB",
"participant_nit": "860003020",
"participant_name": "BBVA Colombia",
"creditor": {
"type": "natural",
"document_type": "CC",
"document_number": "1192793827",
"full_name": "Diego Sánchez"
},
"creditor_account": {
"type": "savings_account",
"number": "0013123456789",
"currency_code": "COP"
},
"transaction_amount": null,
"payment_id": null
},
"same_person_keys": [
{
"key_type": "alphanumeric",
"key_value": "@BBVA3104838791",
"bank_code": "007",
"participant_nit": "890903938",
"creditor_account": {
"type": "savings_account",
"number_last_four": "4321",
"currency_code": "COP"
},
"resolution_required": true
}
]
}
```
The target is fresh and single-use: pass its `id` to exactly one outgoing
transfer. Calling discovery again creates another target rather than returning
a cached target.
### No exact target found [#no-exact-target-found]
```json
{
"state": "not_discovered",
"state_reason": "no_exact_target",
"attempts_count": 4,
"resolution_id": null,
"target": null,
"same_person_keys": [
{
"key_type": "email",
"key_value": "diego@example.com",
"bank_code": "007",
"participant_nit": "890903938",
"creditor_account": {
"type": "savings_account",
"number_last_four": "4321",
"currency_code": "COP"
},
"resolution_required": true
}
]
}
```
`same_person_keys` is always present and may be empty. It contains at most five
keys confirmed for the same identity, never data from a different person. It is
not exhaustive: candidates come only from the request data, and discovery stops
at the first exact target. Therefore, a `discovered` response includes only
same-person keys observed before that first exact target.
An item in `same_person_keys` is not a target. Before using one:
1. Let the payer choose the alternative.
2. Call [Resolve target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target)
with its `key_value` to obtain a fresh target.
3. Confirm that the fresh target's `participant_nit` and the ending of
`creditor_account.number` still match the alternative's `participant_nit`
and `number_last_four`.
4. Ask for confirmation before sending money.
The extra resolution is required because an alternative is not reserved and
may point somewhere else later.
## Cache behavior [#cache-behavior]
Mono may cache only `not_discovered` results that have an empty
`same_person_keys` list, for up to one hour. On such a cache hit,
`attempts_count` is `0`. Results containing a target or an alternative are not
served from cache, so their directory data and IDs come from the current
request.
## Send the outgoing transfer [#send-the-outgoing-transfer]
After the payer confirms the resolved beneficiary, create the outgoing
transfer with the target ID:
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"description": "Confirmed beneficiary payout",
"transfers": [
{
"target_id": "bbtgt_5tgliBmzjZ6mpQPRbQjfKj",
"amount": { "amount": 12000000, "currency": "COP" },
"external_id": "confirmed-transfer-1"
}
]
}
```
See [Create outgoing
transfers](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers)
for the complete request. A target can fund only one transfer; use a new
discovery or resolution for another payment.
When discovery creates an exact target, the existing
[`target_resolution_created`](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_created)
and
[`target_resolution_resolved`](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_resolved)
webhooks apply. `not_discovered` results and alternatives do not emit
target-resolution webhooks. Consumers should deduplicate resolution webhooks by
`resolution_id`; delivery is asynchronous, so a `target_resolution_created`
event may already carry `state=resolved` and ordering is not guaranteed. Treat
the latest `state` and `updated_at` as authoritative.
## Errors and retries [#errors-and-retries]
| Status | When it occurs | What to do |
| -----: | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
| `400` | The document or account number cannot be compared safely. | Correct the beneficiary data; do not retry unchanged. |
| `401` | The token is missing, invalid, or expired. | Obtain a valid token and retry. |
| `403` | The credential lacks the required scope, or discovery is not enabled for the tenant. | Request the scope or contact Mono to enable target discovery. |
| `404` | The tenant account does not exist or does not belong to the tenant. | Verify `tenant_account_id`. An unknown exact bank lookup also returns `bank_not_found`. |
| `422` | The payload is invalid or `bank_code` is not accepted (`unknown_bank`). | Correct the request or select a bank from the public catalog. |
| `429` | The per-client or per-tenant discovery limit was exceeded (`rate_limit_exceeded`). | Wait for the `Retry-After` interval before retrying. |
| `503` | The Bre-B directory is temporarily unavailable. | Retry after `Retry-After` when present, using bounded backoff. |
| `500` | An unexpected server error occurred. | Retry safely; contact support with the response `id` if it persists. |
A valid search with no exact target is not an HTTP error: it returns `200` with
`state=not_discovered`.
## Related reference [#related-reference]
* [List banks](/docs/api-reference/breb-participant/target-discovery/list_banks)
* [Get bank](/docs/api-reference/breb-participant/target-discovery/get_bank)
* [Discover target](/docs/api-reference/breb-participant/target-discovery/discover_target)
* [Resolve target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target)
## Next steps [#next-steps]
* [Target discovery in sandbox](/docs/guides/breb-participant/sandbox/target-discovery) — exercise the deterministic `101`–`104` scenarios before going live.
* [Outgoing transfer flow](/docs/guides/breb-participant/flows/outgoing-transfer) — pay the target you just discovered.
* [Bre-B Participant authentication](/docs/guides/breb-participant/authentication) — the scopes and token flow discovery requires.
# Flows (/docs/guides/breb-participant/flows)
A **flow** is a complete, end-to-end use case that composes several Bre-B concepts together. While the [Concepts](/docs/api-reference/breb-participant) section answers "what is a payment key, target, or tenant account?", flows answer "how do I receive a Bre-B payment, or send one?".
Flows are grouped by commercial capability:
* **Payin** — receiving a Bre-B payment from another participant.
* **Payout** — sending a Bre-B transfer to another participant.
## Available flows [#available-flows]
Generate a Bre-B QR, receive the payment, and reconcile the confirmation event.
Find a beneficiary key from identity and bank-account data, then confirm the destination.
Resolve the destination key, dispatch the transfer, and handle the lifecycle states.
## Next steps [#next-steps]
* [Outgoing transfer states](/docs/guides/breb-participant/architecture/outgoing-transfer-states) — the lifecycle every outgoing transfer follows.
* [Outgoing transfer errors](/docs/guides/breb-participant/outgoing-transfer-errors) — rejection reasons and how to handle them.
* [Webhook signatures](/docs/guides/breb-participant/webhook-signatures) — how to verify event authenticity.
# QR collection (/docs/guides/breb-participant/flows/qr-collection)
**Bre-B** is Colombia's interbank instant-payment scheme — a real-time rail similar to Brazil's Pix or India's UPI that lets any participant bank or licensed entity send and receive funds 24/7 in seconds. A **QR collection** is the flow your business runs when you want a customer to pay you over Bre-B: you generate a QR code that encodes a payment instruction, the customer scans it from their banking app, their bank submits the transfer, and Mono notifies you when the funds arrive.
This flow is the payin half of Bre-B Participant. Use it for in-person checkout, kiosk top-ups, donation pages, or any moment when the payer initiates a bank-to-bank payment using their phone.
This page is a flow scaffold and will be expanded with the QR generation request, the EMVCo
payload format, the confirmation webhook, common errors, and a sandbox walkthrough.
## Before you start [#before-you-start]
You will need:
* A Bre-B participant account with Mono and OAuth client credentials. See [Authentication](/docs/guides/api-standards/authentication).
* A registered tenant account that will receive the funds.
* A webhook endpoint that can receive [collection events](/docs/api-reference/breb-participant/collection-webhooks) and verify their signature — see [Webhook signatures](/docs/guides/breb-participant/webhook-signatures).
* An [idempotency key](/docs/guides/api-standards/idempotency-keys) per QR generation request.
## Sequence overview [#sequence-overview]
1. **Generate a collection** — your backend asks Mono for a QR encoding the amount, currency, and a reference you control.
2. **Display the QR** — your frontend or terminal renders the EMVCo payload as a scannable code.
3. **Customer pays** — the payer scans the QR with their banking app and authorizes the transfer.
4. **Receive confirmation** — Mono fires a webhook to your endpoint with the final result, signed with HMAC-SHA256.
5. **Reconcile** — your system credits the order or releases the goods.
## Steps [#steps]
The full step-by-step (with curl, Node.js, and Python examples for QR generation, verification, and reconciliation) is pending. Until it is published, see:
* The [Bre-B participant API reference](/docs/api-reference/breb-participant) for the collection endpoints.
* [Webhook signatures](/docs/guides/breb-participant/webhook-signatures) for verifying the confirmation event.
* [Webhook payloads](/docs/guides/breb-participant/webhook-payloads) for the event envelope and payload schemas.
## Next steps [#next-steps]
* [Outgoing transfer](/docs/guides/breb-participant/flows/outgoing-transfer) — the payout counterpart of this flow.
* [Sandbox: collections](/docs/guides/breb-participant/sandbox/collections) — simulate a customer payment without involving a real bank.
* [Webhook payloads](/docs/guides/breb-participant/webhook-payloads) — the event you will receive on a successful collection.
# Payins (/docs/guides/core/sandbox/payins)
In production, money arrives when a real person decides to pay you. In the sandbox you play that
person yourself: one request makes a payment land on one of your collections, and everything
downstream behaves as if the money were real. It is how you build and test a collection from end
to end before you ever touch production.
The simulated payment runs the real payment rail: Bre-B, Colombia's instant transfer network.
Same states, same webhooks, same timing. Only the money is not real.
## Before you start [#before-you-start]
You will need:
* A sandbox API key, and requests pointed at `https://api.sandbox.cuentamono.com`.
* The `payins` scope on your credentials.
* Something that can be paid: a [template](/docs/guides/core/concepts/payins/templates) in state
`ready`, or an [intent](/docs/guides/core/concepts/payins/intents) in state `ready_to_pay`.
That last point is the one that trips people up. A template or an intent is not payable the moment
you create it. Mono still has to register its Bre-B key with the network. Wait for the
[`payin_template_ready`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_ready) or
[`payin_intent_ready_to_pay`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_ready_to_pay)
webhook first. Simulating against a target with no key yet answers `400` with
`payment_token_not_found`.
Sandbox only. In every other environment this endpoint answers `403` with
`feature_not_enabled`, so a call that leaks into production code cannot mint payments.
## What a simulation actually does [#what-a-simulation-actually-does]
Mono operates directly on Bre-B, so the sandbox does not fake the payment: it drives the same
acceptance flow a real transfer goes through. You name a target and an amount, and Mono builds an
incoming payment against that target's Bre-B key, exactly as if a payer had scanned its QR.
The response is a receipt, not a verdict. It tells you a payin exists; the payment's outcome
reaches you a few seconds later over the [payin webhooks](/docs/guides/core/webhooks/payins), the
same way a real one does.
## The two shapes of a payment [#the-two-shapes-of-a-payment]
Which shape you send depends on what you are paying. A template accepts many payments and knows
nothing about this particular one, so the request has to carry its own method and amount. An intent
was declared for one specific payment and already fixes both.
| Paying a... | Send | Because |
| :------------------------------------------------------ | :---------------------------------- | :---------------------------------------------------------- |
| [Template](/docs/guides/core/concepts/payins/templates) | `template_id` + `method` + `amount` | The template accepts many payments; this one brings its own |
| [Intent](/docs/guides/core/concepts/payins/intents) | `intent_id` | The intent already fixed the method and the amount |
The two shapes are exclusive. Naming both targets, or sending a `method` alongside an
`intent_id`, answers `422`.
### Paying a template [#paying-a-template]
```json title="POST /v1/core/payins/sandbox/payments"
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" }
}
```
`method` must be one the template accepts, or the call answers `400` with `method_not_allowed`.
Today `co_breb` is the only payment method Core Payins offers. Amounts are integers in cents, so
the example above is 50,000 COP.
### Paying an intent [#paying-an-intent]
```json title="POST /v1/core/payins/sandbox/payments"
{
"intent_id": "cpint_5tgliBmzjZ6mpQPRbQjfKj"
}
```
That is the whole request. The intent supplies the method and the amount, which is exactly what
makes it useful for testing: send this and you are verifying the amount the intent enforces, not
one you retyped.
## Reading the response [#reading-the-response]
The status code tells you whether a payin was born:
| Status | Body | What happened |
| :----- | :------------------------------------------------------------- | :-------------------------------------------------------- |
| `201` | `{ "payin_id": "cpin_...", "state_reason": null }` | The payment entered. Follow it over the webhooks. |
| `201` | `{ "payin_id": "cpin_...", "state_reason": "bank_rejected" }` | A payin exists and was already rejected on its own rules. |
| `200` | `{ "payin_id": null, "state_reason": "amount_below_minimum" }` | The payment was refused before a payin existed. |
That `200` deserves attention. When Mono's payment authorization refuses the payment (an amount
outside the template's bounds, a payer it does not expect), no payin is created and **no webhook
fires**. In production the payer's own banking app is what reports the refusal, so this response is
the only place your system can learn what happened. Provoking those refusals is a large part of
what this endpoint is for.
## Choosing who pays [#choosing-who-pays]
Leave `payer` out and Mono invents one: a random Colombian name, a random 10-digit `CC` document.
Fine for most tests. Send it when the payer is the point of the test:
```json
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" },
"payer": {
"document": { "type": "CC", "number": "1234567890" },
"name": "Juan Garcia"
}
}
```
This is how you exercise a template's
[`expected_payers`](/docs/guides/core/concepts/payins/templates#restricting-who-can-pay) list. Send
a document on the list to confirm the payment goes through; send one that is not to confirm you get
`payer_not_allowed`. Document `type` accepts `CC`, `CE`, `NIT`, `PA`, and `TI`.
## Provoking failures [#provoking-failures]
There are two kinds of failure, and you provoke them in two different ways. Getting this distinction
right saves a lot of confusion.
### Rail faults: the `error` field [#rail-faults-the-error-field]
These are infrastructure conditions the payment rail reports **after** the payment was already
accepted. Name one in `error` and the sandbox asks the rail to produce it:
```json
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" },
"error": "risk_control"
}
```
The call still answers `201` with a `payin_id` and a null `state_reason`: the payment was accepted,
so a payin exists. The failure arrives 2 to 5 seconds later, on the webhook:
| `error` | The payin ends | Meaning |
| :--------------------- | :-------------------------------- | :-------------------------------------- |
| `risk_control` | `failed` / `risk_control` | The provider's risk controls stopped it |
| `provider_unavailable` | `failed` / `provider_unavailable` | The payment provider was out of service |
| `breb_timeout` | `failed` / `breb_timeout` | The Bre-B rail did not answer in time |
| `unknown` | `failed` / `unknown` | An unclassified fault |
All four land the payin in `failed` and reach you as
[`payin_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_failed). If the payment was
addressed to an intent, the intent reports separately: it re-arms for a retry with
[`payin_intent_attempt_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_attempt_failed),
or gives up with
[`payin_intent_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_failed).
### Business refusals: by construction [#business-refusals-by-construction]
Everything Mono's own validation catches has no lever, and needs none: you provoke it by sending a
payment the target genuinely refuses. These are refused synchronously, so they come back on the
response itself as a `200` with no payin.
| To get | Send |
| :------------------------------------ | :------------------------------------------------------ |
| `amount_below_minimum` | An amount under the template's `minimum_payin_amount` |
| `amount_exceeds_maximum` | An amount over its `maximum_payin_amount` |
| `amount_exceeds_total_maximum` | An amount that pushes it past `total_maximum_amount` |
| `payer_not_allowed` | A `payer` document absent from `expected_payers` |
| `template_expired` / `intent_expired` | A payment after the target's `expires_at` |
| `template_paid` | A second payment to a `single_use` template |
| `template_disabled` | A payment to a template you updated to `enabled: false` |
| `template_deleted` | A payment to a template you deleted |
Every code, with its cause and what to do about it, is in
[Payin errors](/docs/guides/core/concepts/payins/payins/errors).
`intent_amount_mismatch` is out of reach in the sandbox. A payment to an intent always takes
the amount from the intent itself, so there is no way to send a different one. Trust the
intent's own validation for that case.
## Simulating the QR the payer scanned [#simulating-the-qr-the-payer-scanned]
A Bre-B payment can carry the identifier of the QR code it came from. Send it when your
reconciliation reads that field:
```json
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" },
"method_params": { "payment_id": "CO.COM.CRB.TRXIDP1234567891234" }
}
```
Omit it and the rail assigns one. `method_params` is method-specific: for `co_breb` the only field
is `payment_id`.
## Errors from the endpoint itself [#errors-from-the-endpoint-itself]
These are faults in the request, not simulated payment outcomes.
| Status | Code | Cause |
| :----- | :---------------------------------------------- | :------------------------------------------------------------------------ |
| `400` | `method_not_allowed` | The template does not accept the `method` you sent. |
| `400` | `payin_invalid_method_params` | The `method_params` do not match the method. |
| `400` | `payment_token_not_found` | The target has no Bre-B key yet: it is not `ready` / `ready_to_pay`. |
| `401` | `missing_authorization_header`, `invalid_token` | Credential problem. |
| `403` | `feature_not_enabled` | Not a sandbox environment, or payins is not enabled for your account. |
| `404` | `template_not_found`, `payin_intent_not_found` | No such target. Another tenant's target reads as one that does not exist. |
| `422` | N/A | The request violates the schema: both targets, or a missing field. |
## A full test, end to end [#a-full-test-end-to-end]
Putting it together, a collection you can run in one sitting:
1. Create a template. Wait for `payin_template_ready`.
2. Simulate a payment under the minimum. Expect `200`, `amount_below_minimum`, no webhook.
3. Simulate a valid payment. Expect `201`, then `payin_created`.
4. A few seconds later, expect `payin_approved_in_provider` and `payin_subaccount_credited`.
5. Simulate another with `"error": "breb_timeout"`. Expect `201`, then `payin_failed`.
6. Read the template. Its `paid_amount` counts step 3 and not steps 2 or 5.
If your integration handles all six, it handles a real collection.
## Next steps [#next-steps]
* [Collect with a template](/docs/guides/core/flows/payins/collect-with-template): the flow this endpoint tests.
* [Collect with intents](/docs/guides/core/flows/payins/collect-with-intents): the same, one declared payment at a time.
* [Payin errors](/docs/guides/core/concepts/payins/payins/errors): every reason a payment ends badly.
* [Simulates a payment](/docs/api-reference/core/payins/simulate_payin_payment): the endpoint reference.
# Payouts (/docs/guides/core/sandbox/payouts)
A Bre-B payout from Core is two calls: you resolve the destination, then you pay it. In the sandbox
both calls run against the sandbox Bre-B directory, and a set of reserved values lets you trigger
each error on demand instead of waiting for it to happen in production.
## Before you start [#before-you-start]
You will need:
* A sandbox API key, and requests pointed at `https://api.sandbox.cuentamono.com`.
* The `payouts` scope on your credentials.
* The `co_breb` payout method enabled in your [program](/docs/guides/core/concepts/program). Ask
Mono to enable it if your sandbox tenant does not have it yet.
* A funded **subaccount** to pay from. Payouts and target resolution only work on accounts of type
`subaccount`; fund it with a topup before you start.
## A successful payout [#a-successful-payout]
Any well-formed key that is not one of the reserved values below resolves, and every one of them
resolves to the same sandbox creditor. An email (`payee@example.com`) or an alphanumeric key
(`@payee123`) works.
1. **Resolve the key** with
[`POST /v1/core/payouts/co_breb/targets/resolve`](/docs/api-reference/core/payouts/resolve_breb_target):
```json
{
"account_id": "lacc_...",
"format": "plain_key",
"value": "@payee123"
}
```
The response carries the target `id` and the `creditor`, masked: the document keeps its last
three digits and each word of the name its first three letters. In the sandbox it is always
the same person:
```json
{
"type": "natural",
"document_type": "CC",
"document_number": "*****455",
"full_name": "Jua*** Car*** Per*** Gom***"
}
```
Compare the creditor against the person you mean to pay before you move money. To walk the happy
path end to end, register your test user with document `CC 123143455`.
2. **Pay the resolved target** with
[`POST /v1/core/payouts`](/docs/api-reference/core/payouts/create_payout), using
`target.kind: co_breb_target_id`:
```json
{
"method": "co_breb",
"account_id": "lacc_...",
"external_id": "withdrawal-0001",
"target_amount": { "amount": 5000000, "currency": "COP" },
"target": {
"country": "CO",
"currency": "COP",
"kind": "co_breb_target_id",
"target_id": ""
}
}
```
The call answers `201` with the payout in state `processed`. The rest of the lifecycle reaches you
over webhooks: Mono sends one on every state change, and each one carries the full payout. Read
its `state` to know where it is: `created`, `processed`, `charged`, `sending_to_provider`,
`sent_to_provider` and finally `successful`. A failed payout ends in `declined`, `rejected` or
`returned`, with the reason in `state_reason`.
3. **Reconcile** with
[`GET /v1/core/payouts/find_successful_by`](/docs/api-reference/core/payouts/find_successful_payout)
using your `external_id`.
Amounts are in cents:
`5000000`
is COP 50,000.
## Errors when resolving a key [#errors-when-resolving-a-key]
Send these values as `value` with `format: plain_key`. Each one answers synchronously, so you can
test the message your users see without creating a payout.
| `value` | Response | What your integration should do |
| :----------------------------------------- | :-------------------------------- | :----------------------------------------------------------------- |
| `sandbox@key_not_found.err` | `404` · `breb_target_not_found` | Tell the user the key does not exist and ask for another one |
| `sandbox@key_suspended.err` | `403` · `key_blocked_or_inactive` | Tell the user the key is suspended; do not retry with the same key |
| A malformed key | `400` · `invalid_breb_key` | Validate the key format before calling |
| `sandbox@key_target_creditor_mismatch.err` | `200`, with document `*****500` | Your creditor check must catch the mismatch and stop before paying |
The last row is not an error from Mono: the key resolves normally, but to a document that differs
from the usual sandbox creditor (`12314345500` instead of `123143455`). Use it to prove that your integration compares the resolved creditor against
your own records and refuses to pay when they differ.
## Errors when creating the payout [#errors-when-creating-the-payout]
These are validated before any money moves, so the payout is not created and the call answers
`400`.
| How to trigger it | Response |
| :---------------------------------------------------------------- | :--------------------------------- |
| Pay more than the subaccount balance | `400` · `insufficient_funds` |
| Pay an amount above the Bre-B limit, e.g. `1000000000000` (cents) | `400` · `amount_exceeds_max_limit` |
## Coming next [#coming-next]
| Scenario | Test case |
| :-------------------------------------------------- | :--------------- |
| Payout rejected by the network after it was charged | To be documented |
| Payout returned after it succeeded | To be documented |
## Next steps [#next-steps]
* [Payouts](/docs/guides/core/concepts/payouts) — how payouts, targets and fees fit together.
* [Ledger](/docs/guides/core/sandbox/ledger) — check the ledger entries each payout leaves behind.
* [Production checklist](/docs/guides/core/production) — steps to take before processing real payouts.
# Ledger (/docs/guides/core/sandbox/ledger)
Use these test cases to validate how your integration handles ledger account transactions — topups, withdrawals, inter-account transfers, fees, payout origins, and reverts.
This page is a sandbox scaffold. The full version will include deterministic test cases for
every `origin` value in the `ledger_account_transaction_created` event catalog
(subaccount\_operation, payout, card\_presentment\_payment, and others).
## Test scenarios [#test-scenarios]
| Scenario | Description | Test case |
| :--------------------- | :----------------------------------------------------------- | :--------------- |
| Subaccount topup | Funds are credited to a subaccount. | To be documented |
| Subaccount withdrawal | Funds are debited from a subaccount. | To be documented |
| Inter-account transfer | Funds move between two subaccounts. | To be documented |
| Fee debit | A platform fee is deducted from the account. | To be documented |
| Payout origin | A ledger entry is created as the result of a payout. | To be documented |
| Card presentment | A ledger entry is created when a card authorization settles. | To be documented |
| Revert | A previous ledger entry is reversed. | To be documented |
## Next steps [#next-steps]
* [Ledger account transaction webhooks](/docs/guides/core/webhooks/ledger-account-transactions) — the event payloads fired for each scenario above.
* [Payouts](/docs/guides/core/sandbox/payouts) — simulate payout-origin ledger entries.
# Cards (/docs/guides/core/sandbox/cards)
Use these test cases to validate card issuance flows — creation states, activation, freeze, unfreeze, and replacement — without provisioning real card credentials.
This page is a sandbox scaffold. The full version will include deterministic test cases
(specific field values that produce each card state) for creation, activation,
freeze/unfreeze, and replacement scenarios.
## Test scenarios [#test-scenarios]
| Scenario | Description | Test case |
| :--------------------------------------- | :-------------------------------------------------------------------------------- | :--------------- |
| Virtual card created active | A virtual card is provisioned and immediately usable. | To be documented |
| Physical card created pending activation | A physical card is provisioned in `created` state and requires holder activation. | To be documented |
| Card freeze | A card is frozen and all subsequent authorization attempts are declined. | To be documented |
| Card unfreeze | A frozen card is unfrozen and authorizations resume. | To be documented |
| Card replacement | An existing card is replaced with a new card id and credentials. | To be documented |
## Next steps [#next-steps]
* [Card Transactions](/docs/guides/core/sandbox/card-transactions) — simulate authorizations and declines on a sandbox card.
* [Card issuance flow](/docs/guides/core/flows/issuing-cards) — the full issuance sequence in production.
# Card Transactions (/docs/guides/core/sandbox/card-transactions)
Use these test cases to validate how your integration handles card transaction events — approvals, declines by reason, withdrawals, partial settlements, reversals, refunds, and reconciliation.
This page is a sandbox scaffold. The full version will include deterministic test cases
(specific test card numbers or amounts that trigger each scenario) for every event type in the
card transaction webhook catalog.
## Test scenarios [#test-scenarios]
| Scenario | Description | Test case |
| :------------------------------------------ | :------------------------------------------------------------------------------------ | :--------------- |
| Authorization approved | The card transaction is authorized and the balance is charged. | To be documented |
| Authorization declined — insufficient funds | The transaction is declined because the account balance is too low. | To be documented |
| Authorization declined — fraud | The transaction is declined due to a fraud flag. | To be documented |
| Authorization declined — limit exceeded | The transaction is declined because a spending limit is reached. | To be documented |
| Withdrawal | An ATM withdrawal is authorized, including the `withdrawal_fee`. | To be documented |
| Partial settlement (credit adjustment) | An authorized amount is partially refunded at settlement. | To be documented |
| Debit adjustment | The authorized amount is increased at settlement. | To be documented |
| Reversal | An authorization is fully reversed and the charge is returned. | To be documented |
| Refund | A refund is issued to the account. | To be documented |
| Reconciliation | The settled amount differs from the authorized amount after final network settlement. | To be documented |
## Next steps [#next-steps]
* [Card transaction webhooks](/docs/guides/core/webhooks/card-transactions) — the event payloads fired for each scenario above.
* [Cards](/docs/guides/core/sandbox/cards) — set up a sandbox card before running transaction scenarios.
# Sandbox (/docs/guides/core/sandbox)
The Core sandbox is a complete, isolated copy of the production environment. You can create ledger accounts, issue cards, simulate authorizations, and trigger payouts — all without moving real money or affecting any real customer. It is the right place to develop your integration end-to-end before requesting production credentials.
Sandbox credentials are different from production credentials and the two are not interchangeable. Always confirm which environment your code is pointed at before running anything.
## Before you start [#before-you-start]
You need a Mono account with sandbox access enabled, and sandbox credentials: an API key or an OAuth 2.0 credential, obtained as described in [Authentication](/docs/guides/core/authentication). Sandbox credentials only work against the sandbox base URL below.
## Base URL [#base-url]
```
https://api.sandbox.cuentamono.com
```
## Test scenarios [#test-scenarios]
Card creation states, activation, and controls
Authorizations, declines, settlements, and reversals
Ledger movements, subaccount operations, and fees
Payout success, failure, and retry scenarios
Simulate a payer paying your templates and intents
## Next steps [#next-steps]
* [Architecture](/docs/guides/core/architecture)
* [Authentication](/docs/guides/core/authentication)
* [Production checklist](/docs/guides/core/production)
# Collection Payment Validation (/docs/guides/breb-participant/architecture/payment-validation)
When a customer pays one of your collections, Mono validates the payment before
accepting it. The rules are the ones you set when creating the collection: how
much can be paid, until when, and by whom. A payment that breaks a rule is
rejected automatically, and the rejection reason tells you exactly which rule it
broke. This page explains each rule and every reason you can receive.
A **collection** is your Bre-B receiving endpoint (a QR code or payment key). Each individual
payment against it is a **payment attempt**, with its own state — separate from the
[collection's lifecycle](/docs/guides/breb-participant/architecture/collection-states). A
rejected attempt does **not** change the collection's state; it only increments its
`failed_attempts` counter.
## How a payment attempt is validated [#how-a-payment-attempt-is-validated]
Every incoming payment becomes a payment attempt that moves through one of
three outcomes:
| Attempt state | Meaning | Final |
| ------------- | ----------------------------------------------------------------- | ----- |
| `created` | Attempt received, validation in progress | No |
| `successful` | Payment was successful | Yes |
| `rejected` | Payment was rejected by Mono based on the collection's rules | Yes |
| `failed` | Payment failed due to external reasons (bank, provider, timeouts) | Yes |
For `rejected` and `failed` attempts, the attempt's `state_reason` field names
the exact cause — the full list is in the
[reference table below](#rejection-reasons-reference).
## The rules you control [#the-rules-you-control]
### Who can pay — `expected_payers` [#who-can-pay--expected_payers]
If you provide `expected_payers` when creating the collection, **only those
payers are allowed** — anyone else is rejected with `payer_not_allowed`. Each
expected payer is identified by their Colombian identity document:
* `document_type` — one of `CC`, `CE`, `NIT`, `NUIP`, `PPT`, `PEP`, `PASS`, `TI`
* `document_number` — the document number of the expected payer
Leave `expected_payers` empty to accept payments from anyone. See
[UC6 — Client invoice](/docs/guides/breb-participant/use-cases/collections#uc6--client-invoice-restricted-payer)
for a worked example.
### How much can be paid — amount bounds [#how-much-can-be-paid--amount-bounds]
All amounts are integers in **centavos** (e.g. `10000000` = COP 100,000.00).
Two levels of bounds apply:
| Field | Applies to | Rule | Rejection reason |
| ------------------------ | ----------------- | -------------------------------------------------------------------------------------- | ------------------------------ |
| `minimum_attempt_amount` | each attempt | Minimum amount allowed per payment attempt (`multiple_use` collections only) | `amount_below_minimum` |
| `maximum_attempt_amount` | each attempt | Maximum amount allowed per payment attempt (`multiple_use` collections only) | `amount_exceeds_maximum` |
| `total_maximum_amount` | the running total | Maximum total the collection can receive — when reached, the collection becomes `paid` | `amount_exceeds_total_maximum` |
| `total_minimum_amount` | the running total | Minimum total required for the collection to be considered fulfilled | `amount_below_total_minimum` |
`usage_mode` decides how attempts accumulate:
* `single_use` — the collection accepts **only one** successful payment.
* `multiple_use` — the collection accepts multiple payments until
`total_maximum_amount` is reached.
### Until when — `expires_at` / `expires_in` [#until-when--expires_at--expires_in]
Set the collection's deadline either as an absolute ISO-8601 datetime
(`expires_at`) or in seconds from now (`expires_in`) — they are mutually
exclusive on the request. A payment after the deadline is rejected with
`collection_expired`. See
[UC7 — Flash sale](/docs/guides/breb-participant/use-cases/collections#uc7--flash-sale-short-window)
for a worked example.
### On or off — `enabled` [#on-or-off--enabled]
`enabled` controls whether the collection can receive payments at all. Payments
against a disabled collection are rejected with `collection_disabled` — useful
for pausing a collection without deleting it.
## Rejection reasons reference [#rejection-reasons-reference]
The attempt's `state_reason` (present only on `rejected` / `failed` attempts)
names the cause:
**Tied to your collection's rules and state:**
| `state_reason` | The payment was rejected because… |
| ------------------------------ | ------------------------------------------------------------------------ |
| `payer_not_allowed` | the payer is not in the collection's `expected_payers` list |
| `amount_below_minimum` | the attempt is below `minimum_attempt_amount` |
| `amount_exceeds_maximum` | the attempt is above `maximum_attempt_amount` |
| `amount_exceeds_total_maximum` | it would push the total received past `total_maximum_amount` |
| `amount_below_total_minimum` | it is below the collection's `total_minimum_amount` requirement |
| `collection_expired` | the collection's `expires_at` / `expires_in` deadline has passed |
| `collection_disabled` | the collection has `enabled: false` |
| `collection_paid` | the collection already reached `paid` (its total maximum, or single use) |
| `collection_discarded` | the collection was discarded |
| `collection_invalid_state` | the collection is not in a state that accepts payments |
| `collection_not_found` | no collection matches the payment's key |
| `collection_pruning` | the collection's key is being pruned |
**External / operational causes** (outside your collection's configuration):
`rejected_by_bank`, `provider_unavailable`, `lock_timeout`, `risk_control`,
`internal_error`, `mono_timeout`, `breb_timeout`, `unknown`.
## Observing rejections in your integration [#observing-rejections-in-your-integration]
* **Webhooks** — every unsuccessful attempt fires
[`collection.attempt_unsuccessful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_unsuccessful)
carrying the attempt with its `state_reason`; successful ones fire
[`collection.attempt_successful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_successful).
* **Polling** — list attempts with
[List collection attempts](/docs/api-reference/breb-participant/collections/list_collection_attempts_by_collection).
* A rejected attempt never moves the collection out of its current state — it
only increments the collection's `failed_attempts` counter.
## Next steps [#next-steps]
* [Collection lifecycle](/docs/guides/breb-participant/architecture/collection-states) — how the collection itself moves between states.
* [Collection use cases](/docs/guides/breb-participant/use-cases/collections) — ready-made rule combinations (fixed price, restricted payer, flash sale…).
* [Create collections](/docs/api-reference/breb-participant/collections/create_collections) — the full request reference for every field on this page.
# Architecture overview (/docs/guides/breb-participant/architecture)
**Bre-B** is Colombia's interbank instant-payment scheme — a real-time rail similar to Brazil's Pix or India's UPI that lets any participant bank or licensed entity send and receive funds 24/7 in seconds. The Bre-B Participant API gives you a programmable on-ramp to that network: you authenticate with OAuth, register tenant accounts, generate QR codes for incoming payments, and dispatch outgoing transfers against payment keys (aliases such as phone, document, email, alphanumeric, or merchant code).
This page is the right starting point if you want to understand the path a request takes from your code to the Bre-B central directory and back.
## Components [#components]
| Component | Role |
| :----------------- | :---------------------------------------------------------------------- |
| Auth service | Issues OAuth access tokens for the Participant API. |
| Tenant accounts | Logical accounts at Mono that hold balances and receive Bre-B payments. |
| Target resolution | Look up a payment key against the Bre-B central directory. |
| Outgoing transfers | Dispatch a payment to a resolved target. |
| Collections | Generate QR codes and reconcile incoming payments. |
| Webhooks | Async events for resolutions, transfers, collections. |
## Request lifecycle (outgoing transfer) [#request-lifecycle-outgoing-transfer]
1. **Authenticate** — exchange client credentials for an OAuth access token.
2. **Resolve the target** — submit the payment key; receive the holder's name and bank.
3. **Dispatch** — submit the resolved target with amount and idempotency key.
4. **Track lifecycle** — webhooks fire on every state transition.
5. **Reconcile** — credit or release the operation in your books.
## Tenant model [#tenant-model]
Every customer is a Bre-B participant tenant at Mono. Tenant accounts are isolated; resolutions and transfers always operate against a specific tenant.
## Lifecycle references [#lifecycle-references]
Once a request enters the platform it moves through a deterministic state machine, and every transition emits a webhook. The two flows have their own lifecycle pages — read these alongside the architecture above to understand what your integration should be listening for.
* [Outgoing transfer lifecycle](/docs/guides/breb-participant/architecture/outgoing-transfer-states) — states, webhook events, and terminal outcomes for payouts you dispatch.
* [Collection lifecycle](/docs/guides/breb-participant/architecture/collection-states) — states, webhook events, and terminal outcomes for QR codes and payment keys that receive funds.
## Next steps [#next-steps]
* [Authentication](/docs/guides/breb-participant/authentication)
* [Concepts](/docs/guides/breb-participant/concepts)
* [Integration example](/docs/guides/breb-participant/integration-example)
# Collection Lifecycle (/docs/guides/breb-participant/architecture/collection-states)
A **collection** is a Bre-B receiving endpoint — typically a QR code or a
shareable payment key — that lets one or more payers send funds into your
tenant account. From the moment you create it, the collection moves through a
deterministic sequence of states. Each transition maps to a webhook event, so
your integration can track progress without polling. This guide explains what
each state means, what makes a collection move between them, and how to tell
terminal success from terminal failure.
## State diagram [#state-diagram]
## States at a glance [#states-at-a-glance]
| State | Terminal? | Webhook event |
| -------------- | --------- | --------------------------------------------------------------------------------------------------------------------- |
| `created` | No | [`collection.created`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_created) |
| `ready` | No | [`collection.ready`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_ready) |
| `minimum_paid` | No | [`collection.minimum_paid`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_minimum_paid) |
| `paid` | **Yes** | [`collection.paid`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_paid) |
| `discarded` | **Yes** | [`collection.discarded`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_discarded) |
| `failed` | **Yes** | [`collection.failed`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_failed) |
Individual payment attempts against a `ready` or `minimum_paid` collection have their own
state machine (`successful`, `rejected`, `failed`) and emit their own events —
[`collection.attempt_successful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_successful)
and
[`collection.attempt_unsuccessful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_unsuccessful).
A failed attempt does **not** move the collection out of its current state — it only
increments the `failed_attempts` counter on the collection.
## Detailed walkthrough [#detailed-walkthrough]
### 1. `created` [#1-created]
Initial state. A collection enters this state as soon as
[create collection](/docs/api-reference/breb-participant/collections/create_collection)
accepts your request. The collection row exists in our system and has an `id`
you can use for lookups, but the underlying Bre-B key has not yet been
registered with the central directory (DICE), so it cannot accept payments
yet.
If the request fails synchronous validation — for example, the amount limits are invalid or
the key format is malformed — the API returns a 4xx response and the collection is **not**
persisted. It will never move through this state machine and no webhook will fire.
**What happens next:** the system registers the Bre-B key with DICE. This
typically completes within seconds.
### 2. `ready` [#2-ready]
The Bre-B key has been successfully registered with DICE and the collection's
`keys` array is populated with the active key. The collection is now live and
can accept incoming payments — payers can scan the QR or send to the key.
**Next states:**
* → `minimum_paid` (multi-use collections only) once cumulative `paid_amount`
reaches or exceeds `total_minimum_amount`.
* → `paid` once cumulative `paid_amount` reaches `total_maximum_amount`. For
single-use collections this happens on the first successful payment.
* → `discarded` if the collection is deleted, expires, or is auto-discarded
for inactivity before being paid.
### 3. `minimum_paid` [#3-minimum_paid]
Only applies to **multiple-use** collections that define both a minimum and a
maximum amount. The cumulative `paid_amount` has crossed
`total_minimum_amount`, meaning the collection has met its soft target but is
still open for additional payments up to `total_maximum_amount`.
This state is useful for partial-payment scenarios — for example, a goal-based
fundraiser that has hit its goal but still accepts contributions until a hard
cap.
**Next states:**
* → `paid` once `paid_amount` reaches `total_maximum_amount`.
* → `discarded` on expiration or inactivity.
### 4. `paid` (terminal) [#4-paid-terminal]
The collection has been fully paid. `paid_amount` equals
`total_maximum_amount`. The collection no longer accepts payments and no
further state transitions occur.
### 5. `discarded` (terminal) [#5-discarded-terminal]
The collection has been removed from the active set. The `state_reason` field
tells you why:
* `deleted` — explicitly removed via
[delete collection](/docs/api-reference/breb-participant/collections/delete_collection).
* `expired` — the collection passed its `expires_at` without reaching a paid
terminal state.
* `inactivity` — the collection was idle long enough to be auto-discarded by
the system.
No further state transitions occur.
### 6. `failed` (terminal) [#6-failed-terminal]
The collection could not be made usable. This happens when the Bre-B key
registration fails between `created` and `ready`. The `state_reason` field
carries the cause — common values include `key_already_registered`,
`key_registration_failed`, and `key_canceled`.
Failed collections do not accept payments and never transition again. To
recover, create a new collection (typically with a different key).
## Working with states in your integration [#working-with-states-in-your-integration]
1. **Treat every non-terminal state as transient.** A collection in `created`
is not yet usable — wait for `ready` before surfacing the QR or key to
payers.
2. **Key off webhooks, not polling.** Every state transition emits a webhook
with the new state and, where applicable, a `state_reason`. Verify each
webhook (see [Webhook signature verification](/docs/guides/breb-participant/webhook-signatures))
and update your local record idempotently — the same state can be retried
by our workers.
3. **Handle out-of-order delivery.** Webhook deliveries are at-least-once. If
you see `paid` before `minimum_paid`, keep the later state and ignore the
earlier one. A simple rule: if the collection is already in a terminal
state locally, ignore non-terminal webhooks.
4. **Distinguish collection state from attempt state.** A `ready` collection
can have many `collection.attempt_unsuccessful` events without leaving the
`ready` state. Surface attempt failures separately from collection state in
your UI and reconciliation logic.
5. **Three states are final.** `paid`, `discarded`, and `failed` are the only
terminal states. Once a collection reaches any of them, it will never move
again.
# Outgoing Transfer Lifecycle (/docs/guides/breb-participant/architecture/outgoing-transfer-states)
An outgoing transfer (a payout leaving your account over Bre-B) moves through a
deterministic sequence of states. Each transition maps to a webhook event, so
your integration can track progress without polling. This guide explains what
each state means, what makes a transfer move between them, and how to tell
terminal success from terminal failure.
## State diagram [#state-diagram]
## States at a glance [#states-at-a-glance]
| State | Terminal? | Webhook event |
| ----------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `created` | No | [`outgoing_transfer.created`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_created) |
| `processing` | No | [`outgoing_transfer.processing`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_processing) |
| `target_resolved` | No | [`outgoing_transfer.target_resolved`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_target_resolved) |
| `held` | No | [`outgoing_transfer.held`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_held) |
| `sent_to_breb_provider` | No | [`outgoing_transfer.sent_to_breb_provider`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_send_to_breb_provider) |
| `successful` | **Yes** | [`outgoing_transfer.successful`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_successful) |
| `failed` | **Yes** | [`outgoing_transfer.failed`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_failed) |
## Detailed walkthrough [#detailed-walkthrough]
### 1. `created` [#1-created]
Initial state. A transfer enters this state as soon as
[create outgoing transfers](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers)
accepts it into a batch. The transfer row exists in our system and has an `id`
you can use for lookups.
If the request fails pre-insert validation — for example, the amount exceeds the max limit, or
the target is invalid — the transfer is returned in the `rejected_transfers` array of the
batch response and is **not** persisted. It will never move through this state machine and no
webhook will fire. See [Outgoing Transfer Error
Codes](/docs/guides/breb-participant/outgoing-transfer-errors) for the list of rejection
reasons.
**What happens next:** a background worker picks up the transfer and moves it
to `processing`. This typically happens within seconds.
### 2. `processing` [#2-processing]
The system is actively working on the transfer. The path it takes depends on
how the target was supplied:
* **Plain-key transfers** (you provided a Bre-B key in `query`): the system
must first resolve the key to a recipient before it can hold funds.
* **Pre-resolved target** (you supplied an existing `target_id`): resolution is
skipped and the transfer goes straight to funds-holding. If that target was
resolved with an `expected_creditor`, the match was already checked by
[Resolve target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target)
and the transfer carries the same `expected_creditor`.
**Next states:**
* → `target_resolved` if resolution succeeds (plain-key path) **or** the target
was already resolved.
* → `failed` if resolution errors out — for example, `key_not_found`,
`key_suspended`, or `invalid_key_format`.
### 3. `target_resolved` [#3-target_resolved]
The recipient's Bre-B key has been resolved to a concrete creditor and bank
account. If you supplied an `expected_creditor` on a transfer by `query`, that's
where we check the resolved creditor matches. This is the **last chance to
reject the transfer before funds move**.
**Next states:**
* → `held` once funds are held on the source account.
* → `failed` with `state_reason = target_creditor_mismatch` if the resolved
creditor's document doesn't match the `expected_creditor` you sent. This is
the safeguard that prevents paying the wrong person when a key is reassigned.
### 4. `held` [#4-held]
Funds equal to the transfer amount are reserved on your source account. The
money has left your available balance but has not yet been sent to the
recipient's bank. At this point the transfer is fully committed from your side.
**Next states:**
* → `sent_to_breb_provider` once we dispatch the payment instruction to the
Bre-B provider (Credibanco).
* → `failed` if the hold itself fails (rare at this point — most funds errors
surface earlier). `state_reason` will carry the cause (e.g.,
`insufficient_funds`).
### 5. `sent_to_breb_provider` [#5-sent_to_breb_provider]
The payment has been handed off to the Bre-B provider for settlement. It is
now in flight across the Bre-B network. The transfer is awaiting the final
settlement callback from the provider.
Most transfers settle within a few seconds, but provider timeouts and downstream bank
availability can stretch this to minutes. Don't assume a transfer has failed just because it
hasn't left this state quickly — wait for the terminal webhook.
**Next states:**
* → `successful` on a positive settlement callback.
* → `failed` on a negative settlement callback (e.g., `breb_timeout`,
`provider_unavailable`, `risk_control`, or an `unknown` fallback).
### 6. `successful` (terminal) [#6-successful-terminal]
The funds have been credited to the recipient. No further transitions; no more
webhooks will fire for this transfer.
### 7. `failed` (terminal) [#7-failed-terminal]
The transfer did not complete. The `state_reason` field on the transfer (and
on the `outgoing_transfer.failed` webhook) tells you why. Depending on the
reason, the error may be retryable by submitting a new transfer — see the
retry guidance in
[Outgoing Transfer Error Codes](/docs/guides/breb-participant/outgoing-transfer-errors#4-retry-guidance).
* **Rejected** transfers are returned synchronously in `rejected_transfers` when you create a
batch. They never become transfers in our system. - **Failed** transfers were accepted,
persisted, moved through at least `created`, and then hit an error. They have a `state_reason`
and emit a webhook.
## Working with states in your integration [#working-with-states-in-your-integration]
1. **Treat every non-terminal state as transient.** A transfer in `held` or
`sent_to_breb_provider` is still in flight — surface it as "processing" in
your UI rather than "complete."
2. **Key off webhooks, not polling.** Every state transition emits a webhook
with the new state and, on failure, a `state_reason`. Verify each webhook
(see [Webhook signature verification](/docs/guides/breb-participant/webhook-signatures))
and update your local record idempotently — the same state can be retried
by our workers.
3. **Handle out-of-order delivery.** Webhook deliveries are at-least-once. If
you see `successful` before `sent_to_breb_provider`, keep the later state
and ignore the earlier one. A simple rule: if the transfer is already in a
terminal state locally, ignore non-terminal webhooks.
4. **Only two states are final.** `successful` and `failed` are the only
terminal states. Once a transfer reaches either, it will never move again.
# Ledger accounting (/docs/guides/core/flows/ledger-accounting)
A **ledger transaction** is the unit of bookkeeping in Core: a balanced pair of debit and credit entries that together describe a movement of money. Every change to balances goes through a ledger transaction — there is no way to silently bump a balance, and there is no way to lose track of where money came from or went.
This flow covers the patterns you will use most often: posting a simple two-entry transaction, holding funds before settlement, settling a hold, and reversing an entry when something goes wrong.
This page is a flow scaffold. The full version will include code snippets for each pattern, a
Mermaid sequence diagram, and a reconciliation worked example.
## Before you start [#before-you-start]
You will need a Core API key, two or more ledger accounts to move funds between, and an [idempotency key](/docs/guides/api-standards/idempotency-keys) per posting attempt.
## Patterns [#patterns]
### Post a transaction [#post-a-transaction]
The simplest pattern: debit one account, credit another, both in the same currency, both for the same amount. The transaction posts atomically — either both entries land or neither does.
### Hold and settle [#hold-and-settle]
For card authorizations and any case where you commit to a future debit, post a **hold** first and then **settle** it. The hold reserves funds; the settlement converts the reservation into a permanent debit. If the hold is not settled within its window, it expires and the funds are released.
### Reverse a transaction [#reverse-a-transaction]
Reversals undo a posted transaction by posting its mirror image. The original is preserved — Core's ledger is append-only — so the audit trail stays intact.
### Cross-currency [#cross-currency]
When debit and credit accounts are in different currencies, the transaction must reference an [FX rate](/docs/guides/core/concepts/fx-rates).
## Steps [#steps]
The full step-by-step (with code) is pending. Until it is published, see the [Ledger concept page](/docs/guides/core/concepts/ledger) and the [Core ledger API reference](/docs/api-reference/core/ledger).
## Next steps [#next-steps]
* [Issuing cards](/docs/guides/core/flows/issuing-cards) — cards spend through ledger holds and settlements.
* [Payout disbursement](/docs/guides/core/flows/payout-disbursement) — payouts are ledger transactions plus a rail.
* [Best practices](/docs/guides/core/best-practices) — reconciliation patterns.
# Payout disbursement (/docs/guides/core/flows/payout-disbursement)
A **payout disbursement** is the end-to-end flow of moving money out of a Core ledger account: triggering the payout, watching it transition through its lifecycle, handling success or failure, and reconciling the ledger. Internal payouts (account-to-account inside Core) settle instantly; external payouts (to Colombian bank accounts) ride Banking's rails and inherit Banking's confirmation timing.
Use this flow whenever a balance becomes due to its owner — a wallet user requests a withdrawal, a marketplace seller's settlement window closes, payroll runs, or a treasury sweep moves funds between internal accounts.
This page is a flow scaffold. The full version will include code samples for internal and
external payouts, the state diagram, the relationship to Banking transfers, and reconciliation
patterns.
## Before you start [#before-you-start]
You will need:
* A Core API key with the `payouts` scope.
* A source ledger account with sufficient balance.
* A destination — another ledger account (internal) or a Colombian bank account (external).
* A webhook endpoint subscribed to payout events.
* An [idempotency key](/docs/guides/api-standards/idempotency-keys) per disbursement.
* Your fee policies for `bank_transfer` and `bank_transfer_gmf`, if you charge your users for payouts. Mono creates the base ones — see [Fee policies](/docs/guides/core/concepts/fees/policies).
## Sequence overview [#sequence-overview]
### Internal payout [#internal-payout]
1. Submit the payout with source, destination, amount, and idempotency key.
2. Core posts a balanced ledger transaction.
3. Webhook fires with the final state.
### External payout [#external-payout]
1. Submit the payout with source, destination bank account, amount, and idempotency key.
2. Core prices the fees with your fee policies, posts a hold on the source account for the amount plus the fees and dispatches the transfer to Banking. The fees come back in `calculated_fees`.
3. Banking routes the transfer (Turbo, ACH, Transfiya) and sends back a confirmation.
4. Core converts the hold into a settlement on success, or releases it on rejection.
5. Webhook fires with the final state.
## Steps [#steps]
The full step-by-step is pending. Until it is published, see:
* [Payouts concept page](/docs/guides/core/concepts/payouts).
* [Banking transfers concept page](/docs/guides/banking/concepts/transfers) — for external destinations.
* [Core payouts API reference](/docs/api-reference/core/payouts).
## Next steps [#next-steps]
* [Ledger accounting](/docs/guides/core/flows/ledger-accounting)
* [Banking transfers flow](/docs/guides/banking/flows/sending-transfers)
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys)
# Issuing cards (/docs/guides/core/flows/issuing-cards)
**Issuing** is the process of creating a card that spends against a Core ledger account, putting it into the user's hands, and managing its lifecycle. A card can be virtual (an instant 16-digit number for online or in-app use) or physical (a plastic card that ships to the cardholder), and it can be provisioned to a digital wallet such as Apple Pay or Google Wallet to enable contactless payments.
Use this flow when your product needs to give end users a card — consumer wallets, corporate expense programs, marketplace seller payouts on a card, or any case where you want money to be spendable directly from a ledger balance.
This page is a flow scaffold. The full version will include code per step, the lifecycle state
diagram, the digital-wallet provisioning handshake, and the authorization flow.
## Before you start [#before-you-start]
You will need:
* A Core API key with the `cards` scope.
* A ledger account that the card will spend against.
* A spending control configuration appropriate for the cardholder.
* A webhook endpoint subscribed to [card and card-transaction events](/docs/guides/core/webhooks).
## Sequence overview [#sequence-overview]
1. **Create the card** — call the create-card endpoint with the linked ledger account, cardholder details, and a spending control reference.
2. **Activate the card** — virtual cards are usable immediately; physical cards require activation by the cardholder after delivery.
3. **Provision to a digital wallet** (optional) — generate a wallet-provisioning token so the cardholder can add the card to Apple Pay or Google Wallet.
4. **Authorize transactions** — Core decides each authorization in real time using the cardholder's balance and the active spending controls. Webhooks fire on every authorization, settlement, and reversal.
5. **Manage the lifecycle** — freeze, unfreeze, replace, or terminate the card as needed.
## Steps [#steps]
The full step-by-step is pending. Until it is published, see:
* [Cards concept page](/docs/guides/core/concepts/cards) — data model and lifecycle.
* [Spending Controls concept page](/docs/guides/core/concepts/spending-controls) — how to configure rules.
* [Core cards API reference](/docs/api-reference/core/cards).
## Next steps [#next-steps]
* [Ledger accounting](/docs/guides/core/flows/ledger-accounting) — how authorizations become ledger entries.
* [Best practices](/docs/guides/core/best-practices)
# Transfers between subaccounts (/docs/guides/core/flows/account-to-account-transfers)
A **transfer between subaccounts** moves money from one of your users' balances to another — a peer-to-peer payment inside your wallet, a movement between a user's COP and USD accounts, a correction between two balances. Same-currency transfers are a plain ledger movement. When the two subaccounts hold different currencies, Core converts the amount at an exchange rate and the transfer becomes an **FX transfer**, with two fees you can price: a fixed conversion charge and your margin on the rate.
This flow is part of Core's **ledger** service. FX transfers must be enabled for the program of the accounts; Mono enables it when you set the program up.
## Before you start [#before-you-start]
You will need:
* A Core API key with the `ledger` scope.
* Two active subaccounts, of active account holders, belonging to you.
* For FX transfers: the feature enabled for the subaccounts' program, and — if you want to price the conversion — your fee policies for `account_to_account_fx` and `account_to_account_fx_spread`. Mono creates the base ones; see [Fee policies](/docs/guides/core/concepts/fees/policies).
* An [idempotency key](/docs/guides/api-standards/idempotency-keys) per transfer.
## Same-currency transfers [#same-currency-transfers]
Send the source subaccount, the destination subaccount and one amount — `source_amount` or `target_amount`, the other one `null`. Both are equal, and the currency must be the one of both subaccounts. Core posts a balanced ledger transaction: a debit on the source and a credit on the destination, instantly. No fees apply.
## FX transfers [#fx-transfers]
When the subaccounts hold different currencies, the same request performs a conversion:
1. Send either `source_amount` (in the source currency) or `target_amount` (in the destination currency); Core calculates the other one.
2. Optionally send `fx_rate`. If you leave it out, Core takes the current market rate — the one the [FX rates endpoint](/docs/guides/core/concepts/fx-rates) publishes — and adds your spread on top. If you send it, Core uses it as-is and assumes it already includes your spread.
3. Core prices the two fees below, posts the transaction and returns the amounts, the rate applied and `calculated_fees`.
### The two fees [#the-two-fees]
| Fee type | What it is | How it is charged |
| :----------------------------- | :---------------------------------------------------------------------- | :------------------------------------------------------------------------------------- |
| `account_to_account_fx` | A fixed charge for the conversion, in the source currency. | As its own movement inside the transfer's transaction (`add_posting`). |
| `account_to_account_fx_spread` | Your margin on the exchange rate, as a percentage over the market rate. | Embedded in the rate applied (`upcharge`): the user simply gets a slightly worse rate. |
Both are priced by your [fee policies](/docs/guides/core/concepts/fees/policies) of those types — by program, account holder or account, like any other fee — and both can be replaced for a single transfer through `override_fees`: `fixed` and `tx_description` for the fixed fee, `percentage` for the spread.
### How the amounts are calculated [#how-the-amounts-are-calculated]
`fx_rate` is the rate from the destination currency to the source one — for COP to USD it looks like `4000`; for USD to COP, like `0.00025`. With the fixed fee in the source currency:
* **Given the target amount**: `source_amount = target_amount × fx_rate + fixed_fee`.
* **Given the source amount**: `target_amount = (source_amount − fixed_fee) ÷ fx_rate`.
Results are rounded to the decimals of each currency. Without an explicit `fx_rate`, and a spread of 1% on a market rate of 4000, the rate applied is `4000 × 1.01 = 4040`.
### What comes back [#what-comes-back]
The response includes `calculated_fees`, with one entry per fee type and the final amount of each in the source currency. The spread fee is the difference between converting at the applied rate and converting at the market rate — what your user paid for the conversion beyond the market price. These are the amounts to reconcile against; they also appear, with their state and the rule that priced them, in the [fees listing](/docs/api-reference/core/fees/list_fees).
## Steps [#steps]
1. Look up the two subaccounts and confirm both, and their holders, are active.
2. Decide which amount you fix — what the sender pays or what the receiver gets — and whether you pass your own `fx_rate`.
3. Call [Perform a transfer between ledger accounts](/docs/api-reference/core/ledger/create_ledger_account_transfer) with an idempotency key. A `201` returns the transfer; repeating the same key returns the same transfer with a `200`.
4. Read `calculated_fees` and the amounts from the response and show your user what was charged.
5. To find a transfer again by your own reference, use [Find a successful subaccount operation](/docs/api-reference/core/ledger/find_successful_subaccount_operation).
## Next steps [#next-steps]
* [Fee policies](/docs/guides/core/concepts/fees/policies) — set the conversion charge and the spread per program, holder or account.
* [FX rates](/docs/guides/core/concepts/fx-rates) — where the market rate comes from.
* [Ledger accounting](/docs/guides/core/flows/ledger-accounting) — the bookkeeping every transfer produces.
# Flows (/docs/guides/core/flows)
A **flow** is a complete, end-to-end use case that composes several Core concepts together. While the [Concepts](/docs/guides/core/concepts) section answers "what is a ledger account, card, or spending control?", flows answer "how do I post a settlement, issue a card, or disburse a payout?".
Core flows are grouped by the kind of operation they describe:
* **Ledger operations** — moving money inside Core's books, including transfers between subaccounts with or without currency conversion.
* **Issuing** — creating cards and provisioning them to digital wallets.
* **Payin** — getting money into subaccounts: top-ups, static-key collection, per-payment intents, and settlement.
* **Payout** — getting money out of a ledger account.
## Available flows [#available-flows]
Double-entry bookkeeping with holds, settlements, and reversals.
Move money between two subaccounts, in the same currency or across currencies, with fees.
Create, activate, freeze, and provision cards to digital wallets.
Top-ups, static-key collection, per-payment intents, and settlement.
Trigger a payout, manage state, and receive confirmation.
## Next steps [#next-steps]
* [Core concepts](/docs/guides/core/concepts)
* [Integration example](/docs/guides/core/integration-example)
* [Webhooks](/docs/guides/core/webhooks)
# Collections (/docs/guides/breb-participant/sandbox/collections)
This sandbox environment lets you test your collection integration end-to-end
without processing real payments. You can simulate incoming payments to your
collections, including both successful and failed scenarios.
## Prerequisites [#prerequisites]
* At least one collection with an **active** Bre-B key
* The collection associated to the Bre-B key must be in `ready` or `minimum_paid` state.
## Sandbox Collections simulator [#sandbox-collections-simulator]
If you want to simulate a payment without writing any code, open the
[Bre-B sandbox simulator](https://breb.app/uc/sandbox-simulator). It is a
Mono-hosted web tool that pays any of your sandbox collection keys
end-to-end — the same webhooks fire as in a real payment.
### Three input modes [#three-input-modes]
* **Enter a key manually** (`Ingresar llave`) — paste a Bre-B key value.
* **Scan a QR** (`Escanear QR`) — use your device camera to read a Bre-B QR.
* **Upload a QR image** (`Subir imagen`) — drag-and-drop a saved QR.
### Form fields [#form-fields]
| Field | Purpose |
| :------------------ | :---------------------------------------------------------------- |
| `Llave de pago` | Destination Bre-B key (auto-filled when you scan or upload a QR). |
| `Monto (COP)` | Amount in Colombian pesos. **Required.** |
| `Tipo de error` | Optional. Pick a failure mode to exercise rejection paths. |
| `Datos del pagador` | Optional. Override the auto-generated payer information. |
Click `Simular Pago` and the simulator drives the same end-to-end flow as the
`POST /sandbox/collection-attempts` API documented below — meaning the webhooks
you receive on your endpoint are identical.
### When to use which [#when-to-use-which]
| Use the web simulator if... | Use the API endpoint if... |
| :-------------------------------------------------------------- | :------------------------------------------ |
| You want a quick check without writing a script | You're adding sandbox calls to a test suite |
| You're verifying a specific scenario by hand | You need to script bulk simulations |
| You haven't set up sandbox tooling yet (Postman, scripts, etc.) | You're already in your test runner |
## Simulating a collection attempt [#simulating-a-collection-attempt]
Send a `POST` request to simulate a payer making a payment to one of your
collection keys. This [endpoint](/docs/api-reference/breb-participant/sandbox/create_sandbox_collection_attempt) is only available in the sandbox environment.
### Minimal example [#minimal-example]
```json
{
"creditor_key_value": "@YOURBREBKEY",
"amount": {
"amount": 50000,
"currency": "COP"
}
}
```
The sandbox will generate random payer information and payment\_id if they aren't provided, and process the payment
through the full flow.
You will receive a `202 Accepted` response immediately:
```json
{
"collection_id": "bbcol_5g8k2mNpQrStUvWx",
"attempt_id": "bbcolat_7hJkLmNpQrStUvWx",
"transfer_id": "bbit_1a2b3c4d5e6f7g8h9i0j",
"attempt_state_reason": null
}
```
A few seconds later, the webhook events will be delivered to your configured webhook URL with the final attempt result.
You could receive the following collection webhooks:
* [`collection.attempt_successful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_successful): The payment attempt was settled successfully by the simulation workflow.
* [`collection.attempt_unsuccessful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_unsuccessful): The payment attempt was rejected (either by a simulated error or a validation failure).
* [`collection.paid`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_paid): The collection has reached its total amount.
* [`collection.minimum_paid`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_minimum_paid): The collection has reached its minimum required amount.
## Error simulation [#error-simulation]
By default, simulated payments complete successfully. To test how your
integration handles failures, pass an `error` field:
```json
{
"creditor_key_value": "@MN1234567890",
"amount": {
"amount": 50000,
"currency": "COP"
},
"error": "tx_risk_control"
}
```
The payment is still created and you will receive the `202 Accepted` response,
but the subsequent webhook event will carry a rejection status.
### Available error codes [#available-error-codes]
| Error code | Description |
| ------------------------- | ----------------------------------------- |
| `tx_unknown` | An unexpected error occurred |
| `tx_provider_unavailable` | The Bre-B system is unavailable |
| `tx_breb_timeout` | The Bre-B system timed out |
| `tx_risk_control` | Transaction blocked by risk control rules |
## Expected flow [#expected-flow]
1. You call the sandbox endpoint to simulate a payment.
2. The API responds with `202 Accepted`.
3. After a short delay (2–5 seconds), a webhook is sent to your configured URL.
1. If no `error` was specified, the payment settles successfully.
2. If an `error` was specified, the payment is rejected with the corresponding reason.
# Target discovery in sandbox (/docs/guides/breb-participant/sandbox/target-discovery)
Use these deterministic scenarios to test [target
discovery](/docs/guides/breb-participant/flows/target-discovery) without relying
on real Bre-B directory data. They apply only at:
```text
https://sandbox.api.cuentamono.com
```
Use the sample identities and accounts only for testing. The suffix rules on this page are not
available in production and do not describe real people or accounts.
## Prerequisites and scopes [#prerequisites-and-scopes]
Use a sandbox tenant account and an access token with the scope required by
each step. Ask Mono to enable target discovery for that sandbox tenant before
testing:
| Step | Scope |
| ------------------------------------------------------ | ----------------------------------------------------- |
| `POST /api/v1/targets/discover` | `target_discoveries` |
| `GET /api/v1/banks` and `GET /api/v1/banks/{ach_code}` | `target_discoveries` or `target_discoveries:readonly` |
| `POST /api/v1/targets/resolve` for the 102 alternative | `target_resolutions` |
| `POST /api/v1/outgoing_transfers` | `outgoing_transfers` |
See [authentication and
scopes](/docs/guides/breb-participant/authentication) for token setup.
## Select a scenario with `document_number` [#select-a-scenario-with-document_number]
The last three characters of `person.document_number` are the **only** selector.
Changing the email, phone, bank, or account does not select a different
scenario.
| Suffix | Result |
| ---------------- | ------------------------------------------------------------------------------- |
| `101` | Exact identification target after one prior same-person alternative |
| `102` | No exact target; one identification alternative that can be resolved |
| `103` | Empty result; a repeated identical request can demonstrate a negative cache hit |
| `104` | Directory unavailable with HTTP `503` |
| Any other suffix | Same empty, cacheable result as `103` |
The document suffix is the only scenario selector. The requested bank and
account remain valid inputs even when the account starts with `99`; changing
either one does not switch the sandbox scenario. However, the deterministic
responses documented for `101` and `102` are guaranteed only when the requested
`account.number` does not start with `99`. That prefix is reserved for synthetic
alternative destinations, which Mono derives independently.
A document-shaped plain key ending in `102` is reserved in sandbox for `POST
/api/v1/targets/resolve` and for outgoing transfers paid by key. This lets the alternative
returned by discovery resolve to the same deterministic destination.
## Exact match: suffix 101 [#exact-match-suffix-101]
Send this request:
```http
POST /api/v1/targets/discover
Content-Type: application/json
Authorization: Bearer
```
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"person": {
"document_type": "CC",
"document_number": "1192793101",
"first_name": "Diego",
"first_surname": "Sánchez",
"phone": "+573104838791",
"email": "diego@example.com"
},
"account": {
"bank_code": "013",
"number": "0013123456789",
"type": "savings_account"
}
}
```
The response is:
```json
{
"state": "discovered",
"state_reason": null,
"attempts_count": 2,
"resolution_id": "bbtgr_5tgliBmzjZ6mpQPRbQjfKj",
"target": {
"id": "bbtgt_5tgliBmzjZ6mpQPRbQjfKj",
"key_type": "identification",
"key_value": "1192793101",
"resolution_type": "plain_key",
"spbvi": "CRB",
"participant_nit": "860003020",
"participant_name": null,
"creditor": {
"type": "natural",
"document_type": "CC",
"document_number": "1192793101",
"full_name": "Diego Sánchez"
},
"creditor_account": {
"type": "savings_account",
"number": "0013123456789",
"currency_code": "COP"
},
"transaction_amount": null,
"payment_id": null
},
"same_person_keys": [
{
"key_type": "alphanumeric",
"key_value": "@BBVA3104838791",
"bank_code": "001",
"participant_nit": "860002964",
"creditor_account": {
"type": "savings_account",
"number_last_four": "2852",
"currency_code": "COP"
},
"resolution_required": true
}
]
}
```
`participant_name` is not part of the scenario: it is the institution's name on
file at Mono, so it can be `null` in sandbox.
For BBVA (`bank_code=013`), discovery tries the bank's alphanumeric template
before the identification candidate. It observes the template as a
same-person alternative, finds an exact match on the second attempt, and exits
early. It does not try the later phone and email candidates.
`attempts_count` therefore depends on the selected bank's candidate order and
templates; it is not the number of fields in the request. `same_person_keys`
is non-exhaustive and contains only same-person candidates observed before the
first exact match.
Only the winning exact target creates a resolution and emits
[`target_resolution_created`](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_created)
and
[`target_resolution_resolved`](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_resolved).
The alternative does not emit resolution webhooks. Delivery is asynchronous,
so `target_resolution_created` can already contain `state=resolved`.
## Alternative only: suffix 102 [#alternative-only-suffix-102]
Use the same payload with the document ending in `102`:
```http
POST /api/v1/targets/discover
Content-Type: application/json
Authorization: Bearer
```
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"person": {
"document_type": "CC",
"document_number": "1192793102",
"first_name": "Diego",
"first_surname": "Sánchez",
"phone": "+573104838791",
"email": "diego@example.com"
},
"account": {
"bank_code": "013",
"number": "0013123456789",
"type": "savings_account"
}
}
```
The full candidate walk returns:
```json
{
"state": "not_discovered",
"state_reason": "no_exact_target",
"attempts_count": 4,
"resolution_id": null,
"target": null,
"same_person_keys": [
{
"key_type": "identification",
"key_value": "1192793102",
"bank_code": "007",
"participant_nit": "890903938",
"creditor_account": {
"type": "savings_account",
"number_last_four": "6809",
"currency_code": "COP"
},
"resolution_required": true
}
]
}
```
This result creates no target, resolution, or resolution webhook. Of the
sandbox alternatives, only this `identification` alternative ending in `102`
is guaranteed to support a later resolution.
### Resolve and verify the 102 alternative [#resolve-and-verify-the-102-alternative]
After the payer selects the alternative, resolve its exact `key_value`:
```http
POST /api/v1/targets/resolve
Content-Type: application/json
Authorization: Bearer
```
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"format": "plain_key",
"value": "1192793102"
}
```
The resolved response includes this deterministic destination:
```json
{
"id": "bbtgr_6tgliBmzjZ6mpQPRbQjfKj",
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"format": "plain_key",
"value": "1192793102",
"state": "resolved",
"state_reason": null,
"target": {
"id": "bbtgt_6tgliBmzjZ6mpQPRbQjfKj",
"key_type": "identification",
"key_value": "1192793102",
"resolution_type": "plain_key",
"spbvi": "CRB",
"participant_nit": "890903938",
"participant_name": null,
"creditor": {
"type": "natural",
"document_type": "CC",
"document_number": "1192793102",
"full_name": "Persona Sandbox"
},
"creditor_account": {
"type": "savings_account",
"number": "99023886809",
"currency_code": "COP"
},
"transaction_amount": null,
"payment_id": null
}
}
```
The synthetic identity is always `CC` / `Persona Sandbox`, and the result is
always an `identification` target for document-shaped values ending in `102`.
Before asking the payer to confirm, verify both of these invariants:
1. The resolved target's `participant_nit` is `890903938`, which corresponds
to the alternative's ACH code `007`.
2. `creditor_account.number` ends in the advertised `number_last_four` value,
`6809`.
Do not pay the alternative if either the ACH destination or the last four
digits changed.
## Empty result and negative cache: suffix 103 or default [#empty-result-and-negative-cache-suffix-103-or-default]
Use suffix `103` (or any suffix other than `101`–`104`) to get an empty result:
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"person": {
"document_type": "CC",
"document_number": "1192793103",
"first_name": "Diego",
"first_surname": "Sánchez",
"phone": "+573104838791",
"email": "diego@example.com"
},
"account": {
"bank_code": "013",
"number": "0013123456789",
"type": "savings_account"
}
}
```
The first uncached response is:
```json
{
"state": "not_discovered",
"state_reason": "no_exact_target",
"attempts_count": 4,
"resolution_id": null,
"target": null,
"same_person_keys": []
}
```
Repeat the **identical request** within one hour to exercise the negative
cache:
```json
{
"state": "not_discovered",
"state_reason": "no_exact_target",
"attempts_count": 0,
"resolution_id": null,
"target": null,
"same_person_keys": []
}
```
Only empty `not_discovered` results are cached, for up to one hour. A shared
sandbox may already contain the same fingerprint, so the first request you
observe can legitimately have `attempts_count=0`. If the sandbox does not have
an HMAC available for discovery, negative results are not cached and each
request performs the full candidate walk.
## Unavailable directory: suffix 104 [#unavailable-directory-suffix-104]
Send the normal payload with document `1192793104`:
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"person": {
"document_type": "CC",
"document_number": "1192793104",
"first_name": "Diego",
"first_surname": "Sánchez",
"phone": "+573104838791",
"email": "diego@example.com"
},
"account": {
"bank_code": "013",
"number": "0013123456789",
"type": "savings_account"
}
}
```
Discovery aborts on the first unavailable directory attempt and returns HTTP
`503`:
```json
{
"code": "503 Service Unavailable",
"errors": [
{
"error_code": "service_is_unavailable",
"message": "The service is unavailable, Please try again later",
"path": null,
"url": null
}
],
"id": "log_7MkWaFqvfosB8fzHhb1Eql",
"message": "Service unavailable"
}
```
The error is not cached and creates no target, resolution, or webhook. Retry
with bounded backoff, as you would for a transient production failure.
## Use the resulting target in a transfer [#use-the-resulting-target-in-a-transfer]
The exact target from `101` and the newly resolved target from `102` are each
single-use. Send the chosen `target.id` in one outgoing transfer:
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"transfers": [
{
"external_id": "sandbox-discovery-001",
"target_id": "bbtgt_6tgliBmzjZ6mpQPRbQjfKj",
"amount": {
"amount": 10000,
"currency": "COP"
}
}
]
}
```
Alternatively, an outgoing transfer by `plain_key` with a document-shaped
value ending in `102` resolves to the same deterministic destination. See
[outgoing transfers in
sandbox](/docs/guides/breb-participant/sandbox/outgoing-transfers) and [create
outgoing
transfers](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers).
## Bank catalog [#bank-catalog]
`GET /api/v1/banks` and `GET /api/v1/banks/{ach_code}` use the normal sandbox
catalog. They do not need a suffix or any other simulation value. Use their
canonical `ach_code` values in discovery requests.
## Related reference [#related-reference]
* [Discover target](/docs/api-reference/breb-participant/target-discovery/discover_target)
* [List banks](/docs/api-reference/breb-participant/target-discovery/list_banks)
* [Get bank](/docs/api-reference/breb-participant/target-discovery/get_bank)
* [Resolve target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target)
* [Target discovery flow](/docs/guides/breb-participant/flows/target-discovery)
* [Outgoing transfer flow](/docs/guides/breb-participant/flows/outgoing-transfer)
# Outgoing transfers (/docs/guides/breb-participant/sandbox/outgoing-transfers)
The deterministic `101` and `102` discovery scenarios produce single-use targets that you can
send in an outgoing transfer. A document-shaped `plain_key` ending in `102` is also reserved
for payment by key. See [target discovery in
sandbox](/docs/guides/breb-participant/sandbox/target-discovery).
## Successful simulation [#successful-simulation]
To simulate a successful [outgoing transfer](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers) in the sandbox environment, simply send a request with a valid payload structure that passes all business validations. No special field values are required.
```json
{
"tenant_account_id": "your_tenant_account_id",
"transfers": [
{
"external_id": "transfer_001",
"payment_id": "123856456",
"amount": {
"amount": 50000,
"currency": "COP"
},
"description": "Payment for services",
"query": {
"format": "plain_key",
"value": "user@email.com"
}
}
]
}
```
If you retrieve the outgoing transfer using this [endpoint](/docs/api-reference/breb-participant/outgoing-transfers/get_an_outgoing_transfer_by_id), you will see the transfer completed successfully:
```json
{
"id": "bbot_5tgliBmzjZ6mpQPRbQjfKj",
"resolution_request_id": "bbtgr_5tgliBmzjZ6mpQPRbQjfKj",
"state": "successful",
"state_reason": null,
...
}
```
## Error simulation [#error-simulation]
You can simulate errors in the sandbox environment through special rules that apply to certain field values of the payload. The following are the rules to simulate errors for the creation of outgoing transfers:
| Condition One | Condition Two | Expected Error in State Reason |
| ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| transfers\[x].description contains "tx\_unknown" | | unknown |
| transfers\[x].description contains "tx\_amount\_exceeds\_balance\_limit" | | amount\_exceeds\_balance\_limit |
| transfers\[x].description contains "tx\_creditor\_account\_not\_found" | | creditor\_account\_not\_found |
| transfers\[x].description contains "tx\_invalid\_creditor\_account" | | invalid\_creditor\_account |
| transfers\[x].description contains "tx\_risk\_control" | | risk\_control |
| transfers\[x].description contains "tx\_breb\_timeout" | | breb\_timeout |
| transfers\[x].query.format = "plain\_key" | transfers\[x].query.value = "[sandbox@tx\_provider\_unavailable.err](mailto:sandbox@tx_provider_unavailable.err)" | provider\_unavailable |
| transfers\[x].query.format = "plain\_key" | transfers\[x].query.value = "[sandbox@key\_unknown.err](mailto:sandbox@key_unknown.err)" | unknown |
| transfers\[x].query.format = "plain\_key" | transfers\[x].query.value = "[sandbox@key\_not\_found.err](mailto:sandbox@key_not_found.err)" | key\_not\_found |
| transfers\[x].query.format = "plain\_key" | transfers\[x].query.value = "[sandbox@key\_suspended.err](mailto:sandbox@key_suspended.err)" | key\_suspended |
| transfers\[x].query.format = "plain\_key" | transfers\[x].query.value = "[sandbox@key\_provider\_unavailable.err](mailto:sandbox@key_provider_unavailable.err)" | provider\_unavailable |
| transfers\[x].query.format = "plain\_key" and transfers\[x].expected\_creditor is set | transfers\[x].query.value = "[sandbox@key\_target\_creditor\_mismatch.err](mailto:sandbox@key_target_creditor_mismatch.err)" | target\_creditor\_mismatch |
> **Note:** These errors are related to internal communication with the Bre-B system and are different from errors related to data validation. These errors are reflected in the `state_reason` of the outgoing transfer.
## Examples [#examples]
### Simulating a Key Not Found Error [#simulating-a-key-not-found-error]
If you want to simulate an error when the Bre-B key does not exist for the creation of outgoing transfers, you can send something like:
```json
{
"tenant_account_id": "your_tenant_account_id",
"transfers": [
{
"external_id": "transfer_001",
"payment_id": "123856456",
"query": {
"format": "plain_key",
"value": "sandbox@key_not_found.err"
}
}
]
}
```
The response will be successful. Then, if you retrieve the outgoing transaction using this [endpoint](/docs/api-reference/breb-participant/outgoing-transfers/get_an_outgoing_transfer_by_id), you will see the error reflected in the state of the outgoing transfer:
```json
{
"id": "bbot_5tgliBmzjZ6mpQPRbQjfKj",
"resolution_request_id": "bbtgr_5tgliBmzjZ6mpQPRbQjfKj",
"state": "failed",
"state_reason": "key_not_found",
...
}
```
### Simulating a creditor mismatch when resolving a target [#simulating-a-creditor-mismatch-when-resolving-a-target]
[Resolve target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target) accepts the same `expected_creditor` as a transfer by key. In sandbox, a plain key that isn't a sandbox trigger resolves to the creditor you expect, and `sandbox@key_target_creditor_mismatch.err` resolves to someone else:
```json
{
"tenant_account_id": "your_tenant_account_id",
"format": "plain_key",
"value": "sandbox@key_target_creditor_mismatch.err",
"expected_creditor": {
"document_type": "CC",
"document_number": "1234567"
}
}
```
The call answers `400` and persists no target:
```json
{
"code": "400 Bad Request",
"errors": [
{
"error_code": "target_creditor_mismatch",
"message": "The resolved creditor doesn't match the expected creditor",
"path": "",
"url": ""
}
],
"id": "log_7MkWaFqvfosB8fzHhb1Eql",
"message": "Malformed request"
}
```
The `target_resolution.failed` webhook for the request carries `"state_reason": "target_creditor_mismatch"` and your `expected_creditor`. Send a regular key with the same `expected_creditor` to get a `resolved` response whose `target.creditor` matches it.
# Reversals and reapplications (/docs/guides/breb-participant/sandbox/reverse-and-reapply)
After a transfer settles, the Banco de la República (BanRep) end-of-cycle
reconciliation can still adjust it. There are two outcomes:
* **Reverse** — an operation that looked successful did not actually settle at
the destination (or was returned), so it is reversed. A reversed **incoming**
payment that belongs to a collection is rolled back: its attempt goes from
`successful` to `reversed`, the collection's `paid_amount` and counters are
recomputed, and the collection may reopen.
* **Reapply** — an operation that was marked `failed` actually settled late, so
it is reapplied. A reapplied **incoming** payment settles the attempt that was
finalized as failed: it goes from `failed` to `successful` and is credited to
the collection.
In production these come from the daily reconciliation file. The sandbox lets
you **replay either outcome on one of your own transfers** so you can verify
that your integration handles the resulting webhooks. These endpoints are only
available in the sandbox environment.
## Prerequisites [#prerequisites]
You need an existing transfer and its `end_to_end_id`:
* To **reverse**, the transfer must be in a reversible state (`successful` or
`reapplied`). For a collection, first
[simulate a successful collection payment](/docs/guides/breb-participant/sandbox/collections).
* To **reapply**, the transfer must be in the `failed` state. For a collection,
first simulate a payment with an
[`error`](/docs/guides/breb-participant/sandbox/collections#error-simulation)
so the attempt is finalized as failed.
The `end_to_end_id` is returned in the
[collection attempt simulation](/docs/guides/breb-participant/sandbox/collections#simulating-a-collection-attempt)
response and is present in every `collection.attempt_*` and
`incoming_transfer.*` webhook payload.
## Simulating a reversal [#simulating-a-reversal]
Send a `POST` to reverse the transfer matching the `end_to_end_id`:
```bash
curl -X POST \
https://breb-participant.sandbox.mono.la/api/v1/sandbox/transfers/{end_to_end_id}/reverse \
-H "Authorization: Bearer "
```
You receive a `202 Accepted` response describing the applied outcome:
```json
{
"end_to_end_id": "20260527345544001ENT177994036665555",
"direction": "incoming",
"state": "reversed",
"state_reason": "reversed_by_banrep",
"result": "applied"
}
```
A few seconds later, the webhooks are delivered to your configured URL. For a
reversed incoming payment that belongs to a collection you receive:
* `collection.attempt_reversed` — the attempt that was `successful` is now
`reversed`.
* `collection.updated` — the collection's `paid_amount`, counters and state were
recomputed.
## Simulating a reapplication [#simulating-a-reapplication]
Send a `POST` to reapply the failed transfer matching the `end_to_end_id`:
```bash
curl -X POST \
https://breb-participant.sandbox.mono.la/api/v1/sandbox/transfers/{end_to_end_id}/reapply \
-H "Authorization: Bearer "
```
You receive a `202 Accepted` response:
```json
{
"end_to_end_id": "20260527345544001ENT177994036665555",
"direction": "incoming",
"state": "reapplied",
"state_reason": "reapplied_by_banrep",
"result": "applied"
}
```
For a reapplied incoming payment that belongs to a collection you receive:
* `collection.attempt_successful` — the attempt that was `failed` is now
`successful`.
* `collection.updated` — the collection's `paid_amount`, counters and state were
recomputed.
See the [collection webhooks reference](/docs/api-reference/breb-participant/collection-webhooks)
for the full payloads.
## Response fields [#response-fields]
| Field | Description |
| --------------- | ----------------------------------------------------------------------------------------------------------- |
| `end_to_end_id` | The transfer the outcome was applied to. |
| `direction` | `incoming` (settles a collection) or `outgoing`. |
| `state` | The resulting transfer state: `reversed` or `reapplied`. |
| `state_reason` | The reason recorded for the change. |
| `result` | `applied` when the transfer transitioned now, or `already_applied` when it was already in the target state. |
Calling reverse on an already-reversed transfer (or reapply on an already-reapplied one)
returns `200`-style success with `"result": "already_applied"` and does **not** emit a new
webhook.
## Errors [#errors]
| Status | Code | When |
| ------ | ------------------------ | ----------------------------------------------------------------- |
| `404` | `transfer_not_found` | No transfer matches the `end_to_end_id`. |
| `409` | `transfer_invalid_state` | The transfer cannot be reversed/reapplied from its current state. |
## Expected flow [#expected-flow]
1. Create a transfer to act on (e.g. simulate a collection payment).
2. Take its `end_to_end_id` from the simulation response or a webhook.
3. Call the reverse or reapply sandbox endpoint with that `end_to_end_id`.
4. The API responds with `202 Accepted` and the applied outcome.
5. After a short delay, the `collection.*` webhooks are delivered to your
configured URL.
# Sandbox (/docs/guides/breb-participant/sandbox)
Use the sandbox environment to test your Bre-B integration end-to-end without processing real payments or transfers.
## Base URL [#base-url]
```
https://sandbox.api.cuentamono.com
```
Test exact matches, alternatives, empty results, caching, and failures
Simulate outgoing transfers in the sandbox environment
Simulate collection payments in the sandbox environment
Simulate the reverse and reapply reconciliation outcomes on your transfers
Want to simulate a collection payment without writing code? Open the [Bre-B sandbox
simulator](https://breb.app/uc/sandbox-simulator) — a Mono-hosted web tool that pays any of
your sandbox collection keys with one click. See [Sandbox →
Collections](/docs/guides/breb-participant/sandbox/collections#sandbox-collections-simulator)
for details.
# Collections (/docs/guides/breb-participant/concepts/collections)
A **collection** is an incoming Bre-B payment to one of your tenant accounts. You receive collections by displaying a Bre-B QR code that encodes a payment instruction, or by sharing a payment key registered against the tenant account. Once the payer authorizes the payment in their banking app, Mono receives it from the network, credits your tenant account, and notifies you with a webhook.
Collections are the payin half of Bre-B Participant. Use them whenever your business needs to receive money on Bre-B — checkout, kiosk top-ups, donation pages, or any moment when the payer initiates a bank-to-bank payment.
## Two ways to receive [#two-ways-to-receive]
| Method | Use case |
| :----------------- | :--------------------------------------------------------------------------------------------------------- |
| Generated QR | Each collection has its own QR with a fixed amount and reference. Best for checkout and one-time payments. |
| Static payment key | The same key receives any number of payments. Best for ongoing relationships (utility billing, donations). |
## Lifecycle [#lifecycle]
Every collection moves through a deterministic state machine — `created` → `ready` → optionally `minimum_paid` → `paid`, with `discarded` and `failed` as alternate terminal outcomes. See the [Collection lifecycle](/docs/guides/breb-participant/architecture/collection-states) page for the full state diagram, per-state walkthrough, and the webhook event that maps to each transition.
## Data model [#data-model]
The full schema lives in the [Bre-B Participant collections API reference](/docs/api-reference/breb-participant/collections).
## Webhook events [#webhook-events]
Collections emit events on every state transition. See [Webhook payloads](/docs/guides/breb-participant/webhook-payloads).
## Next steps [#next-steps]
* [QR collection flow](/docs/guides/breb-participant/flows/qr-collection)
* [Tenant accounts](/docs/guides/breb-participant/concepts/tenant-accounts)
* [Webhook signatures](/docs/guides/breb-participant/webhook-signatures)
# Tenant accounts (/docs/guides/breb-participant/concepts/tenant-accounts)
A **tenant account** is a logical account at Mono that belongs to your participant tenant. It holds a balance and acts as the source of outgoing Bre-B transfers and the destination of incoming Bre-B collections. If your product manages balances for many end users, you typically have one tenant account per user; if you operate a single pooled balance, you have one tenant account total.
Every Bre-B operation references a tenant account. Outgoing transfers debit it; collections credit it. Payment keys are registered against tenant accounts.
## Next steps [#next-steps]
* [Payment keys](/docs/guides/breb-participant/concepts/payment-keys)
* [Collections](/docs/guides/breb-participant/concepts/collections)
* [Architecture](/docs/guides/breb-participant/architecture)
# Concepts (/docs/guides/breb-participant/concepts)
Bre-B Participant's API is built around four entities. Each one has its own data model, lifecycle, and webhooks. Understanding them in isolation is the prerequisite for combining them in a [flow](/docs/guides/breb-participant/flows).
Incoming Bre-B payments — QR codes, settlement events.
The aliases that identify destinations on Bre-B — phone, document, email, or alphanumeric.
The resolved destinations returned by the central directory.
The logical accounts that hold balances and source/receive Bre-B operations.
# Targets (/docs/guides/breb-participant/concepts/targets)
A **target** is what you get when you resolve a payment key against the Bre-B central directory: a structured object describing who owns the key, which bank holds the destination account, and what name will be displayed to the payer for confirmation. Targets are the artifact you pass to the outgoing-transfer endpoint when you want to dispatch a payment.
You will encounter targets every time your tenant sends a Bre-B payment. The flow is always: take a payment key, resolve it to a target, show the target's holder name to the payer for confirmation, then dispatch the transfer.
## What a target contains [#what-a-target-contains]
| Field | Meaning |
| :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Holder name | The name registered at the destination bank. Show this to the payer. |
| Holder document | Identification document of the holder (when authorized). |
| Destination participant | The institution that holds the destination account, returned as `participant_nit` and `participant_name` (`null` when Mono does not know the name yet). A Bre-B participant is not necessarily a bank. |
| Resolution timestamp | When the resolution happened. |
## Pinning who should own the key [#pinning-who-should-own-the-key]
When you already know who must receive the money — a refund, for example — tell Mono who you expect behind the key when you resolve it. Send `expected_creditor` (`document_type` + `document_number`) to [Resolve target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target), with any key format. Mono resolves the key only when the registered holder has that document. If someone else owns the key, the request fails with `target_creditor_mismatch` and no target is created, so a `target_id` obtained this way always points to the person you expected.
A transfer created with that `target_id` carries the same `expected_creditor` in its API response and webhooks, exactly as a transfer that sends `expected_creditor` with a key does. See [Outgoing transfer use cases](/docs/guides/breb-participant/use-cases/outgoing-transfers) for both scenarios.
## Next steps [#next-steps]
* [Payment keys](/docs/guides/breb-participant/concepts/payment-keys)
* [Outgoing transfer flow](/docs/guides/breb-participant/flows/outgoing-transfer)
* [Best practices](/docs/guides/breb-participant/best-practices)
# Payment keys (/docs/guides/breb-participant/concepts/payment-keys)
A **payment key** is an alias that identifies a destination on the Bre-B network. Instead of using account numbers (which are long, error-prone, and bank-specific), Bre-B participants register keys against their accounts in the central directory: a mobile phone number (the 10-digit national number only, no country code), a document ID, an email address, an alphanumeric Bre-B-issued identifier, or a merchant code. When someone wants to send you money, they enter your key in their banking app, and Bre-B looks it up and routes the payment to your account.
You will use payment keys in two ways: registering keys for your tenant accounts so they can receive payments, and submitting keys for resolution when your tenant wants to send a payment.
## Key types [#key-types]
| Type | Example | Notes |
| :------------ | :------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Cellphone | 3001234567 | Colombian mobile number only. Exactly 10 digits, starts with `3`. No country code, no `+`, no spaces. Regex `^3\d{9}$`. |
| Document | 21482961 | Identification number only — no `CC`/`CE`/`NIT` prefix, no separators. 1–18 alphanumeric characters (passports accepted). Regex `^[A-Za-z0-9]{1,18}$`. |
| Email | [user@example.com](mailto:user@example.com) | Local part 1–30 chars (no whitespace, no `@`); domain 1–61 chars of `[A-Za-z0-9.-]`. Regex `^[^\s@]{1,30}@[A-Za-z0-9.-]{1,61}$`. |
| Alphanumeric | @user123 | Bre-B-issued identifier. Starts with `@`, followed by 5–20 alphanumeric chars. Useful when the user does not want to reveal personal data. Regex `^@[A-Za-z0-9]{5,20}$`. |
| Merchant code | 0012345678 | 10 digits, starts with `00`. Identifies registered merchants on Bre-B. Regex `^00\d{8}$`. |
## Next steps [#next-steps]
* [Targets](/docs/guides/breb-participant/concepts/targets)
* [Tenant accounts](/docs/guides/breb-participant/concepts/tenant-accounts)
* [Outgoing transfer flow](/docs/guides/breb-participant/flows/outgoing-transfer)
# Card Webhooks (/docs/api-reference/banking/card-webhooks)
Receive notifications when card lifecycle events occur, including plastic card activation, card tokenization OTP requests, and 3DS transaction authentication.
## Events [#events]
Plastic card activation
Card tokenization OTP
3DS transaction OTP
# Card tokenization OTP (/docs/api-reference/banking/card-webhooks/webhook_card_token_otp)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a tokenization OTP is requested for a card.
This occurs when a card is being added to a digital wallet (e.g., Apple Pay, Google Pay)
and an OTP verification is required to complete the tokenization process.
# 3DS transaction OTP (/docs/api-reference/banking/card-webhooks/webhook_threeds_otp)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a 3-D Secure (3DS) OTP is requested for an online card transaction.
This occurs when a cardholder makes an online purchase that requires 3DS authentication
and a one-time password must be delivered to the cardholder to confirm the transaction.
# Plastic card activation (/docs/api-reference/banking/card-webhooks/webhook_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a plastic card activation changes state.
This occurs during the card activation process when the physical card is linked to the system.
The `state` field indicates the result of the activation:
* `activated`: Card was successfully activated
* `failed`: Card activation failed (e.g., error retrieving card details from provider)
* `group_mismatch`: The card's affinity group does not match the expected configuration group
# Cards (/docs/api-reference/banking/cards)
Endpoints for managing cards, cardholders, and digital wallet provisioning. To issue a new card, see [Card Issuing and Prepaid Balance](/docs/api-reference/banking/card-issuing-and-prepaid-balance).
## Cards [#cards]
List cards
Gets card
Update a card
Gets card transactions
Gets information about the card look
## Cardholders [#cardholders]
Create a cardholder
Show a cardholder
Update a cardholder
## PIN Management [#pin-management]
Change plastic card pin
## Plastic Card Activation [#plastic-card-activation]
Generate a URL to activate a plastic card
List plastic card activations
Get plastic card activation
## Digital Wallet Provisioning [#digital-wallet-provisioning]
Apple Pay Tokenization
Google Pay Tokenization
# Gets card (/docs/api-reference/banking/cards/get_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card with sensitive information
# Update a cardholder (/docs/api-reference/banking/cards/update_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update a cardholder
# List plastic card activations (/docs/api-reference/banking/cards/list_plastic_card_activations)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List plastic card activations
# Gets card transactions (/docs/api-reference/banking/cards/get_card_transactions)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card transactions
# Get plastic card activation (/docs/api-reference/banking/cards/get_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get plastic card activation
# Update a card (/docs/api-reference/banking/cards/update_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to update cards
# Get plastic card activation (/docs/api-reference/banking/cards/fetch_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get plastic card activation
# Gets the sensitive details of a card (/docs/api-reference/banking/cards/get_card_look)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the URL of an iframe that displays the card's sensitive details — number,
security code and expiration — so an app or site can show them without ever
handling them. The URL carries a short-lived credential of the cardholder, so
treat it as sensitive: anyone holding it sees the card.
Which fields the iframe renders comes from the card's configuration group; by
default it renders all three.
# Generate a URL to activate a plastic card (/docs/api-reference/banking/cards/build_plastic_card_activation_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a URL to activate a plastic card
# List cards (/docs/api-reference/banking/cards/list_cards)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List cards. the result will include the cards with the paging information
# Change plastic card pin (/docs/api-reference/banking/cards/change_card_pin)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to change plastic card PIN
# Apple Pay Tokenization (/docs/api-reference/banking/cards/provision_apple_pay_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to handle Apple Pay token provisioning. If you want to use this endpoint, communicate with us.
# Show a cardholder (/docs/api-reference/banking/cards/get_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Show a cardholder
# Create a cardholder (/docs/api-reference/banking/cards/create_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a cardholder
# List plastic card activations (/docs/api-reference/banking/cards/get_plastic_card_activations)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List plastic card activations
# Generate an URL to activate a plastic card (/docs/api-reference/banking/cards/activate_plastic_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create an URL to activate a plastic card
# Google Pay Tokenization (/docs/api-reference/banking/cards/provision_google_pay_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to handle Google Pay token provisioning. If you want to use this endpoint, communicate with us.
# Authentication (/docs/api-reference/banking/authentication)
Endpoints for the OAuth 2.0 token lifecycle of Banking partners: exchange your client credentials for an access token, check whether a token is still active, and revoke it.
The scopes a Banking token can carry, and what each one grants, are listed in the [Banking authentication guide](/docs/guides/banking/api/authentication#scopes).
## Endpoints [#endpoints]
Obtain access token
Introspect token
Revoke token
# Obtain access token (/docs/api-reference/banking/authentication/get_auth_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token endpoint supporting client credentials and refresh token grant types.
Use this endpoint to obtain an access token that can be used to authenticate
requests to other API endpoints. The token should be included in the
Authorization header as a Bearer token.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Grant Types:**
* **client\_credentials**: Use client ID and secret to obtain an access token
* **refresh\_token**: Use a refresh token to obtain a new access token. A refresh token works
once: the response carries the one to use next, and the one sent stops working.
# Introspect token (/docs/api-reference/banking/authentication/introspect_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token introspection endpoint (RFC 7662).
Use this endpoint to determine the active state and metadata of an access token or refresh token.
This is useful for resource servers to validate tokens and retrieve their associated information.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* If the token is active, returns token metadata including client\_id, scope, expiration, etc.
* If the token is inactive, expired, or invalid, returns `{"active": false}`
# Revoke token (/docs/api-reference/banking/authentication/revoke_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token revocation endpoint (RFC 7009).
Use this endpoint to revoke an access token or refresh token. After revocation,
the token will no longer be valid for authentication or authorization.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* Returns 200 OK with empty body on successful revocation
* The server responds with 200 OK even if the token was already invalid or expired
# Bank Transfers (/docs/api-reference/banking/bank-transfers)
Endpoints for creating and managing bank transfers.
## Endpoints [#endpoints]
List bank transfers
Create bank transfers
Prepare bank transfers
# Create bank transfers (/docs/api-reference/banking/bank-transfers/create_bank_transfers)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates and sends a batch of bank transfers without the requiring of an additional authorization,
this can be between accounts of the same or different banks,
which allows multiple transfers to be processed together in a single request.
When the server receives the request, it will process it and initiate the transfer of funds
between the source and destination bank accounts. This involves debiting the appropriate amount from
the source account, and crediting the destination account with the transferred funds.
This endpoint is used in cases where the client wants to initiate bank transfers from
their own system or application, without the need to log in to their bank's website or
use a separate banking app. This can be useful for a variety of purposes,
such as to automate financial operations, to streamline the payment process,
or to provide a more convenient user experience.
Considerations:
1. API key role allowed to consume this endpoint is "Administrator".
2. The minimum number of transfers allowed per batch is 1.
3. The maximum number of transfers allowed per batch are 1.000.
# List bank transfers (/docs/api-reference/banking/bank-transfers/list_bank_transfers)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List bank transfers of your organization.
This endpoint allows to filters, sort, and control pagination of the transfers you want to fetch.
Considerations:
* API key roles allowed to consume this endpoint are "Administrator", "Preparer", and "Viewer".
# Prepare bank transfers (/docs/api-reference/banking/bank-transfers/prepare_bank_transfers)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates and leaves a batch of transfers pending for approval,
these bank transfers can be between accounts of the same or different banks,
which allows multiple transfers to be processed together in a single request.
This endpoint is designed to allow transfers to be reviewed and approved by an administrator
user from the Mono app before being sent to the banks.
When the server receives the request, it will process the batch of transfers and create transfer records in our database
for each of them, the transfers will not be executed immediately, instead, the batch will be queued for
review and approval by an administrator user from Mono app.
Considerations:
1. API key roles allowed to consume this endpoint are "Administrator" and "Preparer".
2. The minimum number of transfers allowed per batch is 1.
3. The maximum number of transfers allowed per batch are 1.000.
# Accounts (/docs/api-reference/banking/accounts)
Endpoints for managing bank accounts and retrieving balances.
## Endpoints [#endpoints]
Get accounts
Get account balance
# Get balance (/docs/api-reference/banking/accounts/get_bank_account_balance)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get account balance
# Get accounts (/docs/api-reference/banking/accounts/list_bank_accounts)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get accounts
# Bank Transfer Webhooks (/docs/api-reference/banking/bank-transfer-webhooks)
Receive notifications when bank transfer events occur, including approvals, rejections, fallback routing, and account credits.
## Events [#events]
Bank transfer approved
Bank transfer rejected
Bank transfer fallback routing
Bank transfer changed final state
Account credited
# Bank transfer changed final state (/docs/api-reference/banking/bank-transfer-webhooks/webhook_bank_transfer_change_final_state)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a bank transfer changes its final state
(e.g., from approved to declined or from declined to approved).
The `previous_state` field indicates the state the transfer was in before the change.
# Bank transfer approved (/docs/api-reference/banking/bank-transfer-webhooks/webhook_bank_transfer_approved)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a bank transfer is approved.
The transfer has been processed successfully and the funds have been sent.
# Account credited (/docs/api-reference/banking/bank-transfer-webhooks/webhook_account_credited)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a transfer is successful and the money is in your account.
There are two types of transfers identified by the `type` field:
* `bank_transfer_mono`: A real-time transfer made via Mono's internal system.
* `incoming_wire`: A wire transfer made via ACH. The `extra` field will be empty for this type.
# Bank transfer rejected (/docs/api-reference/banking/bank-transfer-webhooks/webhook_bank_transfer_rejected)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a bank transfer is declined.
The `declination_reason` field will contain a string indicating the reason for the rejection.
# Bank transfer fallback routing (/docs/api-reference/banking/bank-transfer-webhooks/webhook_bank_transfer_fallback_routing)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a bank transfer is attempted with another routing.
This occurs when the original routing fails and the transfer is retried with a fallback routing option.
The `fallback_reason` field indicates the reason the original routing failed.
# Banks (/docs/api-reference/banking/banks)
The list of banks you can send money to, with the code that identifies each one. Use the code to set the destination bank of a bank transfer.
## Endpoints [#endpoints]
Get banks
# Get banks (/docs/api-reference/banking/banks/list_banks)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Every operation that has to name a bank takes its `code`, and this endpoint is where those codes come from: preparing or applying a bank transfer and creating a collection intent, for a banking credential, and sending a payout, for a core one.
It retrieves all the banks we support with the `code` each of them is known by here, and the account types supported by each bank you can transfer to (only when the `kind` parameter is `transfers`).
# Card Issuing and Prepaid Balance (/docs/api-reference/banking/card-issuing-and-prepaid-balance)
Endpoints for issuing banking cards and operating their prepaid balance. Once issued, manage a card with the [Cards](/docs/api-reference/banking/cards) endpoints.
## Endpoints [#endpoints]
Creates a card
Gets card balance operations
# Topping up and withdrawing the card balance (/docs/api-reference/banking/card-issuing-and-prepaid-balance/apply_banking_card_balance_operation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Topping up and withdrawing the card balance
# Creates a card (/docs/api-reference/banking/card-issuing-and-prepaid-balance/create_banking_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to create cards
# Gets card balance (/docs/api-reference/banking/card-issuing-and-prepaid-balance/get_card_balance)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card balance information
# Gets card balance operations (/docs/api-reference/banking/card-issuing-and-prepaid-balance/get_card_balance_operations)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card balance operations
# Card Transaction Webhooks (/docs/api-reference/banking/card-transaction-webhooks)
Receive notifications when card transaction events occur, including authorizations, adjustments, reversals, and refunds.
## Events [#events]
Card authorization approved
Card authorization declined
Card withdrawal
Card credit adjustment
Card debit adjustment
Card force debit
Card authorization reversed
Card refund
Card reconciliation
# Card force debit (/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_force_debit)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a transaction is made without a previous authorization.
This means that a charge will be done without taking into account the account's balance.
There is no `original_transaction_id` nor `previous_values`.
# Card credit adjustment (/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_credit_adjustment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an authorization is updated by a partial refund.
This means a card transaction has been approved and there will be a refund reflected in the
account balance. A partial refund is when part of the purchase value is returned to the account.
# Card authorization reversed (/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_authorization_reversed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a transaction is reversed and the value of the initial charge
is returned fully to the account.
# Card debit adjustment (/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_debit_adjustment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an authorization is updated with a new amount.
This means a card transaction has been approved and charged, and then a new charge is done
to update the previous authorization.
# Card authorization approved (/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_authorization_approved)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction has been authorized.
For example, after making a purchase or a payment with a card. It means a card transaction
has been approved and therefore a charge will be reflected on the account's balance.
# Card withdrawal (/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_withdrawal)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a withdrawal has been authorized.
For example, when the card owner withdraws money from an ATM. Includes a `withdrawal_fee` detail.
# Card refund (/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_refund)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a refund is made to the account.
The value of the refund can be related or not to a previous authorization.
# Card authorization declined (/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_authorization_declined)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction has not been authorized.
Includes a `declination_reason` to understand the reason behind the denied card transaction.
It doesn't reflect any change on the account's balance.
# Card reconciliation (/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_reconciliation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction is reconciled.
Reconciliation occurs when the final settlement amount differs from the original authorization.
The `previous_values` field contains the original amounts before reconciliation.
# Collection Links (/docs/api-reference/banking/collection-links)
Endpoints for creating and managing collection links and their associated intents.
## Endpoints [#endpoints]
List of collection links
Create a collection link
Get a collection link by ID
# Get a collection link by ID (/docs/api-reference/banking/collection-links/get_collection_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
This endpoint allows you to get a collection link by ID
# Create a collection link (/docs/api-reference/banking/collection-links/create_collection_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
This endpoint allows you to create a collection link to share it with anybody.
# List of collection links (/docs/api-reference/banking/collection-links/list_collection_links)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
This endpoint allows you to list the created collection links
# Get collection intent (/docs/api-reference/banking/collection-links/get_collection_intent_by_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get collection intent detail
# Get list of collection intents (/docs/api-reference/banking/collection-links/list_collection_intents_by_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
This endpoint allows you to list the successful collection intents
# Collection Link Webhooks (/docs/api-reference/banking/collection-link-webhooks)
Receive notifications when collection link payment intents are credited or fail.
## Events [#events]
Collection intent credited
Collection intent confirmed
Collection intent failed
# Collection intent credited (/docs/api-reference/banking/collection-link-webhooks/webhook_collection_intent_credited)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection link payment intent has been successfully credited
to the recipient's bank account.
This signifies a successful transaction where the funds have been successfully transferred.
# Collection intent confirmed (/docs/api-reference/banking/collection-link-webhooks/webhook_collection_intent_confirmed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection link payment intent has been confirmed
by the payment provider.
This indicates the payment was approved by the provider but the funds have not yet
been credited to the recipient's bank account.
# Collection intent failed (/docs/api-reference/banking/collection-link-webhooks/webhook_collection_intent_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection link payment intent could not be processed
by the payment provider and the payment was declined.
The `provider_message` field in the `payment` object will show the reason of failure
given by the provider.
# Batch Webhooks (/docs/api-reference/banking/batch-webhooks)
Receive notifications when batch events occur, including duplication, authorization requests, processing, and cancellations.
## Events [#events]
Batch duplicated
Batch authorization requested
Batch sent to process
Batch canceled
# Batch duplicated (/docs/api-reference/banking/batch-webhooks/webhook_batch_duplicated)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a batch was created with all its transfers as duplicated
(with previously registered `entity_id`).
# Batch authorization requested (/docs/api-reference/banking/batch-webhooks/webhook_batch_authorization_requested)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a batch is requested for authorization.
The batch state will be `pending_otp`.
# Batch canceled (/docs/api-reference/banking/batch-webhooks/webhook_batch_canceled)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a batch is canceled.
The batch state will be `cancelled`.
Includes the user who canceled the batch in the `canceled_by_user` field.
# Batch sent to process (/docs/api-reference/banking/batch-webhooks/webhook_batch_sent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a batch is sent to process.
The batch state will be `processing_transactions`.
Includes the user who approved the batch in the `approved_by_user` field.
# Spending Controls (/docs/api-reference/banking/spending-controls)
Endpoints for managing spending controls, rules, and targets.
## Spending Controls [#spending-controls]
Gets all the Card spending controls
Creates a spending control
Get a spending control
Updates a spending control
Deletes a spending control
## Rules [#rules]
Add or update a rule to a spending control
Delete a rule of a spending control
## Targets [#targets]
List spending control's targets
Add target to a spending control
Remove targets to a spending control
# Gets all the Card spending controls (/docs/api-reference/banking/spending-controls/list_spending_controls)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint get all the Card spending controls
# Delete a rule of a spending control (/docs/api-reference/banking/spending-controls/delete_spending_control_rule)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to delete a rule to a spending control
# Add target to a spending control (/docs/api-reference/banking/spending-controls/add_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to add target to a spending control
# Updates a spending control (/docs/api-reference/banking/spending-controls/update_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to update a spending control
# List spending control's targets (/docs/api-reference/banking/spending-controls/list_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to list spending control's targets
# Deletes a spending control (/docs/api-reference/banking/spending-controls/delete_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to delete a spending control
# Get a spending control (/docs/api-reference/banking/spending-controls/get_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to get a spending control
# Creates a spending control (/docs/api-reference/banking/spending-controls/create_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to create spending controls
# Remove targets to a spending control (/docs/api-reference/banking/spending-controls/remove_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to remove targets to a spending control
# Add or update a rule to a spending control (/docs/api-reference/banking/spending-controls/add_or_update_spending_control_rule)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to add or update a rule to a spending control
# Card Transaction Webhooks (/docs/api-reference/core/card-transaction-webhooks)
Receive notifications when card transaction events occur, including authorizations, adjustments, reversals, and refunds.
## Events [#events]
Card authorization approved
Card authorization declined
Card withdrawal
Card credit adjustment
Card debit adjustment
Card force debit
Card authorization reversed
Card refund
Card reconciliation
# Card force debit (/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_force_debit)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a transaction is made without a previous authorization.
This means that a charge will be done without taking into account the account's balance.
There is no `original_transaction_id` nor `previous_values`.
# Card credit adjustment (/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_credit_adjustment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an authorization is updated by a partial refund.
This means a card transaction has been approved and there will be a refund reflected in the
account balance. A partial refund is when part of the purchase value is returned to the account.
# Card authorization reversed (/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_authorization_reversed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a transaction is reversed and the value of the initial charge
is returned fully to the account.
# Card debit adjustment (/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_debit_adjustment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an authorization is updated with a new amount.
This means a card transaction has been approved and charged, and then a new charge is done
to update the previous authorization.
# Card authorization approved (/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_authorization_approved)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction has been authorized.
For example, after making a purchase or a payment with a card. It means a card transaction
has been approved and therefore a charge will be reflected on the account's balance.
# Card withdrawal (/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_withdrawal)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a withdrawal has been authorized.
For example, when the card owner withdraws money from an ATM. Includes a `withdrawal_fee` detail.
# Card refund (/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_refund)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a refund is made to the account.
The value of the refund can be related or not to a previous authorization.
# Card authorization declined (/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_authorization_declined)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction has not been authorized.
Includes a `declination_reason` to understand the reason behind the denied card transaction.
It doesn't reflect any change on the account's balance.
# Card reconciliation (/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_reconciliation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction is reconciled.
Reconciliation occurs when the final settlement amount differs from the original authorization.
The `previous_values` field contains the original amounts before reconciliation.
# Collection Intents (/docs/api-reference/banking/collection-intents)
Endpoints for creating and managing collection intents.
## Endpoints [#endpoints]
Get list of collection intents
Create a collection intent
Get collection intent
# Get list of collection intents (/docs/api-reference/banking/collection-intents/list_collection_intents)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
This endpoint allows you to list your collection intents, optionally filtered by state, account.
and collection link id
# Create a collection intent (/docs/api-reference/banking/collection-intents/create_collection_intent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to create a collection intent
# Get collection intent (/docs/api-reference/banking/collection-intents/get_collection_intent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get collection intent detail
# Ledger (/docs/api-reference/core/ledger)
Endpoints for managing the core ledger system including account holders, accounts, balances, and transactions.
## Tenant [#tenant]
Get the tenant global configuration
## Account Holders [#account-holders]
Creates a third-party account holder
Get account holder information
Find account holder information
Updates a third-party account holder
## Accounts [#accounts]
List of ledger accounts
Creates a ledger account
Updates state of a ledger account
### Balances [#balances]
Get balances
Topup or withdrawal a ledger account balance
### Transactions [#transactions]
Gets account transactions
Gets an account transaction detail
Lists the transactions of every ledger account
### Transfers [#transfers]
Perform a transfer between ledger accounts
Find successful subaccount operation
## Other [#other]
Get FX rates for supported currencies
# Get balances (/docs/api-reference/core/ledger/get_ledger_account_balances)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get account balances
# Get account holder information (/docs/api-reference/core/ledger/get_account_holder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get detailed information about an account holder by ID.
# Perform a transfer between ledger accounts (/docs/api-reference/core/ledger/create_ledger_account_transfer)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Transfer funds smoothly between any two of your subaccounts
### General considerations [#general-considerations]
* This endpoint could be only used for accounts with type `subaccount`.
* Both payer and receiving accounts should be `active`.
* The associated account holders should also be `active`.
### Considerations for same currency transfers [#considerations-for-same-currency-transfers]
* You can specify either `source_amount` or `target_amount`, the other field should be set to null.
It will be set equal to the given amount.
* The currency code of the amount should be equal to the payer and receiving accounts' currency code.
### Considerations for transfers with different currencies [#considerations-for-transfers-with-different-currencies]
* You can specify either `source_amount` or `target_amount`, the other field should be set to null.
It will be calculated according to the given amount, `fx_rate`, and configured fees.
* The currency code of the `source_amount` should be equal to the payer account currency code.
* The currency code of the `target_amount` should be equal to the receiving account currency code.
* The `fx_rate` is the conversion rate from target currency to source currency
For example, with source currency `COP` and target currency `USD`, the rate would be like `4000`.
For source currency `USD` and target currency `COP`, the rate would be like `0.00025`.
The rate includes any FX markup configured (or overridden in the request).
#### How the amounts are calculated [#how-the-amounts-are-calculated]
##### If given target amount [#if-given-target-amount]
`source_amount = target_amount * fx_rate + fixed_fee` (rounded to the appropriate number of decimal places)
##### If given source amount [#if-given-source-amount]
`target_amount = (source_amount - fixed_fee) / fx_rate` (rounded to the appropriate number of decimal places)
#### How the `fx_rate` field works [#how-the-fx_rate-field-works]
If the `fx_rate` field is not given, it's calculated by using the current marked rate
(as returned by the FX rates endpoint) and adding the spread (FX markup).
For example, for a market rate of 4000 and a spread of 1%. `fx_rate` will be `4000 * (1 + 0.01) = 4040`.
If `fx_rate` is given, it's assumed to include the spread already, so the given rate won't be modified in any way.
#### How the `override_fees` field works [#how-the-override_fees-field-works]
There are two fees that apply to this operation:
* A fixed amount fee named `account_to_account_fx`, in the currency of the source amount
* A percentage fee named `account_to_acount_fx_spread`, included in the conversion rate
These fees are configured in the system but can be overridden for individual requests by passing them in `override_fees`.
For `account_to_account_fx`, the `fixed` and `tx_description` fields can be provided to override the defaults.
For `account_to_account_fx_spread`, `percentage` can be overridden.
#### What is returned in `calculated_fees` [#what-is-returned-in-calculated_fees]
For each supported fee (`account_to_account_fx` and `account_to_account_fx_spread`), a fee object is returned in
`calculated_fees` that contains the final amount of the fee.
This amount is always in the currency of the source amount and represents how much was charged for the operation.
The spread fee is calculated from the difference in rates between the actual conversion rate (`fx_rate`) and the market rate.
# Lists the transactions of every ledger account (/docs/api-reference/core/ledger/list_ledger_transactions)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the transactions of every ledger account of the tenant in one feed: its subaccounts and its
tenant-level accounts (`root`, `current`, `fees`). Each transaction carries the `account_id` of
the account it belongs to, so both legs of a transfer between two of your accounts appear, one per
account. Under each account, the feed lists the same transactions as that account's own listing,
`GET /v1/ledger/accounts/{account_id}/transactions`.
`sort[field]` selects how the feed is walked:
* `inserted` (default, ascending by default): the order in which the ledger recorded the
transactions. Use it to extract everything recorded since your last run in one pass: store the
`next_cursor` of the last page and send it on the next run, even when `has_more` is `false`. It
serves transactions about 2 minutes after the ledger records them, so a transaction committed late
is never left behind a cursor; for subaccount transactions, the `ledger_account_transaction_created`
webhook delivers them sooner. Once a page in ascending order reaches that delay, its
`next_cursor` moves up to it, even when the page is empty, so the next run does not read the
same range again. The `transaction_at` and `origin_transaction_at` filters are not accepted in
this order.
* `transaction_at` or `origin_transaction_at` (descending by default): the transactions by that
date, with no delay. A walk in these orders does not come back for a transaction recorded after
it passed that transaction's date, so use `inserted` to extract.
Pagination is by cursor only: `next_cursor` reads the next page of the same request and is
rejected with `invalid_cursor` if it is sent with another sort or other filters. It is kept even
when `has_more` is `false`, and it is `null` only when a first page outside ascending `inserted`
order comes back empty. `page_size` can change between pages. There are no page numbers nor
totals.
`reverted_by_id` and `origin_state` are read when the page is served: a transaction already read
can be reversed or change its origin state later. A reversal arrives as its own transaction, whose
`reverts_id` points to the transaction it reverts.
In a date order, a range on that same date, such as `transaction_at` in `transaction_at` order,
narrows what the pages read: set it to skip history you do not need.
# Get FX rates for supported currencies (/docs/api-reference/core/ledger/get_fx_rates)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns FX rates for the tenant's supported currencies. If no date is provided, returns the latest rates.
If a date is provided, returns historical rates for that date (in UTC).
# Gets an account transaction detail (/docs/api-reference/core/ledger/get_ledger_account_transaction)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets an account transaction detail
# Creates a third-party account holder (/docs/api-reference/core/ledger/create_account_holder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a new third-party account holder and associates it to the tenant.
# List of ledger accounts (/docs/api-reference/core/ledger/list_ledger_accounts)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists all the ledger accounts related to the tenant
Considerations:
1. This endpoint doesn't return the account balance for every account, so you can use the endpoint to get the balance for a specific account `/v1/ledger/account/:id/balances`
2. You couldn't retrieve more than 500 registers in a single request.
# Find successful subaccount operation (/docs/api-reference/core/ledger/find_successful_subaccount_operation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Find successful subaccount operation by external\_id
# Creates a ledger account (/docs/api-reference/core/ledger/create_ledger_account)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a ledger account associated to a third-party account holder.
Considerations:
1. You could previusly check the accepted currency codes in the endpoint `/v1/ledger/tenant/config`.
2. The third-party account holder must be `active`.
# Updates state of a ledger account (/docs/api-reference/core/ledger/update_ledger_account_state)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Updates state of a subaccount type ledger account.
Considerations:
1. You couldn't modify state of the canceled ledger accounts.
2. You couldn't modify accounts where the type is different of a `subaccount`
# Gets account transactions (/docs/api-reference/core/ledger/list_ledger_account_transactions)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets account transactions
# Updates a third-party account holder (/docs/api-reference/core/ledger/update_account_holder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Updates a third-party account holder
# Get the tenant global configuration (/docs/api-reference/core/ledger/get_tenant_config)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the configuration variables tailored for your ledger environment by accessing the tenant configuration endpoint.
# Find account holder information (/docs/api-reference/core/ledger/find_account_holder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Find account holder information by external\_id or document\_type and document\_number
# Topup or withdrawal a ledger account balance (/docs/api-reference/core/ledger/update_ledger_account_balance)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Topup or withdraw a ledger account balance.
Considerations:
1. This endpoint could be only used for accounts with type `subaccount`.
2. The accounts should be `active`.
3. The associated account holder should also be `active`.
4. You can only make operations if the currency code of the amount is the same as the currency code of the account.
# Cards (/docs/api-reference/core/cards)
Endpoints for managing cards, cardholders, and digital wallet provisioning. To issue a new card against a ledger account, see [Ledger Cards](/docs/api-reference/core/ledger-cards).
## Cards [#cards]
List cards
Gets card
Update a card
Gets card transactions
Gets information about the card look
## Cardholders [#cardholders]
Create a cardholder
Show a cardholder
Update a cardholder
## PIN Management [#pin-management]
Change plastic card pin
## Plastic Card Activation [#plastic-card-activation]
Generate a URL to activate a plastic card
List plastic card activations
Get plastic card activation
## Digital Wallet Provisioning [#digital-wallet-provisioning]
Apple Pay Tokenization
Google Pay Tokenization
# Gets card (/docs/api-reference/core/cards/get_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card with sensitive information
# Update a cardholder (/docs/api-reference/core/cards/update_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update a cardholder
# List plastic card activations (/docs/api-reference/core/cards/list_plastic_card_activations)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List plastic card activations
# Gets card transactions (/docs/api-reference/core/cards/get_card_transactions)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card transactions
# Get plastic card activation (/docs/api-reference/core/cards/get_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get plastic card activation
# Update a card (/docs/api-reference/core/cards/update_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to update cards
# Get plastic card activation (/docs/api-reference/core/cards/fetch_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get plastic card activation
# Gets the sensitive details of a card (/docs/api-reference/core/cards/get_card_look)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the URL of an iframe that displays the card's sensitive details — number,
security code and expiration — so an app or site can show them without ever
handling them. The URL carries a short-lived credential of the cardholder, so
treat it as sensitive: anyone holding it sees the card.
Which fields the iframe renders comes from the card's configuration group; by
default it renders all three.
# Generate a URL to activate a plastic card (/docs/api-reference/core/cards/build_plastic_card_activation_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a URL to activate a plastic card
# List cards (/docs/api-reference/core/cards/list_cards)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List cards. the result will include the cards with the paging information
# Change plastic card pin (/docs/api-reference/core/cards/change_card_pin)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to change plastic card PIN
# Apple Pay Tokenization (/docs/api-reference/core/cards/provision_apple_pay_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to handle Apple Pay token provisioning. If you want to use this endpoint, communicate with us.
# Show a cardholder (/docs/api-reference/core/cards/get_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Show a cardholder
# Create a cardholder (/docs/api-reference/core/cards/create_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a cardholder
# List plastic card activations (/docs/api-reference/core/cards/get_plastic_card_activations)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List plastic card activations
# Generate an URL to activate a plastic card (/docs/api-reference/core/cards/activate_plastic_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create an URL to activate a plastic card
# Google Pay Tokenization (/docs/api-reference/core/cards/provision_google_pay_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to handle Google Pay token provisioning. If you want to use this endpoint, communicate with us.
# Tenant Accounts (/docs/api-reference/breb-participant/tenant-accounts)
Endpoints for retrieving tenant accounts associated with your authenticated tenant.
## Endpoints [#endpoints]
List tenant accounts
Get tenant account by ID
# Get tenant account by ID (/docs/api-reference/breb-participant/tenant-accounts/get_a_tenant_account_by_id)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves a single tenant account by its ID.
# List tenant accounts (/docs/api-reference/breb-participant/tenant-accounts/list_tenant_accounts)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists all accounts associated with the authenticated tenant with optional filters.
# Ledger Cards (/docs/api-reference/core/ledger-cards)
Issue a card backed by one of your ledger accounts. Once issued, manage it with the [Cards](/docs/api-reference/core/cards) endpoints.
## Endpoints [#endpoints]
Creates a card
# Creates a card (/docs/api-reference/core/ledger-cards/create_ledger_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to create cards.
Considerations:
1. This endpoint could be only used for accounts with type `subaccount`. To issue a card for
yourself, create a subaccount under the account holder of type `self` and use that one.
# Authentication (/docs/api-reference/breb-participant/authentication)
Endpoints for managing OAuth 2.0 tokens used to authenticate requests to the Bre-B Participant API.
The scopes a Bre-B Participant token can carry, and what each one grants, are listed in the [Bre-B Participant authentication guide](/docs/guides/breb-participant/authentication#scopes).
## Endpoints [#endpoints]
Obtain access token using client credentials or refresh token
Revoke an access or refresh token
Introspect token to check validity and metadata
# Obtain access token (/docs/api-reference/breb-participant/authentication/get_auth_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token endpoint supporting client credentials and refresh token grant types.
Use this endpoint to obtain an access token that can be used to authenticate
requests to other API endpoints. The token should be included in the
Authorization header as a Bearer token.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Grant Types:**
* **client\_credentials**: Use client ID and secret to obtain an access token
* **refresh\_token**: Use a refresh token to obtain a new access token. A refresh token works
once: the response carries the one to use next, and the one sent stops working.
# Introspect token (/docs/api-reference/breb-participant/authentication/introspect_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token introspection endpoint (RFC 7662).
Use this endpoint to determine the active state and metadata of an access token or refresh token.
This is useful for resource servers to validate tokens and retrieve their associated information.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* If the token is active, returns token metadata including client\_id, scope, expiration, etc.
* If the token is inactive, expired, or invalid, returns `{"active": false}`
# Revoke token (/docs/api-reference/breb-participant/authentication/revoke_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token revocation endpoint (RFC 7009).
Use this endpoint to revoke an access token or refresh token. After revocation,
the token will no longer be valid for authentication or authorization.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* Returns 200 OK with empty body on successful revocation
* The server responds with 200 OK even if the token was already invalid or expired
# Sandbox (/docs/api-reference/breb-participant/sandbox)
These endpoints are only available in the sandbox environment.
Visit the [sandbox guide](/docs/guides/breb-participant/sandbox/collections) for detailed explanations on how to simulate a collection attempt in this environment.
## Endpoints [#endpoints]
Simulate a collection attempt
# Simulate a collection attempt (/docs/api-reference/breb-participant/sandbox/create_sandbox_collection_attempt)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Simulates a payment attempt to a collection in the sandbox environment.
**Requirements:**
* The creditor key must exist, be active and needs to be related to a collection
* The associated tenant account must be valid
**Payer information:**
If payer information is not provided, it will be randomly generated.
**Paying a QR:**
Send the QR's `payment_id` to simulate a payment by QR, either as the QR
creation returned it (`CO.COM.CRB.TRXIDP…`) or as the transaction ID read
from the QR itself (`P…`, subfield `01` of the `CO.COM.CRB.TRXID` template).
It must belong to a QR of the creditor key, or the request fails with
`qr_code_not_found`. Leave it out to simulate a payment to the key.
**Error simulation:**
Use the `error` field to simulate different failure scenarios. The payment
will be created successfully but will receive a failure event with the
specified error.
# Payin Webhooks (/docs/api-reference/core/payin-webhooks)
Receive notifications across the whole collection flow: the template that accepts payments,
the intent prepared for a payer, the payin itself, and the batch that delivers the funds to
your funding account.
## Templates [#templates]
A template is a reusable configuration tied to a subaccount. These events tell you when it
can accept payins, when it changes, and when it stops accepting them.
Payin template created
Payin template ready
Payin template updated
Payin template minimum paid
Payin template paid
Payin template discarded
Payin template failed
## Intents [#intents]
An intent is a single expected payment declared before the funds arrive. These events follow
it from creation until it is paid, fails, or expires.
Payin intent created
Payin intent ready to pay
Payin intent awaiting payment
Payin intent attempt failed
Payin intent successful
Payin intent failed
Payin intent expired
## Payins [#payins]
A payin is a payment received from a payer. These events follow the money from the moment it
is registered until it is credited to your subaccount — or rejected.
Payin created
Payin approved in provider
Payin subaccount credited
Payin rejected
Payin failed
## Settlement batches [#settlement-batches]
A settlement batch is the single money movement that delivers the funds of many payins to
your funding account.
Payin settlement batch created
Payin settlement batch processing
Payin settlement batch completed
# Payin intent attempt failed (/docs/api-reference/core/payin-webhooks/webhook_payin_intent_attempt_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payment attempt for a payin intent fails after
authorization, but the intent survives it.
The payload carries the intent back in `ready_to_pay`, so the payer can retry with the
same payment token while the intent lives. The failed attempt itself is reported by
the `payin_rejected` or `payin_failed` webhook. When the provider rejected the payment
made through the intent's dynamic QR, the QR cannot be paid again, and a
`payin_intent_failed` with `qr_code_rejected` follows shortly after.
# Payin intent ready to pay (/docs/api-reference/core/payin-webhooks/webhook_payin_intent_ready_to_pay)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin intent becomes payable: its payment token is
ready.
The payload carries the `payment_tokens` (Bre-B key and QR, when requested) the payer
needs and the `expires_at` after which the intent is no longer payable. It is also
sent when an intent is re-armed after a failed payment attempt.
If a requested QR could not be created, the intent still becomes payable through its
Bre-B key alone: `payment_tokens.qr` comes null and `payment_tokens.qr_error` says why
(`service_failure` — a provider-side failure, nothing to fix on your side).
# Payin intent created (/docs/api-reference/core/payin-webhooks/webhook_payin_intent_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered immediately after a payin intent is accepted and persisted.
The intent is in the `created` state: its payment token is still being prepared, so
`payment_tokens` is null and the payer cannot pay it yet.
# Payin template created (/docs/api-reference/core/payin-webhooks/webhook_payin_template_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered as soon as a payin template is accepted and persisted.
The template is in the `created` state: the payment tokens of the methods that use
them are still being set up, so `payment_tokens` is null and the template cannot
accept payins yet — `payin_template_ready` announces that. Everything else in the
payload is what the template was created with, plus the fields the platform derives
at creation (`id`, `state`, `paid_amount`, the counters and the timestamps).
# Payin intent awaiting payment (/docs/api-reference/core/payin-webhooks/webhook_payin_intent_awaiting_payment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin intent transitions to `awaiting_payment`: a payin
for it was authorized and its payment is in flight.
# Payin template minimum paid (/docs/api-reference/core/payin-webhooks/webhook_payin_template_minimum_paid)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered the first time the cumulative `paid_amount` of a payin
template reaches its `total_minimum_amount`.
The template stays payable up to `total_maximum_amount`.
# Payin rejected (/docs/api-reference/core/payin-webhooks/webhook_payin_rejected)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin is rejected.
`state_reason` explains the business, configuration or payer condition behind the
rejection (e.g. `amount_exceeds_maximum`, `payer_not_allowed`, `template_expired`,
`bank_rejected`). The payer's funds, if any, are returned by the provider.
# Payin intent successful (/docs/api-reference/core/payin-webhooks/webhook_payin_intent_successful)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin intent is spent by its successful payment.
The intent is final: its payment tokens are torn down and it can no longer be paid.
# Payin subaccount credited (/docs/api-reference/core/payin-webhooks/webhook_payin_subaccount_credited)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when the payin funds are credited to the subaccount.
`subaccount_amount` is the amount credited, in the subaccount currency. The delivery
of the funds to the funding account is reported separately by the settlement batch
webhooks.
# Payin template discarded (/docs/api-reference/core/payin-webhooks/webhook_payin_template_discarded)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin template is discarded.
A template is discarded when it is deleted, when it expires or when it is swept for
inactivity. Its payment tokens are torn down with it, so it can no longer be paid;
`prune_reason` explains why.
# Payin template ready (/docs/api-reference/core/payin-webhooks/webhook_payin_template_ready)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin template finishes its payment-token setup and
moves to `ready`.
From this point the template can accept payins. `payment_tokens` carries one entry per
method that issues a token the payer addresses the payment to — for `co_breb`, the
Bre-B key and its QR when one was requested. Methods that need no token (the payer is
sent to the provider instead, as with `co_pse`) have no entry there.
# Payin intent failed (/docs/api-reference/core/payin-webhooks/webhook_payin_intent_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin intent fails for good and there is nothing left
to pay.
`state_reason` explains the cause: a payment that failed on a method that does not
re-arm attempts (`payment_failed`), a payment token that never made it
(`key_registration_failed`, `payment_token_registration_failed`), or a dynamic QR
whose payment the provider rejected (`qr_code_rejected`).
# Payin failed (/docs/api-reference/core/payin-webhooks/webhook_payin_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin cannot be processed because of a system or
provider failure.
Unlike `payin_rejected`, the cause is not a business condition: `state_reason` is
`lock_timeout` when the template lock could not be acquired in time, or `unknown` for
any other internal failure.
# Payin created (/docs/api-reference/core/payin-webhooks/webhook_payin_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin is first persisted, before any confirmation from
the provider.
No funds have moved to the subaccount yet.
With `co_breb` it carries who paid (`instrument.payer`), how much (`amount`) and the
key the payment was received on (`recipient_data`).
With `co_pse` the payin is created with its intent, before the payer pays: it is a
payment attempt, not money received. `instrument.payer` is who is about to pay, and
`transaction_data.cus` is null until the PSE charge is mounted
(`payin_awaiting_payment`).
# Payin settlement batch processing (/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_processing)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a settlement batch is accepted by its delivery provider
and moves to `processing`.
# Payin template updated (/docs/api-reference/core/payin-webhooks/webhook_payin_template_updated)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered after every successful update of a payin template.
The payload is the updated template plus a `previous_values` object holding only the
fields that changed, with their pre-update values. When an update of
`total_minimum_amount`/`total_maximum_amount` also moves the template between
`ready`, `minimum_paid` and `paid`, the transition travels inside this event
(`previous_values.state`) and the separate state webhooks are not sent.
# Payin approved in provider (/docs/api-reference/core/payin-webhooks/webhook_payin_approved_in_provider)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when the provider confirms the payin was approved.
The money exists on the provider side and `funds_delivery_status` is set according to
the program configuration, but the funds have not been credited to the subaccount yet.
# Payin awaiting payment (/docs/api-reference/core/payin-webhooks/webhook_payin_awaiting_payment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin moves to `awaiting_payment`: its charge is
mounted and the payer is authorizing it on their bank's page. The payment is not
confirmed yet — that is `payin_approved_in_provider`.
Only methods whose payin exists before the payment emit it (`co_pse`), and it is
the first event that carries the payment's `transaction_data.cus`.
# Payin settlement batch created (/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when pending payins are grouped into a settlement batch.
The batch is the single money movement that delivers the funds of the grouped payins
to the funding account. `payins` lists what came in and what will be delivered for
each one; the money has not moved yet.
# Payin settlement batch completed (/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_completed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a settlement batch completes: the funds reached the
destination funding account.
There is no failure counterpart: `retryable` is a transient send failure the system
resends on a later run and `failed` is terminal and resolved manually, so neither is
notified.
# Payin intent expired (/docs/api-reference/core/payin-webhooks/webhook_payin_intent_expired)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin intent expires: its window closed with nobody
paying it.
The intent is no longer payable and its `external_id` is free for a new intent.
# Payin template failed (/docs/api-reference/core/payin-webhooks/webhook_payin_template_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when the payment-token setup of a payin template fails and it
moves to `failed`.
A `failed` template can never accept payins. `state_reason` explains why the setup
failed (e.g. `key_already_registered`).
# Payin template paid (/docs/api-reference/core/payin-webhooks/webhook_payin_template_paid)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when the cumulative `paid_amount` of a payin template reaches
its `total_maximum_amount` and the template transitions to `paid`.
A `paid` template accepts no further payins.
# Collection attempt authorization (/docs/api-reference/breb-participant/collection-authorization/webhook_collection_attempt_authorization)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event recording how an authorization resolved. It is emitted for
**every** outcome — you approved, you declined, or Mono could not obtain
a usable answer and applied the collection's `failure_mode` — and the
attempt's `authorization.outcome` says which.
It fires exactly where the attempt carries an `authorization` object, so
an attempt Mono never asked you about produces none: a collection
without `authorization`, one Mono's own rules rejected before you were
asked, or one Mono could not ask about at all because of an incident on
its own side. A request that left Mono and never reached you does
produce one, with `authorization.failure_kind` set to `transport`.
Two things make it worth consuming even though you answered the request
yourself. Under `fail_open` the attempt continues to the bank as if you
had approved it, and if the payment then settles its lifecycle webhook
is indistinguishable from a real approval — this event is the only thing
that tells them apart. And because it reports what Mono *recorded*, it
closes the loop on a response of yours that was subtly malformed: from
your side the request looked answered, while Mono resolved it through
`failure_mode`.
The event goes out before the receiving bank has confirmed, so an
`approved` here can still be followed by
`collection.attempt_unsuccessful` — approving is not settling.
`data` is the collection and the attempt as they stand when this webhook
is delivered, which for this event is normally before the bank has
answered: expect the attempt in `created`, and the collection's
`paid_amount` and `successful_attempts` not yet moved. It is delivered
independently of the attempt's lifecycle webhook, so neither order is
guaranteed; correlate the two by `attempt.id`.
This event is additional, never a replacement: the attempt's own
lifecycle webhook still fires. It is opt-in per webhook subscription.
# Ledger Account Transaction Webhooks (/docs/api-reference/core/ledger-account-transaction-webhooks)
Receive notifications when ledger account transactions are recorded for subaccounts.
## Events [#events]
Ledger account transaction created
# Ledger account transaction created (/docs/api-reference/core/ledger-account-transaction-webhooks/webhook_ledger_account_transaction_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a new ledger account transaction is recorded for a subaccount.
It includes the transaction details and the origin entity that caused the transaction.
The `details` field structure varies depending on the `origin` value.
Possible origins:
* `subaccount_operation`: A subaccount operation (topup, withdrawal, account-to-account transfer, etc.)
* `payout`: A payout to an external bank account
* `card_transaction`: A card transaction (purchase, ATM withdrawal, etc.)
* `card_presentment_payment`: Final settlement of an international card transaction
* `fee`: A fee charge
* `synced_bank_transaction`: A synced bank transaction from an external bank
* `payin`: A payin collected into a subaccount
# Collection Webhooks (/docs/api-reference/breb-participant/collection-webhooks)
Receive notifications when collection state changes or payment attempts occur.
## Events [#events]
Collection created
Collection ready to receive payments
Collection failed
Collection discarded
Collection updated
Collection minimum amount paid
Collection fully paid
Payment attempt successful
Payment attempt unsuccessful
Payment attempt reversed
# Collection created (/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a new collection is created.
This event is sent immediately after the collection is accepted and persisted.
At this point, the Bre-B key registration process has not started yet.
The collection will be in `created` state.
# Collection ready (/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_ready)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection is ready to receive payments.
This event is sent after the Bre-B key has been successfully registered in the DICE.
The collection will be in `ready` state and will include the `key_type` and `key_value`
fields with the registered key information.
# Collection updated (/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_updated)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection is updated by the tenant.
This event is sent when the tenant updates collection fields via the PATCH endpoint.
The `data.previous_values` object contains the previous values of all fields that were changed.
**Important:** If the update causes a state change (e.g., updating `total_minimum_amount`
triggers a transition to `minimum_paid`), `previous_values` will include the previous `state`.
This event does NOT trigger separate state change webhooks like `collection.minimum_paid`.
Only the `collection.updated` webhook is sent.
# Collection paid (/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_paid)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection reaches the `paid` state.
This occurs when the `paid_amount` equals the `total_maximum_amount`.
The collection is now complete and will no longer accept payments.
# Collection discarded (/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_discarded)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection is discarded.
This event is sent when:
* A collection is automatically discarded due to expiration (`state_reason`: "expired")
* A collection is automatically discarded due to inactivity (`state_reason`: "inactivity")
* A collection is deleted by the tenant via the DELETE endpoint (`state_reason`: "deleted")
The collection will be in `discarded` state.
# Collection failed (/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection fails during the key registration process.
This event is sent when the Bre-B key registration in the DICE fails.
The collection will be in `failed` state with a `state_reason` explaining the failure.
The `keys` array will be empty.
# Collection attempt successful (/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_successful)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payment attempt against a collection is successful.
The collection's `paid_amount` is updated with the new accumulated amount,
`successful_attempts` is incremented, and the collection state may change to
`paid`, `minimum_paid`, or remain `ready`/`minimum_paid` depending on the amounts.
# Collection minimum paid (/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_minimum_paid)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection reaches the `minimum_paid` state.
This occurs when the `paid_amount` reaches or exceeds the `total_minimum_amount`.
The collection can still receive more payments until it reaches `paid` state.
# Collection attempt unsuccessful (/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_unsuccessful)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payment attempt against a collection fails or is rejected.
* `rejected`: The payment was rejected by Mono based on collection rules (expired, disabled, amount limits, etc.)
* `failed`: The payment failed due to external reasons not controlled by Mono (bank errors, timeouts, etc.)
The collection's `failed_attempts` is incremented. The collection state remains unchanged.
# Collection attempt reversed (/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_reversed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a previously successful payment attempt against a
collection is reversed (the underlying incoming transfer was reversed by Bre-B
reconciliation).
The reversed amount is subtracted from the collection's `paid_amount`,
`successful_attempts` is decremented, and the collection state is recalculated
from the remaining `paid_amount`: a `paid` or `minimum_paid` collection may
reopen as `ready` or `minimum_paid`. The attempt transitions to `reversed`
with `state_reason: transfer_reversed`.
# Banks (/docs/api-reference/core/banks)
The list of banks you can send money to, with the code that identifies each one. Use the code to set the destination bank of a payout.
## Endpoints [#endpoints]
Get banks
# Get banks (/docs/api-reference/core/banks/list_banks)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Every operation that has to name a bank takes its `code`, and this endpoint is where those codes come from: preparing or applying a bank transfer and creating a collection intent, for a banking credential, and sending a payout, for a core one.
It retrieves all the banks we support with the `code` each of them is known by here, and the account types supported by each bank you can transfer to (only when the `kind` parameter is `transfers`).
# Payouts (/docs/api-reference/core/payouts)
Endpoints for creating payouts and resolving payout targets.
## Endpoints [#endpoints]
Creates a payout
Get payout by ID
Find successful payout by external ID
### For Bre-B method [#for-bre-b-method]
Endpoints exclusive to the `co_breb` payout method.
Resolves a Bre-B target
# Creates a payout (/docs/api-reference/core/payouts/create_payout)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Endpoint to create payouts.
Considerations:
1. The source account must be of type `subaccount`.
2. The source account and its account holder should be `active`.
# Resolves a Bre-B target (/docs/api-reference/core/payouts/resolve_breb_target)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Endpoint to resolve a Bre-B target for payouts synchronously
# Get payout by ID (/docs/api-reference/core/payouts/get_payout)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Endpoint to get a payout by its ID
# Find successful payout by external ID (/docs/api-reference/core/payouts/find_successful_payout)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Find successful payout by external\_id
# Target discovery (/docs/api-reference/breb-participant/target-discovery)
Use target discovery when you know the beneficiary and destination account but
do not know which Bre-B key points to them. Start with the bank catalog to get a
canonical ACH code, then submit the beneficiary data to discovery.
## Endpoints [#endpoints]
List or search banks
Get a bank by canonical ACH code
Discover a target
For the end-to-end workflow, see the [target discovery
guide](/docs/guides/breb-participant/flows/target-discovery).
# Discover target (/docs/api-reference/breb-participant/target-discovery/discover_target)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Discovers a Bre-B key for a beneficiary from the data you already know about
them, and returns a usable `Target` **only** when the key can be proven to
belong to that same person and to point to that same bank account.
Both outcomes are `200`:
* `state=discovered` — a single-use Target was created for the requested bank
and account. It can be used in exactly one outgoing transfer; repeating
discovery produces a new Target.
* `state=not_discovered` with `state_reason=no_exact_target` — no Target could
be created for that destination. This does not necessarily mean the person
has no other key.
Both carry `same_person_keys`, possibly empty: keys confirmed to belong to the
same person that point somewhere else. Discovery stops at the first exact
target, so in a `discovered` response this list only reflects matches observed
before the winner and is not exhaustive. They are **not** Targets. To use one,
pick it explicitly and call `POST /api/v1/targets/resolve`, then compare the
bank and account ending of the fresh Target with the ones you showed the payer
before authorizing the transfer — discovery neither reserves the key nor
guarantees it still points to the observed destination.
Use `GET /api/v1/banks` to turn an approximate bank name into the canonical
`account.bank_code`. Discovery only accepts the canonical ACH code.
The endpoint is rate limited per OAuth client and per tenant. Exceeding either
limit answers `429 rate_limit_exceeded` with a `Retry-After` header.
# Get bank (/docs/api-reference/breb-participant/target-discovery/get_bank)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Exact lookup by canonical ACH code. A code absent from the catalog answers
`404 bank_not_found`; the catalog only lists banks discovery accepts.
# List banks (/docs/api-reference/breb-participant/target-discovery/list_banks)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists every bank target discovery accepts, in canonical order.
Pass `q` to search by name or alias. The search tolerates accents,
punctuation, partial names and typos, and it separates confidence from
suggestion:
* `match` is a single bank and `data` is empty when the query matched an ACH
code, a canonical name or an alias exactly. This is the only result you may
use without asking the payer to choose.
* `match` is `null` and `data` carries ordered suggestions when the query only
matched by prefix or similarity — **even if there is exactly one**. Show them
and let the payer pick.
* A search with no results is a `200` with `match: null` and an empty `data`.
Either way, what you send to discovery is the canonical `ach_code` of the bank
that was selected.
# Fees (/docs/api-reference/core/fees)
Endpoints to manage your fee policies — the pricing rules of the fees you charge your users — and to list every fee charged on your accounts, yours and Mono's. See the [Fees](/docs/guides/core/concepts/fees) and [Fee policies](/docs/guides/core/concepts/fees/policies) guides for the concepts behind them.
## Fee policies [#fee-policies]
Creates one or many fee policies atomically
Lists your fee policies, with filters and pagination
Gets a fee policy by ID
Updates the price or settings of a fee policy
Deletes a fee policy
## Per account [#per-account]
Lists the fee policies that apply to a subaccount, and which one wins
## Fees [#fees]
Lists the fees charged on your accounts — yours and Mono's
# Lists fee policies (/docs/api-reference/core/fees/list_fee_policies)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists your fee policies — the ones pricing the fees you charge your users — newest first,
paginated. Deleted policies are never listed.
#### Filters [#filters]
* `fee_type` — the kind of fee the policies price.
* `target_type` — the kind of entity they apply to.
* `target_id` — one entity in particular; requires its `target_type`.
* `skip` — whether they are skipped (paused).
To see which policies apply to one specific account and which of them wins, use
[the account's fee policies endpoint](/docs/api-reference/core/fees/list_account_fee_policies)
instead.
# Creates fee policies (/docs/api-reference/core/fees/create_fee_policies)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates one or more fee policies at once.
A **fee policy** tells Mono how much to charge your users for a kind of fee — for example,
the fee a subaccount pays for a bank-transfer payout — and whom it applies to: every
account of your users or customers (`tenant`), one program, one account holder or a
single subaccount. When a fee is charged, the most specific policy for the charged account
wins, so you can set a general price and override it only where you need to.
#### How the request works [#how-the-request-works]
Send the policies under `policies`, always as a list, even for a single one. The batch is
**atomic**: if any policy is rejected, none is created, and the error points at the item
that failed with its position in the list (for example `/policies/1/strategies/COP`). The
created policies come back under `policies`, in the order you sent them.
#### Keep in mind [#keep-in-mind]
* Your **base or global policies** — one per fee type, with `target_type: tenant` — are
set up by Mono. You can update them but not create or delete them: they are your default
price, and the policies you create for programs, account holders or accounts override it.
* Only **one active policy per fee type and target**. To change the price of a target that
already has one, update that policy: creating another is rejected with
`fee_policy_already_exists`.
* `strategies` decides the **currencies the policy covers**, and it must stay within the
currencies its target supports. The policy only applies to the currencies it includes;
for the others, the next less specific policy applies.
* `processing_type` and `processing_subtype` must be among the ones the fee type supports.
* A new policy never starts out skipped (paused); use the update endpoint to skip it
afterwards.
See the [fee policies guide](/docs/guides/core/concepts/fees/policies) for how policies are
resolved and priced, and the [conditions reference](/docs/guides/core/concepts/fees/conditions)
for the properties and operators the rules can use.
# Gets a fee policy (/docs/api-reference/core/fees/get_fee_policy)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets one of your fee policies by its ID, with its full strategies.
The lookup is limited to your own policies: an ID that doesn't exist, belongs to another
tenant or was deleted is reported as `fee_policy_not_found`.
# Lists fees (/docs/api-reference/core/fees/list_fees)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the fees charged on your accounts, newest first, paginated. Use it to reconcile what
was charged, when, on which account and under which rule.
#### The two sides [#the-two-sides]
Two kinds of fees appear here, told apart by `owner`:
* `tenant` — the fees **you charge your own users**, debited on their subaccounts and priced
with your fee policies. They are your revenue.
* `mono` — the fees **Mono charges you** for using the platform, debited on your own
accounts. They are your cost.
Omit `owner` to list both.
#### What each fee carries [#what-each-fee-carries]
The policy that priced it, the rule and the calculation that produced the amount, its
state along the charging lifecycle and the entity that triggered it — a payout, a payin or
a transfer between subaccounts. Filter by any of them, or by the date range of the
transaction that charged the fee.
See the [fees guide](/docs/guides/core/concepts/fees) for the kinds of fees and their
lifecycle.
# Deletes a fee policy (/docs/api-reference/core/fees/delete_fee_policy)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes one of your fee policies. From then on it no longer applies — the next less
specific policy takes over the accounts it covered — and it is no longer listed, so its
`target_type`, `target_id` and `fee_type` combination is free for a new policy.
Your **base policies** (`target_type: tenant`) can't be deleted: they guarantee every fee
you charge has a price. Only the policies overriding them — the ones targeting a program,
an account holder or an account — can.
Returns `204 No Content`. Fees already charged with the policy are kept and still reference
it.
# Lists the fee policies that apply to an account (/docs/api-reference/core/fees/list_account_fee_policies)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Shows, for one of your subaccounts, every fee policy that could price a fee on it and
which one actually does. Use it to answer "how much will this account be charged, and
why?", or to check the effect of a policy you just created or skipped.
#### How the response is built [#how-the-response-is-built]
The policies come grouped by fee type. Within each group they go from the most to the
least specific target — `account`, `account_holder`, `program`, `tenant` — and exactly one
has `applied: true`: the most specific one that is not skipped or paused. Skipped (paused)
policies are listed with `applied: false`, so it is visible why a policy is not applying.
Only the policies with a strategy in the account's currency bear on the account, and only
the fee types with at least one policy are present.
#### Scope [#scope]
Only subaccounts have policies applied on them. Any other kind of account, or an account
that isn't yours, is reported as `account_not_found`. Narrow the response to a single fee
type with `fee_type`.
See [who a policy applies to](/docs/guides/core/concepts/fees/policies#who-a-policy-applies-to)
in the fee policies guide.
# Updates a fee policy (/docs/api-reference/core/fees/update_fee_policy)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Changes the price or the settings of one of your fee policies. Only the fields you send
are replaced; the ones you leave out keep their value.
#### What can change [#what-can-change]
* `strategies`, when sent, **replaces the whole map**: send every currency the policy must
keep. Removing a currency is a matter of sending the map without it. The new strategies
are checked against the policy's target the same way as on creation.
* `skip: true` skips (pauses) the policy: Mono ignores it while resolving, so the next
less specific one applies. Your base policies (`target_type: tenant`) can never be
skipped: they are the last resort guaranteeing every fee has a price.
* `summary`, `transaction_description`, `processing_type` and `processing_subtype` change
freely, within what the fee type supports.
#### What can't [#what-cant]
`fee_type`, `target_type` and `target_id` identify the policy and are immutable. To move a
policy to another target, delete it and create a new one.
Changes apply to the fees charged from then on. Fees already charged keep the price they
were calculated with. See the [fee policies guide](/docs/guides/core/concepts/fees/policies).
# Outgoing Transfers (/docs/api-reference/breb-participant/outgoing-transfers)
Endpoints for creating outgoing transfers, resolving targets, and retrieving transfer information.
## Endpoints [#endpoints]
Resolve target
Create outgoing transfer batch
List outgoing transfers
Get outgoing transfer by ID
Find successful outgoing transfer
# Find successful outgoing transfer (/docs/api-reference/breb-participant/outgoing-transfers/find_an_successful_outgoing_transfer)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Find an outgoing transfer by external\_id.
# Resolve target (/docs/api-reference/breb-participant/outgoing-transfers/resolve_target)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a target resolution request to resolve a payment destination.
Supports multiple resolution formats:
* **plain\_key**: Resolve by cellphone, email, identification, alphanumeric key, or merchant code
* **image**: Resolve by scanning a QR code image (base64-encoded)
* **emvco**: Resolve by raw EMVCo QR code data
* **emvco\_b64**: Resolve by base64-encoded EMVCo QR code data
The response includes the resolved target information including creditor details and account information.
Send `expected_creditor` to resolve only when the key belongs to the person you expect: on a
document mismatch the request fails with `target_creditor_mismatch` and no target is returned.
# Get outgoing transfer by ID (/docs/api-reference/breb-participant/outgoing-transfers/get_an_outgoing_transfer_by_id)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves a single outgoing transfer by its ID
# List outgoing transfers (/docs/api-reference/breb-participant/outgoing-transfers/list_outgoing_transfers)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves a paginated list of outgoing transfers for the authenticated tenant.
**Filtering options:**
* `state`: Filter transfers by their current state (e.g., created, processing, successful, failed)
* `transaction_at`: Filter transfers by creation datetime range in UTC using `from` and `until` datetime parameters
* `tenant_account_id`: Filter transfers by the source tenant account
* `external_id`: Filter transfers by their external identifier
* `key_type`: Filter transfers by the target's key type (e.g., identification, cellphone, email)
* `key_value`: Filter transfers by the target's key value
* `metadata`: Filter transfers by metadata key-value pairs
**Pagination:**
Use `page_number` and `page_size` parameters to control pagination. Results are ordered by creation date in descending order (newest first).
# Create outgoing transfer batch (/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a batch of outgoing transfers to be processed by the Bre-B system.
To create a batch of outgoing transfers, you must provide either a target or a query
for each transfer.
If a transfer has a validation error, it will be returned as a rejected transfer without
affecting the other transfers or the batch processing.
# Programs (/docs/api-reference/core/programs)
A program is the configuration your ledger accounts run under: the currencies it operates and the features it enables. Each ledger account reports its program as `program_id`.
## Endpoints [#endpoints]
Lists programs
# Lists programs (/docs/api-reference/core/programs/list_programs)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists your programs, oldest first. The order is deterministic — `id` breaks
ties between programs created at the same instant — so paging through a list
that does not change between requests never repeats or skips a program.
Filters combine with **AND**: `features` and `supported_currencies` each
require a program to satisfy every value passed, and the per-field text
filters narrow further. The one exception is `search`, which matches `name`
**or** `description`, and still ANDs against everything else. Filtering does
not go below the feature level — there is no filter by method or by
configuration value.
A filter that matches no program returns an empty page, not an error.
# Outgoing Transfer Batch Webhooks (/docs/api-reference/breb-participant/outgoing-transfer-batch-webhooks)
Receive notifications when batch transfer status changes.
## Events [#events]
Outgoing transfer batch created
Outgoing transfer batch processing
Outgoing transfer batch processed
# Outgoing transfer batch created (/docs/api-reference/breb-participant/outgoing-transfer-batch-webhooks/webhook_outgoing_transfer_batch_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a new outgoing transfer batch is created.
This event is sent immediately after the batch is accepted and all transfers are validated.
# Outgoing transfer batch processing (/docs/api-reference/breb-participant/outgoing-transfer-batch-webhooks/webhook_outgoing_transfer_batch_processing)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer batch starts processing.
This event indicates that the batch has entered the processing state.
# Outgoing transfer batch processed (/docs/api-reference/breb-participant/outgoing-transfer-batch-webhooks/webhook_outgoing_transfer_batch_processed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer batch finishes processing.
This event indicates that all transfers in the batch have reached a terminal state (successful or failed).
# Payins (/docs/api-reference/core/payins)
Endpoints for collecting money into your accounts. A template is a reusable configuration
that defines how payments are collected, an intent is a single expected payment declared
before the funds arrive, and a payin is the incoming payment itself. A settlement batch is
the movement that delivers the funds of many payins to your funding account.
## Templates [#templates]
A template is tied to a subaccount and defines the accepted payment methods, amount limits,
expected payers and expiration for the payins collected against it.
Lists templates
Creates a template
Gets a template
Updates a template
Deletes a template
## Intents [#intents]
An intent is a single expected payment declared before the funds arrive. It is created
against a template and tracked until it is paid, fails, or expires.
Lists intents
Creates an intent
Gets an intent
Finds the successful intent for an external\_id
## Payins [#payins]
A payin is a payment received from a payer against one of your templates.
Lists payins
Gets a payin
## Settlement batches [#settlement-batches]
A settlement batch is the single money movement that delivers the funds of many payins to
your funding account.
Lists settlement batches
Gets a settlement batch
## Brands [#brands]
A brand is what the hosted checkout wears: the merchant name, logo, and color a payer sees when paying one of your templates. Every template wears a brand.
Lists brands
Gets a brand
## Sandbox [#sandbox]
These endpoints are only available in the sandbox environment. Use them to simulate a payer
paying a template or an intent, and to provoke the failures you need to test.
Simulates a payment (sandbox)
# Lists payins (/docs/api-reference/core/payins/list_payins)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the requesting client's payins, paginated. Results are ordered by
creation date descending (newest first) unless `sort` is provided, and can
be narrowed with the filters below.
Every payin is listed, whether or not its payment completed: a payment that
has not reached the bank yet (`created`, `awaiting_payment`), one the payer
abandoned and one that was rejected all show up next to the approved ones.
Read `state` — or filter by it — to tell them apart.
# Updates a template (/docs/api-reference/core/payins/update_template)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Updates specific fields of a payin template. Scoped to the requesting client.
The update is presence-aware: an omitted field keeps its current value and an
explicit null clears a nullable field. `payin_data`, `form_customization` and
`method_params` are partial objects merged field by field; `expected_payers`
replaces the whole list.
Expected payers can only be set on a template one of whose methods validates
them: on a template that only offers `co_pse` the request fails with
`payin_expected_payers_not_supported`.
`method_params` only takes `co_pse`, whose `payer` is the suggested payer the
hosted checkout pre-fills: a complete payer replaces it, null removes it and an
omitted payer keeps it. On a template that does not offer `co_pse` the request
fails with `payin_unsupported_method_params`.
A template cannot be updated while still `created` (its Bre-B setup may be in
flight), in `discarded` or `failed` state, as a consumed (`paid`) single-use
template, or while being pruned (`prune_state` not null). The update is
serialized with payin processing through the template lock.
Updating `total_minimum_amount`/`total_maximum_amount` may transition the
template state (`ready` ⇄ `minimum_paid`, → `paid`, or back out of `paid`).
The transition is included in the `payin_template_updated` webhook — which every
successful update emits, with the previous values of the changed fields — and
does NOT trigger the separate state-change webhooks.
# Lists brands (/docs/api-reference/core/payins/list_payin_brands)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the requesting client's payin brands, paginated. The default brand comes
first and the rest follow by name. Deleted brands are not returned.
Each brand carries `templates_count`, how many of the client's live templates
wear it — the same reference that keeps a brand from being deleted.
# Lists templates (/docs/api-reference/core/payins/list_templates)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the requesting client's payin templates, paginated. Results are ordered
by creation date descending (newest first) unless `sort` is provided, and can
be narrowed with the filters below. Deleted templates are not returned.
# Simulates a payment (sandbox) (/docs/api-reference/core/payins/simulate_payin_payment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Simulates a payer transferring to one of your templates or intents, running
the provider's real rail: the payin is created, authorized and funded
exactly as a production payment would be, and its outcome reaches you over
the same webhooks.
**Sandbox only.** In any other environment the endpoint answers `403`.
A payment to a template is *unreferenced* — the template accepts many, so
the request brings its own `method` and `amount`. A payment to an intent is
*referenced* — the intent was declared for it and already fixes both.
Use `error` to provoke a failure. Leaving it out simulates a payment that
succeeds, though the payment authorization may still refuse it on its own
validations — an amount outside the template's bounds, a payer it does not
expect. That refusal happens before a payin exists, so the response comes
back with no `payin_id` and the reason instead. Provoking those rejections
is exactly how you test them.
# Gets a brand (/docs/api-reference/core/payins/get_payin_brand)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets a payin brand by its ID. Scoped to the requesting client.
# Lists intents (/docs/api-reference/core/payins/list_intents)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the client's intents, paginated and optionally filtered.
# Gets an intent (/docs/api-reference/core/payins/get_intent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns a single intent by its ID.
# Gets a settlement batch (/docs/api-reference/core/payins/get_settlement_batch)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets a settlement batch by its ID, along with the payins it settles and
what each one contributed. Scoped to the requesting client.
The response is the same shape the `settlement_batch_*` webhooks deliver.
# Creates a template (/docs/api-reference/core/payins/create_template)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a template: a reusable configuration, tied to a subaccount, that defines how
payins are collected (accepted methods, amount limits, expected payers and expiration).
The request is idempotent on `external_id` — repeating it returns the existing template
instead of creating a duplicate.
`subaccount_id` must reference an account of type `subaccount`.
# Finds the successful intent for an external_id (/docs/api-reference/core/payins/find_successful_intent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the single intent in `successful` state whose `external_id` matches the
given tenant-side identifier, answering "was this operation paid?" without
storing Mono ids. At most one such intent exists per client.
# Gets a payin (/docs/api-reference/core/payins/get_payin)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets a payin by its ID. Scoped to the requesting client.
# Gets a template (/docs/api-reference/core/payins/get_template)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets a payin template by its ID. Scoped to the requesting client.
# Lists settlement batches (/docs/api-reference/core/payins/list_settlement_batches)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the requesting client's settlement batches, paginated. Results are
ordered by creation date descending (newest first) unless `sort` is
provided, and can be narrowed with the filters below.
A row reports how many payins the batch settles; fetch the batch by its ID
to see them.
# Deletes a template (/docs/api-reference/core/payins/delete_template)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Soft-deletes a payin template: marks it as deleted so it stops accepting payins and
frees its `external_id` for reuse. Scoped to the requesting client.
A template can be deleted only when it holds no money — no successful payins
(`paid_amount` = 0) and none in flight (`pending_amount` = 0) — and is in a deletable
state: not `created`, `discarded` or `failed`, and not being pruned (`prune_state` is
null). The deletion is serialized with payin processing through the template lock.
# Creates an intent (/docs/api-reference/core/payins/create_intent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates an intent: a single expected payment declared before the funds arrive,
configured from a template the client owns or created templateless against one
of its subaccounts (`template_id` and `subaccount_id` are mutually exclusive).
The request is idempotent on `external_id` — repeating it returns the existing
intent instead of creating a duplicate.
# Target Resolution Webhooks (/docs/api-reference/breb-participant/target-resolution-webhooks)
Receive notifications when target resolution status changes.
## Events [#events]
Target resolution created
Target resolution resolved
Target resolution failed
# Target resolution created (/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a new target resolution request is created.
This event is sent immediately after the target resolution request is accepted and persisted.
# Target resolution resolved (/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_resolved)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a target resolution request is successfully resolved.
This event includes the resolved target with creditor and account details. When the tenant has person data masking enabled (`mask_person_data`), the creditor's `full_name`, `document_number` and `creditor_account.number` are delivered masked (e.g. `Jua*** Pér***`, `*****789`, `*****4321`).
# Target resolution failed (/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a target resolution request fails.
This event includes the state\_reason field explaining why the resolution failed. When the request carried an `expected_creditor` that the resolved creditor did not match, the reason is `target_creditor_mismatch` and no target was persisted.
# Spending Controls (/docs/api-reference/core/spending-controls)
Endpoints for managing spending controls, rules, and targets.
## Spending Controls [#spending-controls]
Gets all the Card spending controls
Creates a spending control
Get a spending control
Updates a spending control
Deletes a spending control
## Rules [#rules]
Add or update a rule to a spending control
Delete a rule of a spending control
## Targets [#targets]
List spending control's targets
Add target to a spending control
Remove targets to a spending control
# Gets all the Card spending controls (/docs/api-reference/core/spending-controls/list_spending_controls)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint get all the Card spending controls
# Delete a rule of a spending control (/docs/api-reference/core/spending-controls/delete_spending_control_rule)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to delete a rule to a spending control
# Add target to a spending control (/docs/api-reference/core/spending-controls/add_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to add target to a spending control
# Updates a spending control (/docs/api-reference/core/spending-controls/update_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to update a spending control
# List spending control's targets (/docs/api-reference/core/spending-controls/list_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to list spending control's targets
# Deletes a spending control (/docs/api-reference/core/spending-controls/delete_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to delete a spending control
# Get a spending control (/docs/api-reference/core/spending-controls/get_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to get a spending control
# Creates a spending control (/docs/api-reference/core/spending-controls/create_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to create spending controls
# Remove targets to a spending control (/docs/api-reference/core/spending-controls/remove_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to remove targets to a spending control
# Add or update a rule to a spending control (/docs/api-reference/core/spending-controls/add_or_update_spending_control_rule)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to add or update a rule to a spending control
# Collections (/docs/api-reference/breb-participant/collections)
Endpoints for creating collections to receive payments through the Bre-B network.
## Endpoints [#endpoints]
### Collections [#collections]
Create collections
List collections
Get collection by ID
Find successful collection
Update a collection
Delete a collection
#### Attempts [#attempts]
List all collection attempts
List collection attempts by collection
Get collection attempt by ID
#### QRs [#qrs]
Create QR code
List QR codes
Get QR code
Cancel QR code
# Delete a collection (/docs/api-reference/breb-participant/collections/delete_collection)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes a collection by marking it as deleted and scheduling key cancellation.
**Restrictions:**
* A collection can only be deleted if it has no successful payments (`paid_amount` = 0)
* Cannot delete collections in `discarded` or `failed` states
**Behavior:**
* If `paid_amount` > 0, the deletion is rejected with `has_payments` error
* If `paid_amount` = 0, the collection is marked as deleted and the associated
Bre-B key will be canceled asynchronously
* A `collection.discarded` webhook will be sent when the key cancellation completes
# Cancel QR code (/docs/api-reference/breb-participant/collections/delete_qr)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Cancels a dynamic QR code. Only dynamic (single\_use) QR codes can be cancelled.
Static (multiple\_use) QR codes cannot be cancelled. A QR code that was already paid,
expired or rejected cannot be cancelled either.
# Get a collection by ID (/docs/api-reference/breb-participant/collections/get_collection)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves a specific collection by its ID.
# List QR codes (/docs/api-reference/breb-participant/collections/list_qr_codes)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns all QR codes associated with a collection.
Results are ordered by creation date (newest first).
# List collection attempts by a given collection ID (/docs/api-reference/breb-participant/collections/list_collection_attempts_by_collection)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists collection attempts for a specific collection with pagination and filtering.
# Create QR code (/docs/api-reference/breb-participant/collections/create_qr)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a QR code for a collection. The collection must be in `ready` or `minimum_paid` state.
The QR usage is determined by the `usage_mode` parameter:
* `single_use`: Creates a dynamic QR (requires amount, has expiration)
* `multiple_use`: Creates a static QR (amount is optional)
When an amount is provided, it must comply with the collection's rules:
* Must be within `minimum_attempt_amount` and `maximum_attempt_amount` range
* Must not exceed remaining balance (`total_maximum_amount - paid_amount`)
# Create collections (/docs/api-reference/breb-participant/collections/create_collections)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates one or more collections for receiving payments through Bre-B network.
Each collection will be created in `created` state and a Bre-B key will be
requested from the provider. Once the key is registered, a webhook notification
will be sent and the collection state will change to `ready`.
The response categorizes collections into:
* `created`: Successfully created collections
* `duplicated`: Collections with external\_id that already exist (returns existing collection)
* `rejected`: Collections that failed validation
A collection carrying `authorization` requires the collection authorization
feature to be enabled for the tenant; without it that collection is rejected
with `authorization_feature_not_enabled`.
# List collections (/docs/api-reference/breb-participant/collections/list_collections)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists collections with pagination and filtering.
# List all collection attempts (/docs/api-reference/breb-participant/collections/list_all_collection_attempts)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists collection attempts with pagination and filtering.
# Find successful collection (/docs/api-reference/breb-participant/collections/find_successful_collection)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Find a collection by external\_id.
# Update a collection (/docs/api-reference/breb-participant/collections/update_collection)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Updates specific fields of a collection.
**Restrictions:**
* Cannot update collections in `discarded` or `failed` states or with `prune_status` not null
* Only the following fields can be updated:
* `nickname`
* `enabled`
* `expires_at` (must be in the future)
* `minimum_attempt_amount` (only for multiple\_use)
* `maximum_attempt_amount` (only for multiple\_use)
* `total_minimum_amount` (may trigger state change)
* `total_maximum_amount` (must be >= current `paid_amount`, may trigger state change)
* `expected_payers` (the list sent replaces the current one as a whole; an empty
list or `null` accepts any payer and an absent key leaves it untouched)
* `authorization` (the whole block is replaced, never merged; `null` disables it
and an absent key leaves it untouched)
**Authorization:**
* Setting `authorization` requires the collection authorization feature to be
enabled for the tenant, and is otherwise refused with `authorization_feature_not_enabled`
* `authorization: null` is always accepted, so configuration can always be removed
* While the feature is off the stored configuration is left untouched
**State changes for `total_minimum_amount`:**
* If `paid_amount` >= new `total_minimum_amount`, state changes to `minimum_paid` (if currently `ready`)
* If `paid_amount` \< new `total_minimum_amount`, state changes to `ready` (if currently `minimum_paid`)
**State changes for `total_maximum_amount`:**
* If `ready` or `minimum_paid`, and new `total_maximum_amount` == `paid_amount`, state changes to `paid`
* If `paid` and new `total_maximum_amount` is null, state changes to `ready` or `minimum_paid`
(depending on whether `total_minimum_amount` is set and reached)
* If `paid` and new `total_maximum_amount` > `paid_amount`, state changes to `ready` or
`minimum_paid` (depending on whether `total_minimum_amount` is set and reached)
**Webhooks:**
* A `collection.updated` webhook is sent after the update
* If a state change occurs due to the update, it is included in the `collection.updated` webhook
but does NOT trigger separate state change webhooks
# Get QR code (/docs/api-reference/breb-participant/collections/get_qr)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns a specific QR code by ID.
# Get a collection attempt by ID (/docs/api-reference/breb-participant/collections/get_collection_attempt_by_id)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves a specific collection attempt by its ID.
# Authentication (/docs/api-reference/core/authentication)
Endpoints for the OAuth 2.0 token lifecycle of Core partners: exchange your client credentials for an access token, check whether a token is still active, and revoke it.
The scopes a Core token can carry, and what each one grants, are listed in the [Core authentication guide](/docs/guides/core/authentication#scopes).
## Endpoints [#endpoints]
Obtain access token
Introspect token
Revoke token
# Revoke token (/docs/api-reference/core/authentication/core_revoke_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token revocation endpoint (RFC 7009).
Use this endpoint to revoke an access token or refresh token. After revocation,
the token will no longer be valid for authentication or authorization.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* Returns 200 OK with empty body on successful revocation
* The server responds with 200 OK even if the token was already invalid or expired
# Obtain access token (/docs/api-reference/core/authentication/core_get_auth_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token endpoint supporting client credentials and refresh token grant types.
Use this endpoint to obtain an access token that can be used to authenticate
requests to other API endpoints. The token should be included in the
Authorization header as a Bearer token.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Grant Types:**
* **client\_credentials**: Use client ID and secret to obtain an access token
* **refresh\_token**: Use a refresh token to obtain a new access token. A refresh token works
once: the response carries the one to use next, and the one sent stops working.
# Introspect token (/docs/api-reference/core/authentication/core_introspect_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token introspection endpoint (RFC 7662).
Use this endpoint to determine the active state and metadata of an access token or refresh token.
This is useful for resource servers to validate tokens and retrieve their associated information.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* If the token is active, returns token metadata including client\_id, scope, expiration, etc.
* If the token is inactive, expired, or invalid, returns `{"active": false}`
# Outgoing Transfer Webhooks (/docs/api-reference/breb-participant/outgoing-transfer-webhooks)
Receive notifications when outgoing transfer status changes.
## Events [#events]
Outgoing transfer created
Outgoing transfer target resolved
Outgoing transfer processing
Outgoing transfer held
Outgoing transfer sent to BREB provider
Outgoing transfer successful
Outgoing transfer failed
# Outgoing transfer processing (/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_processing)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer starts processing.
# Outgoing transfer held (/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_held)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer is held for review or additional processing.
# Outgoing transfer created (/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a new outgoing transfer is created within a batch.
# Outgoing transfer target resolved (/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_target_resolved)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when the target for an outgoing transfer is successfully resolved.
This event includes the resolved target. When the tenant has person data masking enabled (`mask_person_data`), the creditor's `full_name`, `document_number` and `creditor_account.number` are delivered masked (e.g. `Jua*** Pér***`, `*****789`, `*****4321`).
# Outgoing transfer failed (/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer fails.
This event includes the state\_reason field explaining why the transfer failed.
# Outgoing transfer sent to Bre-B provider (/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_send_to_breb_provider)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer is sent to the Bre-B provider for processing.
# Outgoing transfer successful (/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_successful)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer completes successfully.
This is the terminal state indicating the transfer was processed successfully by the Bre-B provider. Even when the tenant has person data masking enabled (`mask_person_data`), this event delivers the creditor's `full_name` and `document_number` in full, so the tenant can reconcile who it paid; `creditor_account.number` stays masked. The same applies to `outgoing_transfer.reapplied`.
# Card Webhooks (/docs/api-reference/core/card-webhooks)
Receive notifications when card lifecycle events occur, including plastic card activation, card tokenization OTP requests, and 3DS transaction authentication.
## Events [#events]
Plastic card activation
Card tokenization OTP
3DS transaction OTP
# Card tokenization OTP (/docs/api-reference/core/card-webhooks/webhook_card_token_otp)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a tokenization OTP is requested for a card.
This occurs when a card is being added to a digital wallet (e.g., Apple Pay, Google Pay)
and an OTP verification is required to complete the tokenization process.
# 3DS transaction OTP (/docs/api-reference/core/card-webhooks/webhook_threeds_otp)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a 3-D Secure (3DS) OTP is requested for an online card transaction.
This occurs when a cardholder makes an online purchase that requires 3DS authentication
and a one-time password must be delivered to the cardholder to confirm the transaction.
# Plastic card activation (/docs/api-reference/core/card-webhooks/webhook_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a plastic card activation changes state.
This occurs during the card activation process when the physical card is linked to the system.
The `state` field indicates the result of the activation:
* `activated`: Card was successfully activated
* `failed`: Card activation failed (e.g., error retrieving card details from provider)
* `group_mismatch`: The card's affinity group does not match the expected configuration group
# Payins vs other Mono products (/docs/guides/core/concepts/payins/comparison)
Mono has three products that receive money, and picking the wrong one costs an integration. They are not tiers of the same thing: each lands the money in a different kind of account, and that choice decides what you can build on top of it. This page compares them so you can tell whether Payins is what you need, and what it gives you that the others do not.
If you already know you are collecting into Core subaccounts, skip this page and start with [Templates](/docs/guides/core/concepts/payins/templates).
## At a glance [#at-a-glance]
| | **Core Payins** | [Banking collection links](/docs/guides/banking/concepts/collection-links) | [Bre-B Participant collections](/docs/guides/breb-participant/concepts/collections) |
| :--------------------------------- | :---------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------- | :---------------------------------------------------------------------------------- |
| **Rails** | Bre-B today; PSE and cards in progress | PSE only | Bre-B only |
| **Multi-currency** | In progress | COP only | COP only |
| **Credits into ledger accounts** | Yes | Not supported | Not supported |
| **Fees charged to subaccounts** | Yes, [configurable](/docs/guides/core/concepts/payins/fees) to your needs | No | No |
| **Getting the money to your bank** | In [settlement batches](/docs/guides/core/concepts/payins/settlement/funding) to your funding account | Depends on the ACH cycles | Immediately, since Bre-B is an instant-payment system |
| **API-only integration** | Yes | Yes | Yes |
| **Mono-hosted checkout** | [Yes](/docs/guides/core/concepts/payins/templates#hosted-checkout-or-api-only) | Yes | No |
| **The product also does** | Ledger, cards, spending controls, payouts | ACH / Transfiya / Turbo transfers, cards | Outgoing Bre-B transfers, payment-key directory resolution |
## What each one is for [#what-each-one-is-for]
### Core Payins [#core-payins]
Use it when your product **holds balances for end users** and money needs to arrive into them. It is the collection half of Core: every payment is validated against rules you defined ahead of time, credited to a subaccount on the double-entry ledger, charged its [fees](/docs/guides/core/concepts/payins/fees), and delivered to your bank account in [settlement batches](/docs/guides/core/concepts/payins/settlement). Wallets, marketplace seller balances, and any product where "who does this money belong to?" has an answer inside your ledger.
What it gives you that the others do not: the four-entity model ([templates](/docs/guides/core/concepts/payins/templates) that define the rules once, [intents](/docs/guides/core/concepts/payins/intents) that pre-identify a specific expected payment, [payins](/docs/guides/core/concepts/payins/payins) with their full lifecycle, and settlement batches), plus per-payment fee overrides and a payer allowlist.
### Banking collection links [#banking-collection-links]
Use it when you want a customer to **pay you from their bank account over PSE**, and the money can land in your organization's Banking account. You create a link, share it, and the payer completes the payment on Mono's hosted form or on a frontend you build yourself. The classic e-commerce checkout case in Colombia.
### Bre-B Participant collections [#bre-b-participant-collections]
Use it when your product plugs into **Bre-B directly as a participant tenant** at Mono, and you also need the other half of the network: dispatching outgoing transfers and resolving payment keys through the central directory. Collections credit tenant accounts, and you manage the keys and QR codes yourself.
## Choose by scenario [#choose-by-scenario]
| Your case | Product |
| :--------------------------------------------------------------------------------- | :------------------------------------------------------------------------- |
| End users top up a wallet balance you keep for them. | **Core Payins** |
| Marketplace where each seller has a balance you later pay out. | **Core Payins** (with Core payouts) |
| E-commerce checkout paid from a Colombian bank account over PSE. | [Banking collection links](/docs/guides/banking/concepts/collection-links) |
| You send and receive on Bre-B as a participant, and manage keys and the directory. | [Bre-B Participant](/docs/guides/breb-participant/concepts/collections) |
| You need each payment pre-identified before the money arrives. | **Core Payins** intents, or a per-payment link or QR on the other two |
## Using more than one [#using-more-than-one]
They compose. A consumer wallet, for example, can keep balances on the Core ledger while collecting over more than one rail. See the [use-case mapping](/docs/guides/why-mono) for the combinations Mono customers run today. What you should not do is split the same balance across two products: pick where the money lands first, and the rest follows.
## Next steps [#next-steps]
* [Payins concepts](/docs/guides/core/concepts/payins): the model behind this product.
* [Templates](/docs/guides/core/concepts/payins/templates): start here if Payins is your fit.
* [Payin flows](/docs/guides/core/flows/payins): end-to-end use cases.
# Fees (/docs/guides/core/concepts/payins/fees)
A **fee** is what **you** charge your own subaccount for a payin: a fixed amount, a percentage, or both, deducted from the money credited when the payment arrives. It is your revenue on the collection, not a cost imposed on you. You set the numbers, Mono applies them on the ledger automatically and reports what was charged per payin in `calculated_fees`.
Set them once in your fee policies and they apply to every payin; override them per template or per intent when a campaign, a customer, or a single payment needs different numbers.
## The fee type [#the-fee-type]
Payins has one fee type, `payin`: it is charged for receiving the payment and applied when the payin is credited to the subaccount. Every `calculated_fees` you see contains only this type.
## Where fees are configured [#where-fees-are-configured]
Your default fees are **fee policies** of type `payin`: pricing rules you manage through the API for every account of your users or customers, a program, an account holder or a single subaccount (a fixed amount and a percentage per currency and, when you need them, rules that price a method or a segment differently). If you use payins, Mono creates your base policy; from there you adjust and override it yourself. See [Fee policies](/docs/guides/core/concepts/fees/policies).
## Overriding fees per template or intent [#overriding-fees-per-template-or-intent]
`payin_data.fee_override` lets a template, or an individual intent, replace your program default for its own payins:
```json
{
"payin_data": {
"fee_override": [
{
"type": "payin",
"fixed": { "amount": 50000, "currency": "COP" },
"percentage": "0.01",
"tx_description": "Collection fee: premium campaign"
}
]
}
}
```
Three rules govern overrides:
1. Every `fixed` amount must be denominated in the currency of the template's subaccount, and be `>= 0`.
2. The override replaces the default **as a whole** per fee type; there is no partial merge. An intent's override wins over its template's.
3. The values are **frozen** on each payin at creation: changing a template's override later does not alter the fees of payins already created.
## Reading what was charged [#reading-what-was-charged]
Each payin reports what it charged in `calculated_fees`, keyed by fee type. That is the field to reconcile against: `subaccount_amount` is already net of it, so the end user sees the amount after your fee, and the difference is what you kept.
## Next steps [#next-steps]
* [Fee policies](/docs/guides/core/concepts/fees/policies): where the default fees are set, and for whom.
* [Payins](/docs/guides/core/concepts/payins/payins): where `calculated_fees` lives.
* [Crediting the subaccount](/docs/guides/core/concepts/payins/settlement/subaccount): the ledger movement fees ride on.
# Payins (/docs/guides/core/concepts/payins)
**Payins** is how money gets into your Core subaccounts. It covers both **top-ups** (an end user loads their own wallet balance) and **collections** (your business charges a customer). You define the rules of what you expect to receive, Mono gives the payer something to pay to (today a Bre-B key or QR code), and every payment that arrives is validated, credited to the right subaccount, and delivered to your bank account, with a webhook at every step.
Mono has three products that receive money, for three different account models. **Core
Payins** (this section) collects into **Core subaccounts**. Use it when your product manages
balances on the Core ledger. [Collection
links](/docs/guides/banking/concepts/collection-links) collect into **Banking accounts**
through PSE payment links. [Bre-B Participant
collections](/docs/guides/breb-participant/concepts/collections) receive Bre-B payments
directly on **tenant accounts**. See [Payins vs other Mono
products](/docs/guides/core/concepts/payins/comparison) for the full comparison.
## The four entities [#the-four-entities]
A payin never travels alone. Four entities, each with its own lifecycle and webhooks, take a payment from "expected" to "money in your bank account":
| Entity | What it is |
| :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Template** | A reusable configuration tied to a subaccount. It defines the methods available to receive payments, the amount limits on what it receives, the allowed payers, the expiration and the custom checkout, among others. |
| **Intent** | A single expected payment, declared before the funds arrive. Use it when each payment must be pre-identified. |
| **Payin** | The received payment itself: who paid, how much, and where the money is in its journey. |
| **Settlement batch** | The single money movement that delivers the funds of many payins to your funding account. |
Two supporting concepts complete the picture: **payment tokens** (what the payer actually pays to) and **fees** (what you charge your subaccounts per payin). And inside every payin live three sub-entities of its own (the **instrument**, the **recipient data**, and the **transaction data**), explained in [Payins](/docs/guides/core/concepts/payins/payins).
## The four amounts [#the-four-amounts]
Every payin carries four amounts, one per leg of the money's journey. Each leg has its own currency, so the model supports collecting in one currency and accounting in another:
| Field | What it measures | Denominated in |
| :------------------ | :----------------------------------------------------- | :----------------------------- |
| `amount` | What the payer paid, as settled by the payment method. | The method's currency |
| `merchant_amount` | The merchant-facing value of the payment. | The template's currency |
| `subaccount_amount` | What is credited to the subaccount. | The subaccount's currency |
| `funding_amount` | What is delivered to your funding account. | The funding account's currency |
**Where multi-currency is headed.** Today the only live method is
[Bre-B](/docs/guides/core/concepts/payins/methods/co-breb), which settles in COP, so the four
amounts normally coincide. The goal of the four-legged model is full multi-currency
collection: receive a payment in the method's currency while pricing it, crediting the
subaccount, and settling your funding account each in their own currency, with Mono handling
the conversions between legs (at a rate reserved in advance or at Mono's live rate). When
multi-currency collection arrives, it lands on these same four fields without breaking
changes.
## Payment methods [#payment-methods]
Each payin is received through a payment method. Today one method is available:
| Method | Rail | Currency | Payer pays with |
| :-------- | :-------------------------------------------------------------------------------------------- | :------- | :--------------------- |
| `co_breb` | [Bre-B](/docs/guides/core/concepts/payins/methods/co-breb), Colombia's instant-payment scheme | COP | A Bre-B key or QR code |
The structure grows by method: when a new method launches, it appears in [Methods](/docs/guides/core/concepts/payins/methods) with its own page, and templates accept it in `available_methods` without any change to the entities above.
## Explore [#explore]
How this compares with Banking collection links and Bre-B Participant collections.
The reusable configuration that defines how payments are collected.
A single expected payment, declared before the funds arrive.
The received payment: states, amounts, sub-entities, and rejection reasons.
What the payer pays to. Each method issues its own kind; today, Bre-B keys and QR codes.
Two movements: when the subaccount is credited, and how Mono delivers the funds to you.
The payin fee and how to override it per template.
The payment methods you can collect with, side by side.
End-to-end use cases: top-ups, static-key collection, per-payment intents, settlement.
The 22 events across the payin lifecycle and when each one arrives.
## Next steps [#next-steps]
* [Templates](/docs/guides/core/concepts/payins/templates): start here: everything begins with a template.
* [Flows](/docs/guides/core/flows/payins): pick the flow that matches your business case.
* [Payins API reference](/docs/api-reference/core/payins): every endpoint, request, and response.
# Payment tokens (/docs/guides/core/concepts/payins/payment-tokens)
A **payment token** is the thing you put in front of the payer: the address the money is sent to. Each [payment method](/docs/guides/core/concepts/payins/methods) defines its own kind of token. Some methods need none at all, because the payer is sent to the provider's own flow instead of paying to an address. That is why `payment_tokens` is a per-method map: one entry per accepted method that issues a token, and no entry for methods that do not.
Today, with Bre-B as the only live method, tokens come in two forms: a **key** (in Colombia, a *llave*: a short alias like a cellphone number or a code, registered in the Bre-B directory) and an optional **QR code** that encodes the same destination so the payer can scan instead of type.
Templates and intents both carry tokens, and the difference between them is the difference between a poster on a wall and a ticket with your name on it: a template's token is shared by every payer; an intent's token identifies one expected payment.
## The Bre-B token [#the-bre-b-token]
`payment_tokens.co_breb` has three parts:
| Field | What it is |
| :---------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key_type` | The kind of key: `cellphone`, `email`, `alphanumeric`, `identification`, or `merchant_code`. Assigned by the network when the key is registered. It is not known at creation time. |
| `key_value` | The key itself: what the payer enters in their bank's app to address the transfer. |
| `qr` | The QR generated for the key, when one was requested (`create_qr: true`). |
The QR object carries everything needed to display or embed it: the EMV payload in plain text (`emvco`), a rendered image in base64 (`image`, with `image_width` and `error_correction_level`), the provider's `payment_id`, its `usage_mode`, and, for dynamic QRs, the fixed `amount` and `expires_at`.
You can request a custom key alias with `method_params.co_breb.custom_key_value`, and override the QR's merchant data (`merchant_category_code`, `receiver_city`, `receiver_postal_code`) with `qr_code_data`. If omitted, Mono generates the key and uses your program's configuration.
## Key per template or key per intent [#key-per-template-or-key-per-intent]
`method_params.co_breb.key_per` decides at which level the Bre-B key is minted, and it shapes the whole collection model:
| `key_per` | Token lives on | QR kind | Payments are… |
| :------------------- | :------------- | :----------------------------------------- | :----------------------------------------------------------------- |
| `template` (default) | The template | Static: multiple use, no amount | Anonymous until they arrive: any payer, any number of payments. |
| `intent` | Each intent | Dynamic: single use, fixed amount, expires | Referenced: each payment maps back to the intent that expected it. |
A template with `key_per: "template"` receives payments directly. It cannot host intents, because a shared key cannot tell which intent a payment belongs to. A template with `key_per: "intent"` has **no key of its own** (`payment_tokens` stays `null`): every key is minted when an intent is created. A templateless intent gets its own dedicated key too.
## Token setup is asynchronous [#token-setup-is-asynchronous]
Registering a token with the method's network takes a callback round-trip, which is why templates and intents are born in `created`. Neither is payable until the token is confirmed:
* The template moves to `ready` and fills `payment_tokens` when every pending token is confirmed (announced by `payin_template_ready`).
* The intent moves to `ready_to_pay` and fills `payment_tokens` (announced by `payin_intent_ready_to_pay`).
If registration exhausts its retries, the template ends `failed` (`key_registration_failed`, `key_already_registered`) or the intent ends `failed` (`key_registration_failed`, `payment_token_registration_failed`).
## Tokens are torn down [#tokens-are-torn-down]
A token only lives while its owner can be paid. When a template is [pruned or deleted](/docs/guides/core/concepts/payins/templates/pruning), or an intent reaches a final state (`successful`, `failed`, or `expired`), Mono deregisters its tokens with the method's provider. The owner's `prune_state` / `prune_reason` fields track that teardown.
## Next steps [#next-steps]
* [Bre-B](/docs/guides/core/concepts/payins/methods/co-breb): the rail behind the keys and QRs.
* [Templates](/docs/guides/core/concepts/payins/templates): where static tokens live.
* [Intents](/docs/guides/core/concepts/payins/intents): where dynamic tokens live.
# Fee policies (/docs/guides/core/concepts/fees/policies)
A **fee policy** is the pricing rule behind a fee: it says how much to charge for one kind of fee and whom the price applies to. You keep a general price for every account of your users or customers and override it wherever you need — a program with its own plan, an enterprise client with negotiated terms, a single account you want to exempt. When a fee is charged, Mono picks the most specific policy that covers the account and applies it.
Policies replace the need to ask Mono every time your pricing changes. You create, adjust, skip (pause) and remove them through the API, and you can always check which policy an account ends up with — and why.
Policies only price the fees **you** charge your users. The fees Mono charges you follow your
commercial terms and are visible, but not editable, in the [fees
listing](/docs/api-reference/core/fees/list_fees).
## Before you start [#before-you-start]
You will need:
* A Core API key with the `fees` scope (`fees:readonly` is enough to read policies and fees).
* Core fee policies enabled for you as a tenant. Mono enables them and creates your **base policies** — one per fee type in use — when you onboard.
* To know your programs, account holders and accounts by their identifiers (`pro_`, `acch_`, `lacc_`): they are what a policy targets.
## Anatomy of a policy [#anatomy-of-a-policy]
| Field | What it is |
| :-------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fee_type` | The kind of fee it prices: `bank_transfer`, `payin`, `account_to_account_fx`… See [Fee types](/docs/guides/core/concepts/fees#fee-types). |
| `target_type` + `target_id` | Whom it applies to (the target): every account of your users or customers (`tenant`), a `program`, an `account_holder` or an `account`, and which one. |
| `strategies` | The price, per currency: the rules to evaluate and the calculation to fall back to. See [How the price is calculated](#how-the-price-is-calculated). |
| `processing_type`, `processing_subtype` | How the fee lands in the ledger, among the options the fee type allows. See [Fee types and their processing](#fee-types-and-their-processing). |
| `summary`, `transaction_description` | A description for you, and the description your users see on the fee's transaction. |
| `skip` | Skips (pauses) the policy without deleting it: Mono ignores it and the next less specific one applies. |
## Who a policy applies to [#who-a-policy-applies-to]
A policy targets one of four levels. From the least to the most specific:
1. **`tenant`** — the base or global policy. It covers every account of your users or customers and is the price of last resort.
2. **`program`** — every account in a [program](/docs/guides/core/concepts/program).
3. **`account_holder`** — every account of a single account holder: one of your users or customers.
4. **`account`** — a single subaccount.
When a fee is charged on an account, Mono looks at the policies of that fee type that cover the account — its own, its holder's, its program's and the base or global one (`tenant`) — and applies **the most specific one that is not skipped or paused**:
```
tenant the base or global policy — always exists, default price
└── program overrides it for every account of the program
└── account_holder overrides both for every account of the holder
└── account overrides everything for this account only
```
Three rules complete the picture:
* **Only one active policy per fee type and target.** To change the price of a program, a holder or an account that already has a policy, update it; creating a second one is rejected.
* **The base policy always exists.** Mono creates your base or global (`tenant`) policies. You can update them, but not create, skip (pause) or delete them — they guarantee every fee has a price.
* **A policy only covers the currencies it includes.** If an account's currency is not among the policy's `strategies`, that policy does not apply to it and the next less specific one does.
### Which currencies a policy must cover [#which-currencies-a-policy-must-cover]
The currencies in `strategies` must stay within the ones the target supports:
| Target | Currencies the strategies may cover |
| :--------------- | :--------------------------------------------------------------- |
| `tenant` | Exactly every currency your programs support — no more, no less. |
| `program` | Any subset of the program's currencies. |
| `account_holder` | Any subset of the currencies of the holder's accounts. |
| `account` | Exactly the account's currency. |
A `program` or `account_holder` policy can therefore price only one currency and leave the others to the base policy — useful when a plan differs in COP but not in USD.
### Seeing which policy applies [#seeing-which-policy-applies]
[`GET /v1/ledger/accounts/{account_id}/fees/policies`](/docs/api-reference/core/fees/list_account_fee_policies) lists, for a subaccount, every policy that bears on it, grouped by fee type and ordered from the most to the least specific, with `applied: true` on the one that wins. Skipped (paused) policies appear with `applied: false`, so you can see why a policy is not taking effect. Use it after creating or skipping a policy to confirm the result.
## How the price is calculated [#how-the-price-is-calculated]
Each currency in `strategies` has its own **strategy**: an ordered list of rules (`rules`) and a default calculation (`default_calculation`). Mono evaluates the rules in order and uses the calculation of the first one whose conditions all hold; if none matches, it uses the default calculation. A strategy with no rules always uses the default calculation, which is the simplest way to set a flat price.
### Calculation [#calculation]
A **calculation** is a fixed amount plus a percentage of the amount of the operation:
| Field | Meaning |
| :----------------- | :------------------------------------------------------------------------------------------------------------------------- |
| `fixed` | Fixed part, in the smallest unit of the currency. `900000` in COP is 9.000,00. |
| `percentage` | Variable part, as a decimal fraction sent as a string. `"0.004"` is 0.4%. |
| `percentage_mode` | `open` (default): no limit. `capped`: the variable part never exceeds `percentage_limit`. `floored`: it is never below it. |
| `percentage_limit` | The cap or floor, in the smallest unit. Required for `capped` and `floored`, absent for `open`. |
Both parts are always explicit — send `0` and `"0"` for the parts you do not charge — so a fee is never priced at zero by omission.
For example, on a COP payout of 2.000.000,00, a calculation with `fixed: 90000`, `percentage: "0.004"`, `percentage_mode: "capped"` and `percentage_limit: 500000` charges 900,00 fixed plus 8.000,00 variable (0.4%); with a larger payout, the variable part would stop growing at 5.000,00.
### Rules and conditions [#rules-and-conditions]
A **rule** applies its calculation when all of its **conditions** hold. Conditions compare a property of the operation — its method, its amount, its currency, a metadata field of the account — against a value or another property. This is how you price a payout rail differently, give a discount above an amount or exempt accounts tagged in a certain way. The full list of operators and of the properties each fee type can look at lives in [Conditions](/docs/guides/core/concepts/fees/conditions).
### Fee types and their processing [#fee-types-and-their-processing]
Each fee type allows specific values of `processing_type` and `processing_subtype` for the fees you charge:
| Fee type | `processing_type` | `processing_subtype` | Notes |
| :----------------------------- | :---------------- | :------------------- | :--------------------------------------------------- |
| `bank_transfer` | `inline` | `add_posting` | The fee rides on the payout's transaction. |
| `bank_transfer_gmf` | `inline` | `add_posting` | The GMF pass-through, priced as a percentage. |
| `payin` | `inline` | `add_posting` | Deducted from what is credited to the subaccount. |
| `account_to_account_fx` | `inline` | `add_posting` | The fixed conversion charge, in the source currency. |
| `account_to_account_fx_spread` | `inline` | `upcharge` | The margin, embedded in the exchange rate. |
## Managing policies [#managing-policies]
| Action | Endpoint |
| :------------------------------------- | :-------------------------------------------------------------------------------------------------------------- |
| Create one or many policies | [`POST /v1/core/fees/policies`](/docs/api-reference/core/fees/create_fee_policies) |
| List your policies | [`GET /v1/core/fees/policies`](/docs/api-reference/core/fees/list_fee_policies) |
| Get a policy | [`GET /v1/core/fees/policies/{id}`](/docs/api-reference/core/fees/get_fee_policy) |
| Update a policy | [`PATCH /v1/core/fees/policies/{id}`](/docs/api-reference/core/fees/update_fee_policy) |
| Delete a policy | [`DELETE /v1/core/fees/policies/{id}`](/docs/api-reference/core/fees/delete_fee_policy) |
| See which policies apply to an account | [`GET /v1/ledger/accounts/{account_id}/fees/policies`](/docs/api-reference/core/fees/list_account_fee_policies) |
A few things to know about each operation:
* **Creating.** Send the policies under `policies`, always a list (up to 50). The batch is atomic: if one policy is rejected, none is created, and the error's `path` points at the item and field that failed — `/policies/1/strategies/COP` reads "the COP strategy of the second policy". A new policy never starts out skipped (paused).
* **Updating.** Only the fields you send change. `strategies` is replaced as a whole, so send every currency the policy must keep; removing a currency is sending the map without it. `fee_type`, `target_type` and `target_id` are immutable — to move a policy, delete it and create a new one. Changes apply to the fees charged from then on; fees already charged keep their price.
* **Skipping or pausing.** `skip: true` skips (pauses) a policy: the next less specific one takes over until you set `skip` back to `false`. Base policies cannot be skipped.
* **Deleting.** The policy stops applying and disappears from the listings, and its target and fee type are free for a new policy. Base policies cannot be deleted. Fees already charged keep referencing the deleted policy.
### Example: a special price for one program [#example-a-special-price-for-one-program]
A program of premium users pays a lower payout fee, and nothing at all for ACH payouts:
```json
{
"policies": [
{
"fee_type": "bank_transfer",
"summary": "Premium program — bank transfer fee",
"transaction_description": "Transfer fee",
"target_type": "program",
"target_id": "pro_3rbnHF9AtEBSyyfqZAmnii",
"processing_type": "inline",
"processing_subtype": "add_posting",
"strategies": {
"COP": {
"rules": [
{
"summary": "ACH payouts are free",
"conditions": [
{
"operator": "equal",
"left": { "property": "method" },
"right": { "value": "co_ach" }
}
],
"calculation": { "fixed": 0, "percentage": "0" }
}
],
"default_calculation": { "fixed": 300000, "percentage": "0" }
}
}
}
]
}
```
From then on, every COP payout from an account of that program costs 3.000,00 — except ACH payouts, which cost nothing — while accounts in your other programs keep the price of your base policy.
## Errors [#errors]
Business errors come with an `error_code` and, when they concern one item of a batch, a `path` locating it:
| Code | HTTP | When |
| :------------------------------------------ | :--- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fee_policy_not_found` | 404 | The policy does not exist, is not yours or was deleted. |
| `fee_policy_already_exists` | 409 | The target already has an active policy for that fee type. Update it instead. |
| `fee_policies_duplicated` | 422 | Two policies of the batch have the same target and fee type. |
| `tenant_fee_policy_cannot_be_created` | 422 | `target_type: tenant` is not allowed on creation — the base policies are set up by Mono. |
| `fee_policy_target_not_found` | 422 | The `target_id` does not exist or does not belong to you. |
| `fee_policy_unsupported_processing_type` | 422 | The `processing_type` is not among the ones the fee type allows. |
| `fee_policy_unsupported_processing_subtype` | 422 | The `processing_subtype` is not among the ones the fee type allows for that processing type. |
| `fee_policy_invalid_strategy_currencies` | 422 | The currencies of `strategies` do not match what the target supports. |
| `fee_policy_invalid_strategy` | 422 | A rule, condition or calculation is invalid for the fee type — an unknown property, incompatible operands, a percentage limit that does not match its mode. |
| `tenant_fee_policy_cannot_be_skipped` | 422 | `skip: true` on a base policy. |
| `tenant_fee_policy_cannot_be_deleted` | 422 | Deleting a base policy. |
| `fee_policy_target_type_required` | 422 | Listing with `target_id` but without `target_type`. |
## Next steps [#next-steps]
* [Conditions](/docs/guides/core/concepts/fees/conditions) — every operator and property available to your rules.
* [Fees](/docs/guides/core/concepts/fees) — what happens once a policy has priced a fee.
* [Fees API reference](/docs/api-reference/core/fees) — request and response payloads in full.
# Fees (/docs/guides/core/concepts/fees)
A **fee** is a charge attached to a money movement: a payout, a payin or a transfer between subaccounts. Some fees are yours — the price you charge your own users or customers for using your product, debited from their subaccounts and credited to you. Others are Mono's — the price of using the platform, debited from your own accounts. Every fee is recorded next to the operation that triggered it, with the rule that priced it and the exact amount, so you can always reconcile what was charged and why.
How much a fee costs is not hard-coded: it is decided by a **fee policy**, a pricing rule you manage per kind of fee and per group of accounts. This page explains fees themselves; [Fee policies](/docs/guides/core/concepts/fees/policies) explains how you set the prices.
## Two sides: your fees and Mono's fees [#two-sides-your-fees-and-monos-fees]
| | Fees you charge (`owner: tenant`) | Fees Mono charges you (`owner: mono`) |
| :--------------------- | :-------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------- |
| **Who pays** | Your users or customers, from their subaccounts | You, from your own accounts |
| **Who receives** | You | Mono |
| **Who sets the price** | You, through your fee policies | Mono, as agreed in your commercial terms |
| **Where you see them** | `calculated_fees` on the operation, and the [fees listing](/docs/api-reference/core/fees/list_fees) | The [fees listing](/docs/api-reference/core/fees/list_fees) with `owner=mono` |
Both sides use the same fee types and the same mechanics. The only difference is who pays whom.
## Fee types [#fee-types]
Each fee has a **type** that says what it is charged for. The types available today, and the service that triggers each one:
| Fee type | Service | Charged when | Charged by |
| :----------------------------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------ | :----------- |
| `bank_transfer` | Payouts | A payout to a bank account is created. | You and Mono |
| `bank_transfer_gmf` | Payouts | A payout to a bank account is created, to pass on the GMF — Colombia's tax on financial movements, 0.4% (the "4 × 1000"). | You and Mono |
| `payin` | Payins | A payin is credited to the subaccount. | You |
| `account_to_account_fx` | Ledger transfers | A transfer between two subaccounts in different currencies is made. A fixed charge for the conversion. | You |
| `account_to_account_fx_spread` | Ledger transfers | The same transfer. Your margin on the exchange rate, embedded in the rate applied. | You |
A fee type also fixes how the fee can be charged — see [How a fee is charged](#how-a-fee-is-charged) — and which properties of the operation its pricing rules can look at — see [Conditions](/docs/guides/core/concepts/fees/conditions).
## How a fee is charged [#how-a-fee-is-charged]
Every fee carries a `processing_type` and a `processing_subtype` that describe when and how it lands in the ledger:
| Processing | Meaning |
| :----------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `inline` / `add_posting` | Charged as its own movement inside the operation's transaction. The most common case: the fee appears next to the payout, payin or transfer that caused it. |
| `inline` / `upcharge` | Added on top of the operation's amount. Used by `account_to_account_fx_spread`, where the margin is embedded in the exchange rate. |
| `inline` / `deduct` | Taken out of the operation's amount, so the receiving side gets the amount minus the fee. |
| `inline` / `new_tx` | Charged right away, in a transaction of its own. |
| `deferred` / `new_tx` | Charged later, in a transaction of its own. |
Each fee type supports specific combinations; a fee policy picks among them. See the fee types table in [Fee policies](/docs/guides/core/concepts/fees/policies#fee-types-and-their-processing).
## Lifecycle of a fee [#lifecycle-of-a-fee]
A fee follows the operation that triggered it:
| State | Meaning |
| :--------- | :------------------------------------------------------------------------------ |
| `created` | Priced when the operation was created; nothing charged yet. |
| `pending` | Waiting for the operation to move forward. |
| `held` | The amount is reserved on the account until the operation settles. |
| `approved` | Charged. |
| `declined` | The operation failed before the charge; the fee was never charged. |
| `reversed` | Charged, then returned — for example because the operation itself was reversed. |
## Where fees show up [#where-fees-show-up]
* **On the operation.** Payouts, payins and ledger transfers return the fees they charged in `calculated_fees`, keyed by fee type, with the amount of each one. That is the field to reconcile against per operation.
* **In the fees listing.** [`GET /v1/core/fees`](/docs/api-reference/core/fees/list_fees) lists every fee charged on your accounts — yours and Mono's — with the policy and the rule that priced it, its state and the operation that triggered it. Filter by owner, type, state, account, policy, origin or date range.
* **Overrides.** Some operations let you replace the policy's price for that single operation: `fee_override` on payins and `override_fees` on ledger transfers. The fee then reports `overridden: true`, and `overridden_fields` tells what you sent.
## Amounts [#amounts]
Fee amounts, like every amount in the Core API, are expressed in the smallest unit of the currency: `900000` **cents** in COP is 9.000,00 COP. Percentages are decimal fractions sent as strings: `"0.004"` is 0.4%.
## Next steps [#next-steps]
* [Fee policies](/docs/guides/core/concepts/fees/policies) — how to set the price of each fee, and for whom.
* [Conditions](/docs/guides/core/concepts/fees/conditions) — the properties and operators your pricing rules can use.
* [Fees API reference](/docs/api-reference/core/fees) — the endpoints to list fees and manage policies.
# Conditions (/docs/guides/core/concepts/fees/conditions)
A **condition** is a yes-or-no question a fee rule asks about the operation being charged: is this payout going through ACH? Is the amount above a threshold? Is the account tagged as VIP? A rule applies its price only when every one of its conditions answers yes. Conditions are how a single fee policy prices different situations differently, instead of needing one policy per case.
Every condition compares an **operand** — a property of the operation, or a constant value you write — with another, through an **operator**. This page is the reference of the operators available and of the properties each kind of fee can look at.
## Anatomy of a condition [#anatomy-of-a-condition]
```json
{
"operator": "greater_equal_than",
"left": { "property": "source_amount" },
"right": { "value": 100000000 },
"negated": false
}
```
| Field | Meaning |
| :--------- | :------------------------------------------------------------------------------------- |
| `operator` | The comparison to make. See [Operators](#operators). |
| `left` | The first operand. Usually a property of the operation. |
| `right` | The second operand. Required by every operator except `is_null`. |
| `negated` | Inverts the result. `false` by default: a `negated` `equal` means "is different from". |
An operand is one of:
* **A property**: `{ "property": "method" }` — the value is read from the operation when the fee is charged. Some properties take arguments: `{ "property": "account.metadata", "args": { "name": "plan" } }`.
* **A constant**: `{ "value": "co_ach" }` — a string, an integer, a boolean, or a list of them. Decimals travel as strings (`"0.5"`).
At least one side of a condition must be a property — comparing two constants would always give the same answer.
## Operators [#operators]
| Operator | Reads as | Operands |
| :------------------- | :------------------------------------- | :------------------------------------------------------------------------------------------------------------------ |
| `equal` | left is equal to right | Any two compatible operands. An integer and a decimal compare as numbers. |
| `greater_than` | left is greater than right | Two numbers. |
| `greater_equal_than` | left is greater than or equal to right | Two numbers. |
| `less_than` | left is less than right | Two numbers. |
| `less_equal_than` | left is less than or equal to right | Two numbers. |
| `in` | left is one of the values in right | `left` is a single value; `right` is a list of values of the same kind (`{ "value": ["co_ach", "co_transfiya"] }`). |
| `is_null` | left has no value | Only `left`, and it must be a property that can be empty. No `right`. |
Two details to keep in mind:
* When a property involved in a numeric comparison is empty (`null`), the condition is false. Use `is_null` if you need to react to the absence of a value.
* Operands must be compatible: comparing an amount with a text, or a string with a list of numbers, is rejected when the policy is created (`fee_policy_invalid_strategy`), not silently ignored later.
## Properties [#properties]
Which properties a rule can use depends on the **fee type**: each one is triggered by a kind of operation — its **main entity** — and can also look at the entities related to it. A property of the main entity is referenced by its name (`method`); a property of a related entity by `entity.property` (`account.name`, `account_holder.type`).
Amounts are in the smallest unit of their currency (`1234` is 12,34), currency codes follow ISO 4217 (`COP`, `USD`) and country codes ISO 3166-1 alpha-2 (`CO`).
### Payouts — `bank_transfer`, `bank_transfer_gmf` [#payouts--bank_transfer-bank_transfer_gmf]
Main entity: the **payout**. Related: the source **account** and its **account holder**.
| Property | Type | Meaning |
| :--------------------- | :------------- | :----------------------------------------------------------------------------------- |
| `method` | string | The payout method, for example `co_ach`, `co_transfiya`, `co_mono_turbo`, `co_breb`. |
| `source_currency_code` | string | The currency of the source account. |
| `source_amount` | number or null | The amount debited from the source account, in its smallest unit. |
| `target_currency_code` | string | The currency of the destination. |
| `target_amount` | number or null | The amount delivered to the destination, in its smallest unit. |
| `target_country_code` | string | The country of the destination account. |
### Payins — `payin` [#payins--payin]
Main entity: the **payin**. Related: the credited **account** (the subaccount) and its **account holder**.
| Property | Type | Meaning |
| :------------------------- | :------------- | :--------------------------------------------------------------- |
| `method` | string | The payin method, for example `co_breb`. |
| `merchant_currency_code` | string | The currency of the merchant amount — the template's currency. |
| `merchant_amount` | number or null | The merchant-facing amount of the payment, in its smallest unit. |
| `subaccount_currency_code` | string | The currency of the credited subaccount. |
| `subaccount_amount` | number or null | The amount credited to the subaccount, in its smallest unit. |
### Transfers between subaccounts — `account_to_account_fx`, `account_to_account_fx_spread` [#transfers-between-subaccounts--account_to_account_fx-account_to_account_fx_spread]
Main entity: the **subaccount operation** (the transfer). Related: the source **account** and its **account holder**.
| Property | Type | Meaning |
| :--------------------- | :------------- | :----------------------------------------------------------------------- |
| `operation_type` | string | The kind of transfer: `account_to_account_fx` when currencies differ. |
| `source_currency_code` | string | The currency of the source subaccount. |
| `source_amount` | number or null | The amount debited from the source subaccount, in its smallest unit. |
| `target_currency_code` | string | The currency of the destination subaccount. |
| `target_amount` | number or null | The amount credited to the destination subaccount, in its smallest unit. |
| `metadata` | string or null | A field of the transfer's metadata. Takes `args: { "key": "" }`. |
### Related entities: account and account holder [#related-entities-account-and-account-holder]
Available to every fee type above, prefixed with `account.` or `account_holder.`:
| Property | Type | Meaning |
| :------------------------ | :------------- | :--------------------------------------------------------------------------------------------------- |
| `account.name` | string or null | The name you gave the account. |
| `account.metadata` | string or null | A field of the account's metadata. Takes `args: { "name": "" }`. |
| `account_holder.type` | string | `self` when the holder is your own company, `third_party` when it is one of your users or customers. |
| `account_holder.metadata` | string or null | A field of the account holder's metadata. Takes `args: { "name": "" }`. |
Metadata is the most flexible hook: tag your accounts or holders with a `plan`, a `segment` or a `tier` when you create them, and price on it without touching the policy every time a user changes plan.
## Examples [#examples]
Each example is one rule inside the strategy of a currency. If the operation does not meet the rule's conditions, the rule does not apply and the price is decided by the next rule or, when none matches, by the strategy's `default_calculation`.
**Free ACH payouts** — the rule applies when the method is exactly `co_ach`; any other method pays the `default_calculation`:
```json
{
"conditions": [
{ "operator": "equal", "left": { "property": "method" }, "right": { "value": "co_ach" } }
],
"calculation": { "fixed": 0, "percentage": "0" }
}
```
**A cheaper rate for large payouts** — at or above 1.000.000,00 COP. This example shows the whole strategy, with the `default_calculation` that charges when the rule does not apply:
```json
{
"rules": [
{
"summary": "Reduced rate from 1.000.000,00 COP",
"conditions": [
{
"operator": "greater_equal_than",
"left": { "property": "source_amount" },
"right": { "value": 100000000 }
}
],
"calculation": { "fixed": 150000, "percentage": "0" }
}
],
"default_calculation": { "fixed": 300000, "percentage": "0" }
}
```
A payout of 1.500.000,00 COP meets the condition and pays 1.500,00; one of 800.000,00 COP does not, so it pays the 3.000,00 of the `default_calculation`.
**Instant rails cost more** — any of two methods; the rest pay the `default_calculation`:
```json
{
"conditions": [
{
"operator": "in",
"left": { "property": "method" },
"right": { "value": ["co_transfiya", "co_breb"] }
}
],
"calculation": { "fixed": 500000, "percentage": "0" }
}
```
**Exempt VIP accounts** — the account was created with `metadata.plan = "vip"`; every other account pays the `default_calculation`:
```json
{
"conditions": [
{
"operator": "equal",
"left": { "property": "account.metadata", "args": { "name": "plan" } },
"right": { "value": "vip" }
}
],
"calculation": { "fixed": 0, "percentage": "0" }
}
```
**Untagged accounts pay a surcharge** — the metadata field is absent; tagged accounts pay the `default_calculation`:
```json
{
"conditions": [
{
"operator": "is_null",
"left": { "property": "account.metadata", "args": { "name": "plan" } }
}
],
"calculation": { "fixed": 200000, "percentage": "0.01" }
}
```
Rules are evaluated in order and the first match wins, so put the most specific rules first and let `default_calculation` handle everything else.
## Next steps [#next-steps]
* [Fee policies](/docs/guides/core/concepts/fees/policies) — where these rules live, and how policies are resolved.
* [Fees](/docs/guides/core/concepts/fees) — the fee types and what each one is charged for.
* [Create fee policies](/docs/api-reference/core/fees/create_fee_policies) — the full request schema.
# Settlement (/docs/guides/core/flows/payins/settlement)
Collecting the payment is half the story; this flow covers the other half: how the money that payers sent becomes a balance in **your bank account**. It walks how Mono delivers the funds, the webhooks that report each step, and what your reconciliation should check at the end of the day.
If you have not read the [Settlement concept](/docs/guides/core/concepts/payins/settlement) yet, start there: this page assumes you know what a settlement batch is.
## Before you start [#before-you-start]
You will need:
* Payins reaching `approved_in_provider` (any of the [collection flows](/docs/guides/core/flows/payins) produces them).
* A webhook endpoint subscribed to the `payin_settlement_batch_*` events.
## How the delivery runs [#how-the-delivery-runs]
1. **Payins accumulate.** Every approved payin destined for your funding account waits as `pending_to_deliver`.
2. **Mono groups them into a batch.** `payin_settlement_batch_created` lists exactly which payins the batch covers and what each delivers. A single day can produce several batches.
3. **The delivery runs.** `processing` means the delivery is under way; `completed` means the money reached your funding account. If you read a batch in `retryable` or `failed`, Mono is handling it and you do not need to act.
4. **Payins are stamped.** Each covered payin gets `settlement_batch_id` and `funds_delivery_status: "completed"`.
## Reconciling settlements [#reconciling-settlements]
* **Batch → bank statement**: match each `payin_settlement_batch_completed` to one credit on your bank statement for `total_amount`.
* **Batch → payins**: `GET /v1/core/payins/settlement_batches/{id}` returns the covered payins with their `funding_amount`. The sum is the batch total, no payin missing, none twice.
* **Leftovers**: a payin approved late in the day can still be `pending_to_deliver` at the close; it arrives in the day's last batch. If a payin stays undelivered for more than a day, contact Mono support with its `id`.
## Next steps [#next-steps]
* [Settlement concept](/docs/guides/core/concepts/payins/settlement): states, batching, and the fields involved.
* [Payins](/docs/guides/core/concepts/payins/payins): the per-payin delivery fields.
* [Webhooks](/docs/guides/core/webhooks/payins): the three settlement batch events in context.
# Top-up (/docs/guides/core/flows/payins/topup)
A **top-up** lets an end user load money into their own wallet balance from any Colombian bank: your product shows them a personal Bre-B key (or QR), they transfer to it from their bank's app, and seconds later their subaccount balance reflects the recharge. The pattern is one **top-up template per subaccount**, created once and reused for every recharge. The key becomes the user's permanent "deposit address".
## Before you start [#before-you-start]
You will need:
* A Mono account with Core and the payins feature enabled for your program. See [Methods: program configuration](/docs/guides/core/concepts/payins/methods#program-configuration).
* One subaccount per end user. See [Ledger](/docs/guides/core/concepts/ledger).
* A webhook endpoint subscribed to the [payin events](/docs/guides/core/webhooks/payins).
## Sequence overview [#sequence-overview]
## Steps [#steps]
1. **Create the template when the subaccount is born.** One call per user, idempotent on `external_id` (use your own user or account ID):
```json
{
"external_id": "user-8421-topup",
"type": "topup",
"usage_mode": "multiple_use",
"subaccount_id": "",
"currency": "COP",
"nickname": "Wallet top-up for user 8421",
"method_params": { "co_breb": { "create_qr": true } }
}
```
The template is created in `created` state: the Bre-B key does not exist yet.
2. **Wait for `payin_template_ready`.** The webhook (or a `GET` on the template) delivers `payment_tokens.co_breb`: the key and, since `create_qr` was true, a static QR image. Store both against the user.
3. **Show the key in your product.** The user recharges whenever they want, from any bank, with no further API calls: every payment to the key becomes a payin against this template.
4. **React to the payin webhooks.** `payin_subaccount_credited` is the signal to update the user's visible balance; `subaccount_amount` is what was credited. Reject conditions (a transfer above the [Bre-B cap](/docs/guides/core/concepts/payins/methods/co-breb#limits-and-timing), an amount outside the template's limits) arrive as `payin_rejected` with a `state_reason`.
5. **Reconcile.** Every payin carries `template_id` and `subaccount_id`, so mapping a payment to a user is a lookup you already have. `instrument.payer` tells you who actually sent the money.
## Variant: one-off recharge [#variant-one-off-recharge]
For a single expected recharge (an onboarding deposit, a one-time funding of a campaign balance), create the template with `"usage_mode": "single_use"` and, if the amount is fixed, set `minimum_payin_amount` = `maximum_payin_amount`. The first successful payin moves the template to `paid` and it never accepts another payment. Use `expected_payers` if only a specific person may fund it.
## Guardrails worth setting [#guardrails-worth-setting]
* `minimum_payin_amount` / `maximum_payin_amount`: bound each recharge.
* `expected_payers`: lock the key to its owner's document, so nobody tops up someone else's wallet by mistake.
* `payin_data.metadata`: stamped on every payin, useful to tag the wallet product or campaign.
## Next steps [#next-steps]
* [Collect with a template](/docs/guides/core/flows/payins/collect-with-template): the same mechanics, pointed at customers.
* [Settlement](/docs/guides/core/flows/payins/settlement): how the recharged money reaches your funding account.
* [Templates](/docs/guides/core/concepts/payins/templates): every field the template accepts.
# Payin flows (/docs/guides/core/flows/payins)
A **flow** is a complete, end-to-end payin use case. Where [Concepts](/docs/guides/core/concepts/payins) answers "what is a template, an intent, a payin?", flows answer "how do I let users recharge their wallet, collect from many payers, or reconcile payments one by one?".
## Choosing a flow [#choosing-a-flow]
The decision comes down to two questions: who is paying, and do you need to know which payment is which before the money arrives?
| Your case | Flow |
| :------------------------------------------------------------------------------ | :------------------------------------------------------------------------------ |
| End users load their own wallet balance, repeatedly, with a permanent key. | [Top-up](/docs/guides/core/flows/payins/topup) |
| Many payers pay to one shared key or static QR: donations, in-store, campaigns. | [Collect with a template](/docs/guides/core/flows/payins/collect-with-template) |
| Each payment must be pre-identified: checkout, invoices, one QR per purchase. | [Collect with intents](/docs/guides/core/flows/payins/collect-with-intents) |
| Understand how the collected money reaches your bank account. | [Settlement](/docs/guides/core/flows/payins/settlement) |
Recharge a subaccount with a permanent Bre-B key.
One static key or QR, many payers, webhook-driven reconciliation.
One intent per payment: dynamic QR, retries, exact reconciliation.
From approved payment to money in your funding account.
## Next steps [#next-steps]
* [Concepts](/docs/guides/core/concepts/payins): the entities these flows compose.
* [Webhooks](/docs/guides/core/webhooks/payins): the events that drive each flow.
* [Payins API reference](/docs/api-reference/core/payins): the endpoints behind them.
# Collect with intents (/docs/guides/core/flows/payins/collect-with-intents)
This flow declares **every expected payment before the money moves**: for each order or invoice, your backend creates an [intent](/docs/guides/core/concepts/payins/intents), Mono mints a dedicated Bre-B key and a dynamic QR for it, and the payment that arrives is linked back to the intent automatically. Reconciliation stops being detective work: you ask "was intent `order-1042` paid?" and the API answers.
Use it for e-commerce checkout, invoice payment, or any case where amount and payer are known per payment and must match exactly.
## Before you start [#before-you-start]
You will need:
* A Mono account with Core and the payins feature enabled for your program. See [Methods: program configuration](/docs/guides/core/concepts/payins/methods#program-configuration).
* A collection template created with `method_params.co_breb.key_per: "intent"`, or the decision to create templateless intents directly against a subaccount.
* A webhook endpoint subscribed to the [payin events](/docs/guides/core/webhooks/payins).
* The [Intents](/docs/guides/core/concepts/payins/intents) concept fresh in mind.
## Sequence overview [#sequence-overview]
## Steps [#steps]
1. **Create one intent per payment.** Idempotent on `external_id`. Use your order ID, so a double-click cannot create two intents:
```json
{
"external_id": "order-1042",
"template_id": "",
"method": "co_breb",
"merchant_amount": { "amount": 8990000, "currency": "COP" },
"method_params": { "create_qr": true }
}
```
Send exactly one of `merchant_amount` or `payer_amount`. With a templateless intent, replace `template_id` with `subaccount_id`.
2. **Wait for `payin_intent_ready_to_pay`.** It delivers `payment_tokens` (the per-intent key and a single-use QR carrying the exact amount) and `expires_at`: for Bre-B, 30 minutes. Show the QR in your checkout with a countdown.
3. **The customer pays.** The amount is enforced: a payment for anything other than the intent's amount is rejected (`intent_amount_mismatch`).
4. **Handle the three endings.**
* **Paid**: `payin_intent_successful` (plus the payin's own `subaccount_credited`). Confirm the order.
* **Attempt failed**: `payin_intent_attempt_failed`: the payment bounced but the intent re-armed back to `ready_to_pay`. Keep the QR on screen; the customer can retry against the same key while the window lasts.
* **Expired**: `payin_intent_expired`: the window closed unpaid. The `external_id` is freed, so offering a fresh checkout for the same order is safe.
5. **Reconcile without storing Mono IDs.** `GET /v1/core/payins/intents/find_successful_by?external_id=order-1042` returns the successful intent for your identifier, or nothing: a one-call answer to "was this paid?".
## Template or templateless? [#template-or-templateless]
* **With a `key_per: "intent"` template**: intents inherit limits, expected payers, and `payin_data`; you manage one campaign configuration in one place.
* **Templateless**: each intent stands alone against a subaccount, carrying its own configuration. Fewer moving parts when there is no shared campaign to speak of.
## Next steps [#next-steps]
* [Intents](/docs/guides/core/concepts/payins/intents): the full state machine, re-arming, and expiry.
* [Payment tokens](/docs/guides/core/concepts/payins/payment-tokens): dynamic QRs in detail.
* [Collect with a template](/docs/guides/core/flows/payins/collect-with-template): the anonymous-payments alternative.
# Collect with a template (/docs/guides/core/flows/payins/collect-with-template)
This flow collects payments from **many payers through one shared address**: a multiple-use template issues a static Bre-B key and QR, you display it (on a screen, a poster, an invoice footer) and every transfer to it lands in your subaccount as a payin. There is no per-payment API call: the template is the campaign, and the webhooks are your ledger of who paid what.
Use it for donations, in-store QR payments, recurring club dues, or any collection where you do not need to know in advance which specific payment is coming.
## Before you start [#before-you-start]
You will need:
* A Mono account with Core and the payins feature enabled for your program. See [Methods: program configuration](/docs/guides/core/concepts/payins/methods#program-configuration).
* The subaccount that will receive the collections.
* A webhook endpoint subscribed to the [payin events](/docs/guides/core/webhooks/payins).
* The [Templates](/docs/guides/core/concepts/payins/templates) concept fresh in mind.
## Sequence overview [#sequence-overview]
## Steps [#steps]
1. **Create the collection template.** `type: "collection"` is the default; make it `multiple_use` and request the QR:
```json
{
"external_id": "campaign-2026-back-to-school",
"usage_mode": "multiple_use",
"subaccount_id": "",
"currency": "COP",
"reference": "Back to school 2026",
"minimum_payin_amount": { "amount": 1000000, "currency": "COP" },
"total_maximum_amount": { "amount": 5000000000, "currency": "COP" },
"expires_at": "2026-12-31T23:59:59Z",
"method_params": { "co_breb": { "create_qr": true } }
}
```
2. **Wait for `payin_template_ready`** and publish the key and QR wherever payers will see them.
3. **Let the payments flow.** Each transfer becomes a payin: `payin_created` → `payin_approved_in_provider` → `payin_subaccount_credited`. The payer's identity arrives in `instrument.payer`. That is how you know who paid, since nothing identifies payments in advance in this flow.
4. **Watch the campaign milestones.** If you set `total_minimum_amount`, `payin_template_minimum_paid` announces the goal was reached; `payin_template_paid` announces the cap (`total_maximum_amount`) and the template stops accepting payments. Reopen a paid campaign by raising the cap with an update.
5. **Close it down.** Let `expires_at` prune the template, or delete it once `paid_amount` is settled and nothing is in flight. Either way the key and QR are deregistered and `payin_template_discarded` confirms it.
## Reconciliation in this flow [#reconciliation-in-this-flow]
Payments are anonymous until they arrive, so reconcile by **listing payins by `template_id`** and matching on `instrument.payer` (document) and `amount`. If you need exact payment-to-order matching, this is the signal to use [intents](/docs/guides/core/flows/payins/collect-with-intents) instead.
## Next steps [#next-steps]
* [Collect with intents](/docs/guides/core/flows/payins/collect-with-intents): when each payment must be pre-identified.
* [Settlement](/docs/guides/core/flows/payins/settlement): how the collected money reaches your funding account.
* [Webhooks](/docs/guides/core/webhooks/payins): the full event catalog this flow rides on.
# Settlement (/docs/guides/core/concepts/payins/settlement)
A payin moves money twice, and the two movements answer two different questions. **Crediting the subaccount** is *your* decision: when does the end user's balance reflect the payment? **Delivering the funds to you** is *Mono's* job: how does the real money reach the **funding account**, the actual bank account backing your program? Each movement has its own page, because they run on different clocks, different fields, and different webhooks.
| Movement | Money | Decided by | Reported through |
| :----------------------------------------------------------------------------------------- | :------ | :--------- | :------------------------------------------------------------- |
| [Crediting the subaccount](/docs/guides/core/concepts/payins/settlement/subaccount) | Virtual | You | `payin_subaccount_credited`, the payin's state |
| [Delivering to your funding account](/docs/guides/core/concepts/payins/settlement/funding) | Real | Mono | `funds_delivery_status`, the `payin_settlement_batch_*` events |
The two are genuinely independent: with the `on_approval` trigger, the subaccount is credited **before** the real money has reached you. The delivery catches up on its own schedule.
The trigger you choose, the webhook, and the amount your users see.
Settlement batches and their lifecycle.
## Next steps [#next-steps]
* [Settlement flow](/docs/guides/core/flows/payins/settlement): the end-to-end walkthrough with webhooks.
* [Payins](/docs/guides/core/concepts/payins/payins): the fields both movements live on.
* [Settlement batches API reference](/docs/api-reference/core/payins#settlement-batches): the full schemas.
# Crediting the subaccount (/docs/guides/core/concepts/payins/settlement/subaccount)
This is the first of the [two settlement movements](/docs/guides/core/concepts/payins/settlement): the **virtual money**, the credit on Core's ledger that makes the payment visible as balance in the subaccount. This movement is *your* decision: you choose when it happens. The second movement, the real money reaching your bank account, is Mono's job and has its own page: [Delivering to your funding account](/docs/guides/core/concepts/payins/settlement/funding).
## Choosing the trigger [#choosing-the-trigger]
`subaccount_disbursement_trigger` decides the moment of the ledger credit. Your program sets the default, and every template or intent can override it for its own payins:
| Trigger | Subaccount credited when… |
| :------------------ | :----------------------------------------------------------------------------------------------------------------- |
| `on_approval` | The provider approves the payment. The natural fit for Bre-B, where approval and money movement are seconds apart. |
| `on_funds_delivery` | The real money reaches its destination account. |
## What the credit looks like [#what-the-credit-looks-like]
The credit is announced by the `payin_subaccount_credited` webhook and the payin moves to `subaccount_credited` (for most integrations, the "payment complete" signal). `subaccount_amount` is the credited value, in the subaccount's currency, and the [fees](/docs/guides/core/concepts/payins/fees) charged on the payin are already reflected: `calculated_fees` on the payin breaks them down.
From this moment the end user can see and use the balance, regardless of where the real money is.
## Independent from funds delivery [#independent-from-funds-delivery]
The two settlement movements are genuinely independent: with `on_approval`, the subaccount is credited **before** the real money has reached you. The [funding-account delivery](/docs/guides/core/concepts/payins/settlement/funding) catches up on its own schedule, and reports through its own fields and webhooks. Choose `on_funds_delivery` when your product must not show balance until the money is physically yours.
## Next steps [#next-steps]
* [Delivering to your funding account](/docs/guides/core/concepts/payins/settlement/funding): the second movement.
* [Payins](/docs/guides/core/concepts/payins/payins): the states and fields around the credit.
* [Fees](/docs/guides/core/concepts/payins/fees): what is deducted when the subaccount is credited.
# Delivering to your funding account (/docs/guides/core/concepts/payins/settlement/funding)
This is the second of the [two settlement movements](/docs/guides/core/concepts/payins/settlement): the **real money**, the collected funds traveling to your **funding account**, the actual bank account backing your program. This movement is *Mono's* job and never blocks the first one (the ledger credit your users see, covered in [Crediting the subaccount](/docs/guides/core/concepts/payins/settlement/subaccount)). It runs behind it, on its own schedule, and reports through its own fields and webhooks.
## Per-payin tracking [#per-payin-tracking]
Every payin tracks its own delivery:
* `funds_delivery_status`: `null` until the payin is `approved_in_provider`, then `pending_to_deliver` → `processing` → `completed`.
* `funds_destination`: `funding_account` means the funds reach you through a settlement batch; `method_receiving_account` means the delivery completes immediately and the payin never appears in a settlement batch.
* `settlement_batch_id`: the batch that delivered the funds, once one covers this payin.
## Settlement batches [#settlement-batches]
Mono delivers your funds in **settlement batches**. Three things to expect when you reconcile:
1. One batch covers many payins: they share a `settlement_batch_id`, and `total_amount` is the sum of their `funding_amount`.
2. A single day can produce several batches.
3. A payin approved late in the day may arrive in the day's last batch instead of an earlier one.
## Batch lifecycle [#batch-lifecycle]
Each of these states is notified through a [webhook](/docs/guides/core/webhooks/payins). When you read a batch you may also see `retryable` or `failed`: Mono is handling the delivery, and you do not need to act. Neither state is notified.
## Reading a batch [#reading-a-batch]
`GET /v1/core/payins/settlement_batches/{id}` returns the batch with its covered payins: what each one collected (`amount`), what it delivers (`funding_amount`), and the `funding_amount_rates` behind the conversion when the currencies differ (empty today, since everything is COP). The response is the same shape the `payin_settlement_batch_*` webhooks deliver. The list endpoint reports `payins_count` per batch.
## Next steps [#next-steps]
* [Settlement flow](/docs/guides/core/flows/payins/settlement): the end-to-end walkthrough with webhooks.
* [Crediting the subaccount](/docs/guides/core/concepts/payins/settlement/subaccount): the first movement.
* [Settlement batches API reference](/docs/api-reference/core/payins#settlement-batches): the full schemas.
# Template errors (/docs/guides/core/concepts/payins/templates/errors)
A [template](/docs/guides/core/concepts/payins/templates) carries `state_reason` in its two terminal states, and they mean very different things:
* **`failed`**: the template never became payable. Its payment-token setup exhausted its retries, so no payer could ever reach it.
* **`discarded`**: the template was payable and its life ended: it was pruned or deleted, and its payment tokens were torn down.
You read the code in the [`payin_template_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_failed) and [`payin_template_discarded`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_discarded) webhooks, and on the template itself via [get](/docs/api-reference/core/payins/get_template) or [list](/docs/api-reference/core/payins/list_templates).
### Example webhook payload [#example-webhook-payload]
```json
{
"timestamp": "2026-02-11T14:22:07.913204Z",
"event": {
"type": "payin_template_failed",
"data": {
"id": "cpit_034EASMHlq36oZISbc9W4b",
"state": "failed",
"state_reason": "key_already_registered",
"external_id": "campaign-2026-02",
"subaccount_id": "csub_034EASMIAFVq06j2NxhnIJ"
}
}
}
```
## Failed: the template never became payable [#failed-the-template-never-became-payable]
A template is created in `created` and only reaches `ready` once its payment token exists. These are the ways that setup ends instead.
| Code | Description | Cause | Suggested fix |
| -------------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `key_already_registered` | Key already taken | The custom key value you asked for is already registered in the instant payments network: keys are unique across the whole network. | Create the template again with a different `custom_key_value`, or let Mono generate the key. |
| `key_registration_failed` | Key registration failed | Registering the payment key with the network exhausted its retries. | Create the template again. If it repeats, contact support with the template `id`. |
| `collection_registration_failed` | Collection registration failed | Mono could not register the collection with the payment method's provider. | Create the template again. If it repeats, contact support with the template `id`. |
| `unknown` | Unknown | An unclassified failure during setup. | Contact support with the template `id`. |
There is no way to retry the setup of a template that already failed. The state is terminal.
Create a new one instead: a template that ends `failed` or `discarded` releases its
`external_id`, so you can reuse the same identifier for the replacement.
## Discarded: the template's life ended [#discarded-the-templates-life-ended]
Three ways out, and only one of them is something you asked for. All three tear down the payment tokens, so any key or QR already distributed stops working.
| Code | Description | Cause | Suggested fix |
| ------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| `deleted` | Deleted | You deleted the template through the API. | Expected. Create a new template when you need to collect again. |
| `expired` | Expired | The template passed its `expires_at` and was pruned. | Set a longer window on the next one, or create a new template. |
| `inactivity` | Inactivity | The template went too long without activity and was pruned. It covers both cases: never paid, and paid at some point but idle since. | Create a new template. If the collection is long-lived, keep it in use or reissue it. |
For a pruned template, `prune_reason` keeps the finer distinction that `state_reason`
collapses: `inactivity_without_attempts` and `inactivity_with_attempts` both become
`inactivity`. Read `prune_reason` when you need to tell "nobody ever paid" from "payments
stopped" (see [Pruning](/docs/guides/core/concepts/payins/templates/pruning)).
## Handling them [#handling-them]
A template that fails or is discarded is not an error in a payment; it is a collection that no longer exists. What matters is that **you stop distributing its payment token**, because payers holding a key or QR from it will have their payments refused with [`template_deleted`, `template_expired` or `template_invalid_state`](/docs/guides/core/concepts/payins/payins/errors#template-conditions).
```ts
app.post('/webhook', (req, res) => {
const { event } = req.body;
if (event.type === 'payin_template_failed') {
const { id, state_reason } = event.data;
if (state_reason === 'key_already_registered') {
// Pick another custom key value and create the template again
} else {
// Setup failed: retry creation, and alert if it repeats
}
}
if (event.type === 'payin_template_discarded') {
const { id, state_reason } = event.data;
// Stop showing this template's key or QR to payers
// `deleted` is yours; `expired` and `inactivity` are the sweep
}
res.status(200).send();
});
```
When contacting support about a template, provide the template `id` and the `external_id` you
created it with.
## Next steps [#next-steps]
* [Payin errors](/docs/guides/core/concepts/payins/payins/errors): why an individual payment was refused.
* [Pruning](/docs/guides/core/concepts/payins/templates/pruning): when and why templates are swept.
* [Webhooks](/docs/guides/core/webhooks/payins): every template event and its payload.
# Templates (/docs/guides/core/concepts/payins/templates)
A **template** answers one question: *how do payments get into this subaccount?* You create it once (like opening a collection point for a wallet, a campaign, or a customer) and it stays there as the standing definition of what you expect to receive. Payers pay, and every payment is checked against the template before it becomes a [payin](/docs/guides/core/concepts/payins/payins).
You never handle the money in a template: it is pure configuration. The money arrives later, as payins created against it.
Creation is idempotent on `external_id`: repeating the request returns the existing template instead of creating a duplicate.
## What a template can do [#what-a-template-can-do]
A template is the most capable entity in payins. Everything below is optional beyond the basics. Each capability has its own section or page:
* **Receive one payment or many**: `usage_mode` ([single use or multiple use](#single-use-or-multiple-use)).
* **Serve top-ups or collections**: `type` ([top-up or collection](#top-up-or-collection)).
* **Issue what the payer pays to**: the [payment token](/docs/guides/core/concepts/payins/payment-tokens) of each accepted method; for Bre-B, a key and an optional QR.
* **Bound the amounts**: per payin and cumulative ([limits and counters](#amount-limits-and-counters)).
* **Restrict who may pay**: [`expected_payers`](#restricting-who-can-pay).
* **Host intents**: with per-intent keys, so each payment is [pre-identified](/docs/guides/core/concepts/payins/intents).
* **Configure every payin it produces**: crediting trigger, metadata, and fee overrides ([inherited configuration](#configuration-inherited-by-every-payin)).
* **Offer a checkout**: integrate API-only with your own UI, or use Mono's hosted checkout, pointed at one of your brands and asking for the extra fields you need ([hosted checkout or API-only](#hosted-checkout-or-api-only)).
* **Track its own progress**: `paid_amount`, `pending_amount`, and the `successful_payins` / `failed_payins` counters.
* **Expire and clean up after itself**: expiration (as a date `expires_at` or a duration `expires_in`, mutually exclusive on creation) and [pruning](/docs/guides/core/concepts/payins/templates/pruning).
## Top-up or collection [#top-up-or-collection]
The `type` field declares the template's purpose:
| Type | Purpose | Typical case |
| :----------- | :-------------------------------------------------------------------------- | :------------------------------------ |
| `topup` | An end user loads money into their **own** subaccount. | Wallet recharge with a permanent key. |
| `collection` | Your business collects money **from customers** into a subaccount. Default. | Checkout, invoicing, donations. |
## Single use or multiple use [#single-use-or-multiple-use]
`usage_mode` defines how many payments the template accepts:
* **`single_use`**: exactly one successful payin. The first payment moves the template to `paid`, and a paid single-use template is closed history: it cannot be updated or reopened.
* **`multiple_use`**: any number of payins, bounded only by the cumulative totals (below) and the expiration.
## Lifecycle [#lifecycle]
* **`created`**: the template was accepted and persisted, but its payment tokens are still being set up, so it cannot receive payments yet. The setup is asynchronous: for Bre-B with `key_per: "template"` (the default), the template waits in `created` until the network confirms the key.
* **`ready`**: the template can receive payments. `payment_tokens` carries one entry per accepted method that issues a token: for Bre-B, the key and the QR when requested. Methods that need no token have no entry there.
* **`minimum_paid`**: the cumulative `paid_amount` reached `total_minimum_amount`. The template keeps accepting payments up to `total_maximum_amount`. An update that raises the minimum above what is already paid moves it back to `ready`.
* **`paid`**: the cumulative maximum was reached, or the `single_use` template received its payment. A `multiple_use` template can reopen (back to `ready` or `minimum_paid`) if an update raises or removes `total_maximum_amount`.
* **`discarded`**: the template was pruned (expiration or inactivity) or deleted, and its payment tokens were torn down. `prune_reason` explains which.
* **`failed`**: the payment-token setup exhausted its retries (for example, the Bre-B key registration failed). `state_reason` explains why.
Every reason behind a `failed` or `discarded` template is in [Template errors](/docs/guides/core/concepts/payins/templates/errors).
State transitions caused by an **update** (raising or lowering the totals) travel inside the `payin_template_updated` webhook with the previous values. The separate state webhooks are not sent for them. All other transitions emit their own [webhook](/docs/guides/core/webhooks/payins).
## Amount limits and counters [#amount-limits-and-counters]
Two pairs of limits, all denominated in the template's `currency`:
| Field | Scope | Effect |
| :---------------------------------------------- | :--------- | :------------------------------------------------------------------------------------------- |
| `minimum_payin_amount` / `maximum_payin_amount` | Each payin | A payment outside the range is rejected (`amount_below_minimum` / `amount_exceeds_maximum`). |
| `total_minimum_amount` / `total_maximum_amount` | Cumulative | Drive the `minimum_paid` and `paid` states. |
The template also keeps running counters: `paid_amount` (cumulative collected), `pending_amount` (authorized, in flight), and the `successful_payins` / `failed_payins` counts.
## Restricting who can pay [#restricting-who-can-pay]
`expected_payers` is an optional list of identity documents (type and number). When set, only those payers can pay the template: a payment from anyone else is rejected with `payer_not_allowed`. For Bre-B, the payer's document arrives with the payment itself, so the check needs nothing from the payer beyond paying from their own bank.
## Per-method configuration [#per-method-configuration]
`method_params` carries the configuration of each accepted method. For `co_breb`: whether to generate a QR (`create_qr`), a custom key alias (`custom_key_value`), whether the key is minted per template or per intent (`key_per`), and the QR merchant data. The details live in [Payment tokens](/docs/guides/core/concepts/payins/payment-tokens) and [Bre-B](/docs/guides/core/concepts/payins/methods/co-breb).
## Configuration inherited by every payin [#configuration-inherited-by-every-payin]
`payin_data` holds settings that every payin created from the template inherits:
* `subaccount_disbursement_trigger`: when the subaccount is credited: `on_approval` (as soon as the provider approves) or `on_funds_delivery` (when the money reaches its destination account). See [Crediting the subaccount](/docs/guides/core/concepts/payins/settlement/subaccount).
* `metadata`: attached to every payin, for your own reconciliation.
* `fee_override`: per-template fee overrides. See [Fees](/docs/guides/core/concepts/payins/fees).
* `redirect_url`: where the payer returns after paying on the hosted checkout. See [Hosted checkout or API-only](#hosted-checkout-or-api-only).
An intent created from the template can override these values for its own payin.
## Hosted checkout or API-only [#hosted-checkout-or-api-only]
A template supports two integration modes for the payer-facing side:
* **API-only**: your product owns the whole payer experience: you read the [payment token](/docs/guides/core/concepts/payins/payment-tokens) from the template or intent and render the key or QR in your own app, page, or receipt. Every flow in these guides works this way.
* **Mono-hosted checkout**: Mono serves the payment page for you: a hosted checkout where the payer completes the payin, rendered with the **brand** the template points at. Share the template's `checkout_url`, or an intent's `url`, and the payer pays there without you building a payment screen.
These fields drive the hosted checkout:
| Field | On | What it is |
| :------------------- | :------------------ | :------------------------------------------------------------------------------------------------------------------------------ |
| `checkout_url` | Template (response) | The hosted checkout URL of the template. |
| `url` | Intent (response) | The hosted page where the payer completes that specific intent. |
| `redirect_url` | `payin_data` | Where the payer is redirected after completing a payin on the hosted checkout. If omitted, Mono's default voucher page is used. |
| `form_customization` | Template | How the checkout is customized for this template: the brand it renders with, and the extra fields it asks the payer to fill. |
`redirect_url` and `form_customization` only take effect on the hosted checkout; an API-only integration does not need them.
### What `form_customization` carries [#what-form_customization-carries]
Two things, and neither of them is the branding itself:
| Field | Type | What it is |
| :-------------- | :------------- | :----------------------------------------------------------------------------------------------------------------------- |
| `brand_id` | string \| null | The brand the checkout renders with for this template. `null` means the checkout falls back to its default presentation. |
| `custom_fields` | array \| null | The fields the checkout asks the payer to fill **beyond the amount**: an ordered list, not a free-form object. |
The whole `form_customization` object comes back `null` when the template has neither a brand nor custom fields.
Each entry in `custom_fields` is a typed field:
| Property | What it does |
| :-------------- | :----------------------------------------------------------------------- |
| `label` | The field name the payer sees. Required. |
| `type` | What the payer writes: `text` (default), `number`, `boolean`, or `date`. |
| `default_value` | Value shown pre-filled in the form. |
| `required` | Whether the payer must fill it. Defaults to `false`. |
| `editable` | Whether the payer can change the pre-filled value. Defaults to `true`. |
```json
{
"form_customization": {
"brand_id": "cpitplb_5tgliBmzjZ6mpQPRbQjfKj",
"custom_fields": [
{
"label": "Order number",
"type": "text",
"default_value": "ORD-1042",
"editable": false
},
{ "label": "Delivery date", "type": "date", "required": true }
]
}
}
```
### Brands [#brands]
A **brand** is the reusable look your hosted checkout wears: a name, the text on the pay button, an accent color, a logo, and the message shown on the voucher after paying. It belongs to you, not to a single template. Many templates can point at the same brand, and changing the brand changes all of them at once. One of your brands is the default, used when a new template does not name one.
Brands are managed today from the **Core Dashboard**, not through the API: `brand_id` is the only part of them that appears in the payins contract, and it is a reference to a brand that already exists on your client. Brands are resolved within your own scope, so a `brand_id` that is not yours is rejected.
## Updating and deleting [#updating-and-deleting]
Updates are **presence-aware**: an omitted field keeps its value, an explicit `null` clears a nullable one. `payin_data` and `form_customization` are merged field by field, but a field holding a list is replaced whole. Sending `custom_fields` swaps the entire list, there is no per-field merge inside it, and the same goes for `expected_payers`. Setting `form_customization` to `null` clears the custom fields **and** unlinks the brand. `custom_merchant_name` is fixed at creation and cannot be updated. A template cannot be updated while still `created`, once `discarded` or `failed`, as a consumed single-use template, or while being pruned. Every successful update emits `payin_template_updated` with the previous values of the changed fields.
Deletion is a soft delete: the template stops accepting payins and its `external_id` is freed for reuse. It is only allowed when the template holds no money (`paid_amount` and `pending_amount` both zero) and is not in `created`, `discarded`, or `failed` state.
## Pruning [#pruning]
Templates do not live forever: a daily sweep retires the ones that are done (expired, inactive, or deleted) and hands their payment tokens back to the provider. Which rules fire, the grace periods you can configure, and what the `payin_template_discarded` webhook means for your integration are covered in [Pruning](/docs/guides/core/concepts/payins/templates/pruning).
## Data model [#data-model]
The full schema, request and response shapes live in the [Templates API reference](/docs/api-reference/core/payins#templates).
## Next steps [#next-steps]
* [Payment tokens](/docs/guides/core/concepts/payins/payment-tokens): the key and QR the template issues.
* [Intents](/docs/guides/core/concepts/payins/intents): declaring each expected payment individually.
* [Collect with a template](/docs/guides/core/flows/payins/collect-with-template): the end-to-end flow.
# Pruning (/docs/guides/core/concepts/payins/templates/pruning)
Templates do not live forever. A template that nobody pays anymore still holds live [payment tokens](/docs/guides/core/concepts/payins/payment-tokens) (Bre-B keys and QR codes registered with the network), and those cost money and add noise. **Pruning** is the daily sweep that retires the ones that are done and hands their tokens back.
You do not call anything to make it happen: Mono runs it for every program with payins enabled. What you control is when it triggers (the template's expiration and your program's grace periods) and what you do when it does (the `payin_template_discarded` webhook).
## What makes a template prunable [#what-makes-a-template-prunable]
Four rules, each recorded in `prune_reason` when it fires:
| Rule | A template is pruned when… | Default grace period |
| :---------------------------- | :------------------------------------------------------------------- | :------------------- |
| `expired` | Its `expires_at` passed. | 1 day after expiry |
| `inactivity_without_attempts` | It never received a payin and has been `ready` since it was created. | 30 days |
| `inactivity_with_attempts` | It has received payins, but the **last** one is too old. | 90 days |
| `deleted` | You deleted it: deletion triggers the same teardown, immediately. | None |
The grace periods are **defaults, configurable per program**: ask Mono to lengthen or shorten any of the three when payins is enabled for you, or later.
## The teardown runs in two phases [#the-teardown-runs-in-two-phases]
Pruning does not just flip a state: the tokens have to be given back to the provider first, and only then is the result recorded.
1. **Tokens are deregistered.** Mono asks each method's provider to discard the template's payment tokens. From that moment the Bre-B key and its QR stop working: no new payment can reach the template.
2. **The template is closed.** A template in `ready` or `minimum_paid` transitions to `discarded` and emits [`payin_template_discarded`](/docs/guides/core/webhooks/payins). A `paid` template keeps its `paid` state (it is already spent) and only records the pruning fields.
While the teardown runs, three fields track it:
| Field | What it tells you |
| :------------- | :----------------------------------------------------------------------- |
| `prune_state` | `pruning` while the tokens are being given back, `pruned` once they are. |
| `prune_reason` | Which of the four rules fired. |
| `pruned_at` | When the teardown completed. |
A prune that stalls (a provider confirmation that never arrives) is picked up again by the next day's sweep, so tokens never stay alive by accident.
## Intents are cleaned up too [#intents-are-cleaned-up-too]
Intents have their own clock: each one is closed by its own scheduled job when its window ends, and its tokens are torn down when it reaches a final state (`successful`, `failed`, or `expired`). The daily sweep also looks at intents, but only as a safety net, to catch the ones whose own job never ran.
## What to do about it [#what-to-do-about-it]
* **Treat `payin_template_discarded` as final.** Stop showing that template's key or QR: payments to it will not arrive anymore.
* **Set `expires_at` deliberately.** It is the only prune rule you control per template; the other two depend on your program's grace periods.
* **Do not rely on inactivity to close a campaign.** If a collection is over, delete the template: you get the teardown immediately instead of waiting out the grace period.
## Next steps [#next-steps]
* [Templates](/docs/guides/core/concepts/payins/templates): the entity this page retires.
* [Payment tokens](/docs/guides/core/concepts/payins/payment-tokens): what gets handed back to the provider.
* [Webhooks](/docs/guides/core/webhooks/payins): the template events, including `payin_template_discarded`.
# Intent errors (/docs/guides/core/concepts/payins/intents/errors)
An [intent](/docs/guides/core/concepts/payins/intents) carries `state_reason` in its two unsuccessful states:
* **`failed`**: there is nothing left to pay. Either the payment token never made it, or a payment closed the intent for good.
* **`expired`**: the window closed with nobody paying.
You read the code in the [`payin_intent_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_failed) and [`payin_intent_expired`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_expired) webhooks, and on the intent itself via [get](/docs/api-reference/core/payins/get_intent) or [list](/docs/api-reference/core/payins/list_intents).
### Example webhook payload [#example-webhook-payload]
```json
{
"timestamp": "2026-02-11T14:22:07.913204Z",
"event": {
"type": "payin_intent_failed",
"data": {
"id": "cpii_034EASMIAFVq06j2NxhnIJ",
"state": "failed",
"state_reason": "payment_token_registration_failed",
"external_id": "order-88213",
"template_id": "cpit_034EASMHlq36oZISbc9W4b"
}
}
}
```
## The intent's own reasons [#the-intents-own-reasons]
What can happen to an intent before a payment exists, plus the catch-all for a payment that failed without an explanation worth handing the payer.
| Code | Description | Cause | Suggested fix |
| ----------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `expired` | Window closed | Nobody paid before `expires_at`. For Bre-B the window is 30 minutes. | Create a new intent. The previous one releases its `external_id` when it expires. |
| `key_registration_failed` | Key registration failed | Registering the intent's own payment key with the network exhausted its retries. | Create the intent again. If it repeats, contact support with the intent `id`. |
| `payment_token_registration_failed` | Payment token setup failed | The key registered, but the payment token could not be assembled from it. | Create the intent again. If it repeats, contact support with the intent `id`. |
| `payment_failed` | Payment failed | A payment closed the intent for a reason that says nothing to the payer: an infrastructure fault such as a timeout, a ledger failure or a provider outage. | Read the payin's own `state_reason` for the detail; create a new intent to retry. |
| `unknown` | Unknown | The payment that closed the intent failed without a known reason. | Contact support with the intent `id`. |
## Reasons inherited from the payment [#reasons-inherited-from-the-payment]
When a payment closes an intent for a reason the payer or the merchant can act on, the intent adopts **the payin's reason under the same name** instead of flattening it into `payment_failed`. Each of these means exactly what it means on the payin, so the table there is the reference:
| Group | Codes |
| :----------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- |
| [Amount limits](/docs/guides/core/concepts/payins/payins/errors#amount-limits) | `amount_below_minimum`, `amount_exceeds_maximum`, `amount_below_total_minimum`, `amount_exceeds_total_maximum`, `intent_amount_mismatch` |
| [Payer restrictions](/docs/guides/core/concepts/payins/payins/errors#payer-restrictions) | `payer_not_allowed` |
| [Bank and risk](/docs/guides/core/concepts/payins/payins/errors#bank-refusal) | `bank_rejected`, `risk_control` |
| [Template conditions](/docs/guides/core/concepts/payins/payins/errors#template-conditions) | `template_paid`, `template_expired`, `template_disabled`, `template_deleted`, `template_not_found`, `template_invalid_state` |
The split is deliberate: what the payer can act on (the amount, being the wrong payer) and what the merchant needs to tell apart (which way the collection stopped taking payments, or that the receiving account cannot take money) keep their name; the infrastructure faults do not, because they mean nothing to whoever is looking at a checkout.
These inherited reasons only appear for payment methods that **do not re-arm** an intent after
a failed payment. Bre-B, the only method available today, re-arms: a failed payment returns
the intent to `ready_to_pay` so the payer can retry with the same key while the window lasts,
and the intent is not closed at all. Until another method ships, an intent that ends `failed`
will carry one of [its own reasons](#the-intents-own-reasons). Handle the inherited ones
defensively rather than expecting them.
## A failed payment is not always a failed intent [#a-failed-payment-is-not-always-a-failed-intent]
Most payment failures do **not** end the intent. With a re-arming method the intent goes back to `ready_to_pay` and emits [`payin_intent_attempt_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_attempt_failed). The failed attempt itself is reported by the payin's own `payin_rejected` or `payin_failed` webhook, which carries the real reason.
So there are two different questions, and two different events:
| Question | Event | Where the reason lives |
| :-------------------------------- | :--------------------------------------------- | :------------------------------------------------------------------------------------------------ |
| Why did this attempt fail? | `payin_rejected` / `payin_failed` | The payin's `state_reason` (see [Payin errors](/docs/guides/core/concepts/payins/payins/errors)). |
| Why can this order never be paid? | `payin_intent_failed` / `payin_intent_expired` | The intent's `state_reason` (this page). |
## Handling them [#handling-them]
```ts
app.post('/webhook', (req, res) => {
const { event } = req.body;
// The order can still be paid: the payer just has to retry
if (event.type === 'payin_intent_attempt_failed') {
// Keep the checkout open; the key is still valid
}
// The order is over
if (event.type === 'payin_intent_failed' || event.type === 'payin_intent_expired') {
const { external_id, state_reason } = event.data;
switch (state_reason) {
case 'expired':
// Nobody paid in time: offer a new checkout
break;
case 'key_registration_failed':
case 'payment_token_registration_failed':
// The intent never became payable: create it again
break;
default:
// Payment closed it: read the payin for the detail, then issue a new intent
}
}
res.status(200).send();
});
```
Do not infer payment from the absence of a failure. `GET
/v1/core/payins/intents/find_successful_by?external_id=...` answers it directly: at most one
`successful` intent exists per identifier.
## Next steps [#next-steps]
* [Payin errors](/docs/guides/core/concepts/payins/payins/errors): the reason behind every failed attempt.
* [Template errors](/docs/guides/core/concepts/payins/templates/errors): why the template behind the intent stopped being payable.
* [Collect with intents](/docs/guides/core/flows/payins/collect-with-intents): the end-to-end flow.
# Intents (/docs/guides/core/concepts/payins/intents)
An **intent** is one expected payment, declared before the money moves. Where a [template](/docs/guides/core/concepts/payins/templates) says "I accept payments under these rules", an intent says "I am expecting **this** payment": one payer, one amount, one window of time, one payment token of its own. When the payment arrives, Mono links it back to the intent, so you know exactly which order, invoice, or checkout session was paid, before reading a single payin field.
Use intents when each payment must be pre-identified: e-commerce checkout, invoice payment, or any flow where "who is paying what" matters more than "money arrived".
## With or without a template [#with-or-without-a-template]
An intent is created in one of two ways (`template_id` and `subaccount_id` are mutually exclusive):
* **From a template**: the intent inherits the template's rules (limits, expected payers, `payin_data`) and can override some for itself. For Bre-B, the template must mint its keys per intent (`method_params.co_breb.key_per: "intent"`); a template with a shared key cannot link payments back to individual intents.
* **Templateless**: the intent is created directly against a subaccount and carries its own configuration. Mono provisions a dedicated single-use payment token for it.
Creation is idempotent on `external_id`: repeating the request returns the existing intent. When an intent **expires**, its `external_id` is freed for a new one, which is what makes the retry loop of a checkout safe.
## Amounts [#amounts]
Provide exactly one of `merchant_amount` or `payer_amount`; the other is derived. With the only method available today (Bre-B, COP) both amounts are equal. The two fields exist so that cross-currency collection can land later without changing the contract. The amount is **enforced**: a Bre-B payment for a different amount is rejected (`intent_amount_mismatch`) and the intent re-arms for a retry.
## Lifecycle [#lifecycle]
* **`created`**: accepted and persisted; the payment token is still being prepared, so `payment_tokens` is `null` and the payer cannot pay yet.
* **`ready_to_pay`**: the intent is payable: `payment_tokens` carries the Bre-B key (and single-use QR, when requested) and `expires_at` is set. For Bre-B the window is 30 minutes.
* **`awaiting_payment`**: a payment for the intent was authorized and is in flight.
* **`successful`**: the payment was confirmed; the intent is spent, its tokens are torn down, and it can never be paid again.
* **`failed`**: nothing left to pay: the payment token never made it (`key_registration_failed`, `payment_token_registration_failed`) or a payment failed on a method that does not re-arm (`payment_failed`).
* **`expired`**: the window closed with nobody paying. `state_reason` is `expired`.
Every reason an intent can end on, and what to do about each, is in [Intent errors](/docs/guides/core/concepts/payins/intents/errors).
**Re-arming**: when a payment attempt fails after authorization but the intent is still alive, the intent returns to `ready_to_pay`. The payer can retry against the same key while the window lasts. The failed attempt itself is reported by the payin's own `payin_rejected` or `payin_failed` webhook; the intent emits `payin_intent_attempt_failed`.
## Answering "was this paid?" [#answering-was-this-paid]
`GET /v1/core/payins/intents/find_successful_by?external_id=...` returns the single `successful` intent for a tenant-side identifier. At most one exists per client. It lets your reconciliation ask about your own order ID without storing Mono IDs.
## Hosted checkout [#hosted-checkout]
`url` is the Mono-hosted page where the payer completes that specific intent. Share it with the payer when you would rather not build the payment screen yourself. See [Hosted checkout or API-only](/docs/guides/core/concepts/payins/templates#hosted-checkout-or-api-only).
## Fees [#fees]
An intent's `calculated_fees` arrives **empty**. The fees actually charged appear on the [payin](/docs/guides/core/concepts/payins/payins).
## Data model [#data-model]
The full schema lives in the [Intents API reference](/docs/api-reference/core/payins#intents).
## Next steps [#next-steps]
* [Payment tokens](/docs/guides/core/concepts/payins/payment-tokens): per-intent keys and dynamic QR codes.
* [Collect with intents](/docs/guides/core/flows/payins/collect-with-intents): the end-to-end flow.
* [Webhooks](/docs/guides/core/webhooks/payins): the seven intent events.
# Payin errors (/docs/guides/core/concepts/payins/payins/errors)
A [payin](/docs/guides/core/concepts/payins/payins) carries `state_reason` only in its two unsuccessful states, and which state it lands in tells you **who stopped the payment**:
* **`rejected`**: Mono or the receiving bank refused the payment. A business, configuration, payer, or receiving-account condition did not hold. The payer's funds, if any, are returned by the provider.
* **`failed`**: the payment broke down. Either the payment rail reported a fault after the payment was already authorized, or Mono could not finish processing it.
You read the code in the [`payin_rejected`](/docs/api-reference/core/payin-webhooks/webhook_payin_rejected) and [`payin_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_failed) webhooks, and on the payin itself via [get](/docs/api-reference/core/payins/get_payin) or [list](/docs/api-reference/core/payins/list_payins).
### Example webhook payload [#example-webhook-payload]
```json
{
"timestamp": "2026-02-11T14:22:07.913204Z",
"event": {
"type": "payin_rejected",
"data": {
"id": "cpi_034EASMIAFVq06j2NxhnIJ",
"state": "rejected",
"state_reason": "amount_exceeds_maximum",
"template_id": "cpit_034EASMHlq36oZISbc9W4b",
"intent_id": null,
"merchant_amount": { "currency": "COP", "amount": 850000 }
}
}
}
```
## Rejected: the payment was refused [#rejected-the-payment-was-refused]
### Feature and configuration [#feature-and-configuration]
Something on your side of the account is not set up to receive this payment. None of these depend on the payer.
| Code | Description | Cause | Suggested fix |
| ------------------------- | ------------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `payins_feature_disabled` | Payins disabled for the tenant | The payins feature is not enabled for your account. | Contact Mono to enable it before collecting. |
| `method_disabled` | Payment method disabled | The payment method the payer used is disabled for your account. | Contact Mono to enable the method. |
| `method_not_allowed` | Method not allowed here | The method is enabled for you, but not permitted for this template or intent. | Create the template or intent with a method your program allows. |
| `subaccount_inactive` | Subaccount inactive | The subaccount that would receive the money is not active. | Reactivate the subaccount, or collect into an active one. |
### Template conditions [#template-conditions]
The [template](/docs/guides/core/concepts/payins/templates) the payment was addressed to is no longer accepting it.
| Code | Description | Cause | Suggested fix |
| ------------------------ | -------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `template_disabled` | Template disabled | The template has `enabled: false`, so it takes no payments while it stays that way. | Enable the template, or stop distributing its payment token. |
| `template_invalid_state` | Template not payable | The template's state does not accept payments (for example it is already `paid`). | Check the template's `state`; create a new one if the collection is over. |
| `template_deleted` | Template deleted | The template was deleted; its payment tokens are torn down. | Create a new template and distribute its token. |
| `template_expired` | Template expired | The payment arrived after the template's `expires_at`. | Create a new template, or extend the window before it lapses. |
### Intent conditions [#intent-conditions]
The [intent](/docs/guides/core/concepts/payins/intents) the payment was addressed to cannot take it.
| Code | Description | Cause | Suggested fix |
| ------------------------ | --------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ |
| `intent_invalid_state` | Intent not payable | The intent's state does not accept payments: most often it is already `successful` and cannot be paid twice. | Create a new intent for a new payment. |
| `intent_expired` | Intent expired | The payment arrived after the intent's window closed. For Bre-B the window is 30 minutes. | Create a new intent; its `external_id` is freed when the previous one expires. |
| `intent_amount_mismatch` | Amount does not match | The intent's amount is enforced, and the payer sent a different one. | Show the payer the exact amount. The intent re-arms, so the same key can be retried. |
### Amount limits [#amount-limits]
The amount is outside what the template or intent accepts. The first two are per payment; the last two are about the cumulative total already collected.
| Code | Description | Cause | Suggested fix |
| ------------------------------ | ----------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `amount_below_minimum` | Below the per-payment minimum | The amount is under `minimum_payin_amount`. | Show the minimum to the payer before they pay. |
| `amount_exceeds_maximum` | Above the per-payment maximum | The amount is over `maximum_payin_amount`. | Show the maximum, or split the collection across several payments. |
| `amount_below_total_minimum` | Below the cumulative minimum | The payment would leave the template under `total_minimum_amount` and cannot be accepted as is. | Ask for an amount that reaches the minimum. |
| `amount_exceeds_total_maximum` | Above the cumulative maximum | The payment would push the template past `total_maximum_amount`. | Check `paid_amount` against the total; raise the total or close the template. |
### Payer restrictions [#payer-restrictions]
| Code | Description | Cause | Suggested fix |
| ------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `payer_not_allowed` | Payer not allowed | The template or intent restricts who may pay through [`expected_payers`](/docs/guides/core/concepts/payins/templates#restricting-who-can-pay), and the payer is not on the list. | Verify the document type and number you registered, or drop the restriction. |
### Bank refusal [#bank-refusal]
This is about the **receiving** end, not the payer's. Before the money is accepted, Mono asks the bank holding the destination account whether it can take the payment. A refusal there stops the payment.
| Code | Description | Cause | Suggested fix |
| --------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `bank_rejected` | Rejected by the receiving bank | The bank that holds the receiving account refused the incoming payment: the account may be deactivated, or the bank no longer allows it to receive funds. | Not retryable. The payer can do nothing about it. Check the account's status with Mono before collecting again. |
## Failed: the payment broke down [#failed-the-payment-broke-down]
### Reported by the payment rail [#reported-by-the-payment-rail]
The payment was already authorized when the rail reported a fault. These are infrastructure conditions, not decisions about your collection.
| Code | Description | Cause | Suggested fix |
| ---------------------- | ----------------------- | ----------------------------------------------------- | ----------------------------------------------------------------- |
| `risk_control` | Blocked by risk control | The provider's risk controls stopped the payment. | Review the payment pattern. It may require contacting support. |
| `provider_unavailable` | Provider unavailable | The payment provider was out of service. | No action on the payment; the payer can retry. |
| `breb_timeout` | Bre-B timeout | The Bre-B rail did not answer in time. | The payer can retry. |
| `mono_timeout` | Mono timeout | An internal Mono system did not answer in time. | The payer can retry. If it persists, contact support. |
| `internal_error` | Internal error | An unclassified failure while processing the payment. | Contact support with the payin `id`. |
| `transfer_reversed` | Transfer reversed | The rail reversed the transfer after accepting it. | Do not deliver against this payin. Reconcile it as not collected. |
### Reported by the rail about the template [#reported-by-the-rail-about-the-template]
These two are the rail's own view of the collection behind your template, so they only arrive once the payment is in flight.
| Code | Description | Cause | Suggested fix |
| -------------------- | --------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `template_paid` | Already paid | The rail found the collection already paid: a single-use template can only take one payment. | Create a new template for the next payment. |
| `template_not_found` | Not found on the rail | The rail has no collection for the token the payment was addressed to. | Contact support with the payin `id` and the template `id`. |
### Mono processing [#mono-processing]
| Code | Description | Cause | Suggested fix |
| -------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `lock_timeout` | Processing lock timeout | Mono could not take the template's processing lock in time: usually many simultaneous payments against one template. | The payer can retry. If it is frequent, spread the load across templates. |
| `unknown` | Unknown | Any other internal failure, or a reason the payment rail reported without a counterpart here. | Contact support with the payin `id`. |
A few codes exist on both paths: `bank_rejected` and the amount and payer codes among them.
When Mono's own validation refuses the payment the payin is `rejected`; when the rail reports
the same fault after the payment was authorized it is `failed`. Read `state` alongside
`state_reason` rather than branching on the reason alone.
## Handling them [#handling-them]
### Retry guidance [#retry-guidance]
"Retryable" here means the payer can pay again against the same payment token. No payin is ever retried by itself.
| Code | Retryable | Recommendation |
| -------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------- |
| `provider_unavailable`, `breb_timeout`, `mono_timeout` | Yes | Ask the payer to retry in a few minutes. |
| `lock_timeout` | Yes | Retry immediately; the lock is short-lived. |
| `intent_amount_mismatch`, `amount_below_minimum`, `amount_exceeds_maximum` | Yes | Show the right amount and let the payer retry. |
| `template_expired`, `template_paid`, `intent_expired` | No | The collection is over. Create a new template or intent. |
| `bank_rejected` | No | The receiving account cannot take payments. Resolve it with Mono. |
| `payer_not_allowed` | No | Fix `expected_payers`, or collect from the registered payer. |
| `transfer_reversed` | No | The money went back. Reconcile as not collected. |
| `internal_error`, `unknown`, `template_not_found` | No | Contact support with the payin `id`. |
### Reacting to the webhook [#reacting-to-the-webhook]
```ts
app.post('/webhook', (req, res) => {
const { event } = req.body;
if (event.type === 'payin_rejected' || event.type === 'payin_failed') {
const { id, state, state_reason } = event.data;
switch (state_reason) {
case 'amount_below_minimum':
case 'amount_exceeds_maximum':
case 'intent_amount_mismatch':
// Show the payer the amount the collection accepts
break;
case 'payer_not_allowed':
// Surface that this payer is not the one expected
break;
case 'template_expired':
case 'template_paid':
case 'intent_expired':
// The collection is over: issue a new one
break;
case 'transfer_reversed':
// The rail took the money back; reconcile as not collected
break;
case 'bank_rejected':
// The receiving account cannot take payments: alert your team, not the payer
break;
default:
// Infrastructure or unknown: log with `id` and let the payer retry
}
}
res.status(200).send();
});
```
When contacting support about a payin, provide the payin `id`, the `template_id` or
`intent_id` it belongs to, and, for Bre-B, the `transaction_data.end_to_end_id`, which
identifies the transfer across the whole network.
## Next steps [#next-steps]
* [Template errors](/docs/guides/core/concepts/payins/templates/errors): why a template stops being payable.
* [Intent errors](/docs/guides/core/concepts/payins/intents/errors): why an intent ends without a payment.
* [Webhooks](/docs/guides/core/webhooks/payins): every payin event and its payload.
# Payins (/docs/guides/core/concepts/payins/payins)
A **payin** is one received payment: the record Mono creates when money actually arrives against a [template](/docs/guides/core/concepts/payins/templates) or an [intent](/docs/guides/core/concepts/payins/intents). It captures who paid, how much on every leg of the journey (the [four amounts](/docs/guides/core/concepts/payins)), which token received it, and two state machines: the payment's own lifecycle and the delivery of the funds to your funding account.
You never create a payin directly; payments create them. Your integration reads them (`list` and `get`), reconciles them against your orders, and reacts to their [webhooks](/docs/guides/core/webhooks/payins).
## Lifecycle [#lifecycle]
* **`created`**: the payment was registered and is being validated and authorized. No funds have moved to the subaccount.
* **`awaiting_payment`**: authorized; the payment is in flight at the provider. A brief state with no webhook of its own.
* **`approved_in_provider`**: the provider confirmed the payment. The money exists on the provider side; `funds_delivery_status` is set according to your program configuration.
* **`subaccount_credited`**: the funds were credited to the subaccount for `subaccount_amount`. For most integrations this is the "payment complete" signal. Delivery to your funding account is reported separately by the [settlement batch](/docs/guides/core/concepts/payins/settlement/funding) events.
* **`rejected`**: a business, configuration, or payer condition stopped the payment. The payer's funds, if any, are returned by the provider.
* **`failed`**: a system or provider failure prevented processing.
## The payin's sub-entities [#the-payins-sub-entities]
Three structures live inside every payin and belong to it alone: no other entity carries them. Together they answer the three questions of any received payment: *who paid it, where did it land, and which rail transaction was it?* Each is a per-method structure: its `kind` (or the payin's `method`) tells you which shape to read, so new payment methods add shapes without changing the payin itself.
### Instrument: who paid [#instrument-who-paid]
`instrument` is the payer-side origin of the payment: the account the money came from, and inside it, the person or company that owns it (`instrument.payer`). For Bre-B nothing is collected upfront. The network delivers it with the payment:
| Part | Shape (Bre-B) | Carries |
| :----------------- | :---------------- | :--------------------------------------------------------------------------------------------- |
| `instrument` | `co_breb_account` | Account number, account type, currency, and the NIT of the participant institution. |
| `instrument.payer` | `co_person` | Names, surnames, document type and number, and whether it is a natural person or legal entity. |
This is also what [`expected_payers`](/docs/guides/core/concepts/payins/templates#restricting-who-can-pay) is validated against, and your main handle for knowing who paid (for reconciliation, and for any identity checks your own compliance runs on incoming money).
### Recipient data: where it landed [#recipient-data-where-it-landed]
`recipient_data` is the receiving side: which [payment token](/docs/guides/core/concepts/payins/payment-tokens) the payment was addressed to. For Bre-B it carries the key (`key_type`, `key_value`) and the provider's `payment_id` (the QR code identifier, when the payer scanned one). It is the join back to the token's owner: `key_value` matches the `payment_tokens.co_breb.key_value` of the template or intent that issued it, and `payment_id` matches the QR's. Use it to tell campaigns apart when several templates collect into the same subaccount, or to distinguish key payments from QR payments.
### Transaction data: which rail transaction [#transaction-data-which-rail-transaction]
`transaction_data` identifies the payment on the rail itself. For Bre-B it carries the `end_to_end_id`: the network-wide identifier of the underlying transfer, the value to quote in disputes or when tracing a payment with Mono support or with the payer's bank.
## Rejection and failure reasons [#rejection-and-failure-reasons]
`state_reason` is set only in the `rejected` and `failed` states, and the state tells you who stopped the payment:
| State | What happened | Reasons |
| :--------- | :------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------- |
| `rejected` | Mono or the receiving bank refused it: a configuration, template, intent, amount, payer, or receiving-account condition did not hold. | Feature and configuration, template and intent conditions, amount limits, payer restrictions, and `bank_rejected`. |
| `failed` | The payment broke down: the rail reported a fault after authorization, or Mono could not finish processing it. | Rail faults and the reversal, plus `lock_timeout` and `unknown`. |
Every code, with its cause and what to do about it, is in [Payin errors](/docs/guides/core/concepts/payins/payins/errors).
## Funds delivery fields [#funds-delivery-fields]
Beyond its own state, a payin tracks how the real money reaches you. The details live in [Delivering to your funding account](/docs/guides/core/concepts/payins/settlement/funding):
* `funds_destination`: `funding_account` means the funds reach you through a settlement batch; `method_receiving_account` means the delivery completes immediately and the payin never appears in a settlement batch.
* `funds_delivery_status`: `null` until the payin is `approved_in_provider`, then `pending_to_deliver` → `processing` → `completed`.
* `settlement_batch_id`: the settlement batch that delivered the funds, once one covers this payin.
`subaccount_disbursement_trigger` records when the subaccount is credited (`on_approval` or `on_funds_delivery`), inherited from the template or intent that produced the payin. See [Crediting the subaccount](/docs/guides/core/concepts/payins/settlement/subaccount).
## Reconciliation fields [#reconciliation-fields]
`metadata` merges the `payin_data.metadata` of the payin's template and intent (the intent's values win). `template_id`, `intent_id`, `subaccount_id`, and `funding_account_id` link the payin to everything around it. `calculated_fees` breaks down what was charged, per [fee type](/docs/guides/core/concepts/payins/fees).
## Data model [#data-model]
The full schema and list filters live in the [Payins API reference](/docs/api-reference/core/payins#payins).
## Next steps [#next-steps]
* [Settlement](/docs/guides/core/concepts/payins/settlement): the two movements behind every payin.
* [Fees](/docs/guides/core/concepts/payins/fees): what `calculated_fees` contains.
* [Webhooks](/docs/guides/core/webhooks/payins): the five payin events.
# Bre-B (/docs/guides/core/concepts/payins/methods/co-breb)
**Bre-B** is Colombia's instant-payment scheme, operated by Banco de la República: bank-to-bank transfers that settle in seconds, around the clock, addressed by a **key** (*llave*, a short alias like a cellphone number registered in a central directory) or a **QR code**. `co_breb` is the payin method that collects over that rail: Mono registers a key for you, the payer pays to it from their own banking app, and the money is validated and credited within seconds.
It is the only payin method available today, and it settles exclusively in COP.
## How a Bre-B payin works [#how-a-bre-b-payin-works]
1. Your [template](/docs/guides/core/concepts/payins/templates) (or [intent](/docs/guides/core/concepts/payins/intents)) requests a Bre-B [payment token](/docs/guides/core/concepts/payins/payment-tokens). Mono registers the key with the network, an asynchronous step announced by the `ready` / `ready_to_pay` webhooks.
2. You show the key or QR to the payer.
3. The payer pays from their bank's app, by entering the key or scanning the QR. No checkout form, no card data, no credentials on your side.
4. The network presents the payment to Mono, which validates it against your rules (amounts, expected payers, template state) and authorizes or rejects it in the same interaction.
5. The [payin](/docs/guides/core/concepts/payins/payins) is created and moves through its lifecycle; webhooks keep you informed.
Because the payment originates in the payer's bank, the payer's identity (`instrument.payer`: names, document, person type) and account (`instrument.data`) arrive **with the payment**, which is what makes `expected_payers` enforceable without asking the payer for anything.
## Keys and QR codes [#keys-and-qr-codes]
| Token | Scope | Behavior |
| :-------------------------- | :------------------------- | :----------------------------------------------------------- |
| Key (`key_per: "template"`) | Shared: the whole template | Static: any payer, any number of payments (`multiple_use`). |
| Static QR | Shared: the whole template | Encodes the template key; no fixed amount. |
| Key (`key_per: "intent"`) | One intent | Minted per expected payment; ties the payment to the intent. |
| Dynamic QR | One intent | Single use, fixed amount, expires with the intent. |
Key types are assigned by the network at registration: `cellphone`, `email`, `alphanumeric`, `identification`, or `merchant_code`. You can request a custom alias with `method_params.co_breb.custom_key_value` and QR merchant data with `qr_code_data`.
## Limits and timing [#limits-and-timing]
* **Per-transaction cap**: a single Bre-B payin is capped at **$12,110,000 COP**. This is the network's per-transaction limit as applied by Mono; the network adjusts it periodically, so treat it as a current value rather than a constant. A payment above it is rejected by the rail. A template's `maximum_payin_amount` can only tighten this cap, never exceed it.
* **Intent window**: a Bre-B intent is payable for **30 minutes** after `ready_to_pay`. Within that window, failed attempts re-arm the intent: the payer can retry against the same key.
* **Settlement**: approved payins reach your funding account in [settlement batches](/docs/guides/core/concepts/payins/settlement/funding).
## Next steps [#next-steps]
* [Payment tokens](/docs/guides/core/concepts/payins/payment-tokens): the key and QR model in detail.
* [Top-up flow](/docs/guides/core/flows/payins/topup): a permanent key per subaccount.
* [Collect with intents](/docs/guides/core/flows/payins/collect-with-intents): dynamic QRs per payment.
# Methods (/docs/guides/core/concepts/payins/methods)
A **payment method** is the rail a payin travels on, plus everything the rail implies: what the payer pays to, which currencies it settles, its limits, and how much Mono knows about the payer. Templates declare the methods they accept in `available_methods`; every payin records the one that actually delivered it in `method`.
One method is available today. The matrix grows a column per method as new ones launch, without changing templates, intents, or payins.
## Method matrix [#method-matrix]
| Capability | [`co_breb`](/docs/guides/core/concepts/payins/methods/co-breb) |
| :---------------------------- | :----------------------------------------------------------------------------------------- |
| Rail | Bre-B, Colombia's instant-payment scheme |
| Payer pays to | A Bre-B key or QR code ([payment token](/docs/guides/core/concepts/payins/payment-tokens)) |
| Settlement currency | COP |
| Payer data (`instrument`) | Arrives with the payment; nothing collected upfront |
| `expected_payers` enforcement | Yes: validated against the payer document that arrives with the payment |
| Intent window | 30 minutes from `ready_to_pay` |
| Retry after a failed attempt | Yes: the intent re-arms while it lives |
| Per-transaction limit | Bre-B network cap (see [Bre-B](/docs/guides/core/concepts/payins/methods/co-breb)) |
## Program configuration [#program-configuration]
Enabling payins is a program-level setup you align with Mono. There is no self-service switch. When you request it, expect to define, per method:
* **Methods and currencies**: which methods your program collects with, and in which currencies.
* **Funding account**: the bank account where the real money lands. See [Delivering to your funding account](/docs/guides/core/concepts/payins/settlement/funding).
* **Disbursement default**: the program-wide `subaccount_disbursement_trigger` (templates and intents can override it).
* **Pruning grace periods**: how long templates survive expiration or inactivity before being discarded.
* **Merchant identity**: the default merchant name shown to payers, and for Bre-B, the QR merchant data (category code, city, postal code) and key-naming rules.
## Next steps [#next-steps]
* [Bre-B](/docs/guides/core/concepts/payins/methods/co-breb): the method available today, in detail.
* [Payment tokens](/docs/guides/core/concepts/payins/payment-tokens): keys and QRs across methods.
* [Flows](/docs/guides/core/flows/payins): what to build with them.
## Locale: es
# Quiénes somos (/es/docs/guides/who-we-are)
Mono es infraestructura bancaria para América Latina. Conectamos empresas que mueven dinero, como parte de su operación, con la red bancaria colombiana y Bre-B, el esquema de pagos instantáneos. A través de una sola API, nuestros clientes mueven dinero dentro y fuera de cuentas bancarias, mantienen saldos en un ledger programable, emiten tarjetas y orquestan pagos — sin tener que convertirse en un banco ni construir las integraciones desde cero.
Existimos porque construir productos financieros en América Latina es más difícil de lo que debería. Cada rail (PSE, Turbo, ACH, Transfiya, Bre-B) tiene sus particularidades, su regulación y su ritmo operativo. Mono absorbe esa complejidad detrás de una sola API para que nuestros clientes se enfoquen en lo que quieren construir en vez de en cómo conectarlo.
## Qué hacemos [#qué-hacemos]
| Capacidad | Qué te da |
| :---------------- | :------------------------------------------------------------------------------------------------------------- |
| Banking | Recaudo (PSE), dispersión (ACH, Transfiya, Mono Turbo) y emisión de tarjetas sobre la red bancaria colombiana. |
| Core | Ledger programable, controles de gasto y orquestación de dispersiones. |
| Bre-B Participant | Acceso programable a Bre-B — el esquema de pagos instantáneos 24/7 de Colombia. |
## A quién servimos [#a-quién-servimos]
Empresas colombianas que manejan dinero como parte de su producto:
* **Fintechs** — neobancos, billeteras digitales, productos de crédito y de ahorro.
* **Marketplaces** — pagos de compradores, dispersiones a vendedores, custodia (escrow).
* **Embedded finance** — productos no financieros que suman una capa financiera (plataformas de gig, SaaS con facturación, compras B2B).
* **SEDPEs y entidades licenciadas** — instituciones que necesitan una conexión como participante de Bre-B sin construirla ellas mismas.
* **Equipos de tesorería y finanzas corporativas** — dispersiones programáticas a escala.
## Cómo operamos [#cómo-operamos]
Sostenemos las relaciones regulatorias y con los rails para que nuestros clientes no tengan que hacerlo. Eso significa:
* **Cobertura nativa de Bre-B** como participante, no como revendedor.
* **Transferencias multi-rail** con fallback automático: si un Mono Turbo falla, reintenta por ACH.
* **Regulación local** — operamos bajo la regulación financiera colombiana y mantenemos los controles que esto exige.
* **Confiabilidad de producción** — cada llamada a la API está autenticada, es idempotente y observable; cada cambio de estado se notifica por webhook.
## Diferenciadores [#diferenciadores]
Lo que hace a Mono distinto, expresado en lo que le importa a quien evalúa:
* **Una API, todos los rails.** Banking, Core y Bre-B comparten la misma autenticación, el mismo modelo de errores y las mismas convenciones operativas.
* **Bre-B nativo.** Mono es participante de Bre-B, no un intermediario — eso significa menor latencia, control total sobre las llaves de pago y settlement directo.
* **Ledger programable.** El ledger de Core es de partida doble y append-only por diseño; la conciliación es una consulta, no un proyecto.
* **LATAM primero.** Construido para los rails colombianos y para equipos de producto latinoamericanos, no adaptado desde un stack pensado para Estados Unidos.
## Dónde estamos [#dónde-estamos]
Hoy: **Colombia.** Nos estamos expandiendo regionalmente; contacta a tu equipo de cuenta si estás evaluando Mono para un país que aún no hemos anunciado.
## Siguientes pasos [#siguientes-pasos]
* [Por qué usar Mono](/docs/guides/why-mono) — casos de uso y criterios de ajuste.
* [Banking](/docs/guides/banking), [Core](/docs/guides/core), [Bre-B Participant](/docs/guides/breb-participant) — guías de servicio.
* [Soporte técnico](/docs/guides/technical-support) — cómo contactarnos.
# Herramientas de IA (MCP) (/es/docs/guides/ai-tools)
Mono publica un **servidor MCP** para esta documentación. MCP (Model Context
Protocol) es un estándar abierto que permite a los asistentes de IA — como
Claude o Cursor — conectarse a herramientas y datos externos. Al conectar el MCP
de Mono, tu asistente puede buscar en estos documentos, abrir páginas completas
y traer recetas de integración listas para usar mientras programas. Si lo
activas, también puede ejecutar llamadas reales contra los sandboxes de **Bre-B**
y **Core** — entornos de pruebas seguros que nunca mueven dinero real — para que
pruebes una integración de principio a fin sin salir de tu editor.
Responde a partir de la misma documentación que estás leyendo y enlaza a la fuente, así la
guía se mantiene fundamentada y al día — no inventada.
## Antes de empezar [#antes-de-empezar]
Necesitarás:
* Una herramienta de IA compatible con MCP — por ejemplo **Claude Code** o **Cursor**.
* *Solo para llamadas en sandbox (opcional):* credenciales de sandbox del
producto que quieras probar. Cada producto se autentica distinto:
* **Bre-B** (el sistema de pagos instantáneos de Colombia) — tu `client_id` y
`client_secret` de sandbox desde el panel de Mono, con los scopes de Bre-B
habilitados (recaudos, dispersiones, resolución de llaves y cuentas tenant).
El MCP inicia sesión con ellas y solicita esos scopes; si falta uno, las
llamadas en sandbox se rechazan.
* **Core** (ledger programable y emisión de tarjetas) — un **token de sandbox
de Core** que Mono genera para ti. El MCP lo reenvía en cada llamada.
Puedes agregar Bre-B, Core o ambos — el asistente obtiene las herramientas de
cada producto cuando sus credenciales están presentes.
## Qué puede hacer tu asistente [#qué-puede-hacer-tu-asistente]
Leer la documentación no requiere nada; ejecutar llamadas requiere las
credenciales de sandbox correspondientes.
**Leer la documentación (sin credenciales):**
* buscar en la documentación por significado, no solo por palabras clave;
* abrir cualquier página completa;
* obtener una guía de integración paso a paso para un caso de uso;
* obtener la referencia exacta del API de un endpoint — ejemplos de petición y
respuesta, campos y valores permitidos.
**Ejecutar llamadas en sandbox de Bre-B:** crear un recaudo, generar códigos QR
para cobrarlo, simular un pago (por llave o por QR), resolver una llave de pago
y enviar una dispersión.
**Ejecutar llamadas en sandbox de Core:** gestionar el ledger (cuentas, saldos,
transferencias) y emitir y administrar tarjetas (titulares, tarjetas, controles
de gasto, activación).
## Conectar para leer la documentación [#conectar-para-leer-la-documentación]
Este modo lo aloja Mono y no necesita credenciales. En **Claude Code**:
```bash
claude mcp add --transport http mono-docs https://docs.cert.monoban.co/api/mcp
```
Otros clientes MCP (Cursor, etc.) se conectan a la misma URL por HTTP (también
llamado "Streamable HTTP"). Luego solo pregunta, en lenguaje natural:
> ¿Cómo recibo mi primer pago por Bre-B? Muéstrame la petición para crear el recaudo.
Tu asistente busca en los documentos, trae la guía de integración y el endpoint
exacto, y responde con código funcional y enlaces a las páginas fuente.
## Ejecutar llamadas en sandbox (opcional) [#ejecutar-llamadas-en-sandbox-opcional]
Es el mismo MCP alojado — agregas cabeceras de credenciales al conectar y tu
asistente obtiene las herramientas de ese producto además de las de
documentación. Las credenciales viajan en la conexión, nunca dentro de una
pregunta, así se mantienen fuera de tu chat y de los logs; Mono las usa para
firmar cada llamada y luego las descarta.
### Bre-B [#bre-b]
En **Claude Code**, agrega las dos cabeceras de credenciales:
```bash
claude mcp add --transport http mono-docs https://docs.cert.monoban.co/api/mcp \
--header "X-Mono-Client-Id: " \
--header "X-Mono-Client-Secret: "
```
Luego guíalo en lenguaje natural. Empieza en modo lectura para confirmar la conexión:
> Lista mis cuentas tenant de Bre-B.
Después recorre el flujo completo:
> Crea un recaudo de Bre-B por COP 50.000, simula un pago y confirma que quedó pagado.
### Core [#core]
Agrega la cabecera con tu token de sandbox de Core:
```bash
claude mcp add --transport http mono-docs https://docs.cert.monoban.co/api/mcp \
--header "X-Mono-Core-Token: "
```
Luego, de nuevo empezando en modo lectura:
> Lista mis cuentas de ledger de Core.
Y el flujo completo:
> Crea un titular, emite una tarjeta contra una cuenta de ledger con un control
> de gasto y luego muéstrame la tarjeta.
### Ambos a la vez [#ambos-a-la-vez]
Para usar las herramientas de Bre-B y Core en la misma conexión, agrega las tres
cabeceras:
```bash
claude mcp add --transport http mono-docs https://docs.cert.monoban.co/api/mcp \
--header "X-Mono-Client-Id: " \
--header "X-Mono-Client-Secret: " \
--header "X-Mono-Core-Token: "
```
Si ya te conectaste solo para documentación, vuelve a agregarlo con estas
cabeceras para habilitar las herramientas de sandbox.
**Solo sandbox.** Las llamadas en vivo solo pueden llegar al sandbox de Mono — nunca a
producción, nunca dinero real. Las credenciales se usan por llamada y nunca se almacenan, y
cualquier dato sensible de tarjeta que Core devuelva en el sandbox es información de prueba
sintética, no una tarjeta real.
## Siguientes pasos [#siguientes-pasos]
* [Recaudos de Bre-B](/docs/guides/breb-participant/use-cases/collections) — recibe tu primer pago.
* [Dispersiones de Bre-B](/docs/guides/breb-participant/use-cases/outgoing-transfers) — envía un pago.
* [Emisión de tarjetas (Core)](/docs/guides/core/flows/issuing-cards) — emite y administra tarjetas.
* [Soporte técnico](/docs/guides/technical-support) — contacta al equipo de Mono.
# Soporte técnico (/es/docs/guides/technical-support)
El soporte de Mono está dimensionado al tipo de pregunta que tengas. Las conversaciones pre-venta, las preguntas de integración durante el build y los incidentes de producción van por canales diferentes. Esta página te dice cuál usar.
**¿Incidente de producción?** Si el dinero se está moviendo mal o tus clientes están
bloqueados, escala de inmediato a tu contacto del equipo de cuenta y envía un correo a
**[tech-support@mono.la](mailto:tech-support@mono.la)** con **`[INCIDENT]`** en el asunto. No esperes a horario laboral.
## Canales según el tipo de necesidad [#canales-según-el-tipo-de-necesidad]
| Necesidad | Canal | Mejor para |
| :------------------------ | :------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- |
| Pre-venta / scoping | Tu contacto del equipo de cuenta | Evaluación de ajuste, precios, preguntas de capacidad, scoping del contrato. |
| Soporte de integración | [tech-support@mono.la](mailto:tech-support@mono.la) | Preguntas durante el build, diagnóstico de errores, aclaraciones de comportamiento de la API. |
| Incidente de producción | [tech-support@mono.la](mailto:tech-support@mono.la) con `[INCIDENT]` en el asunto + tu contacto del equipo de cuenta | Dinero moviéndose incorrectamente, integración caída, impacto a clientes. |
| Problema de documentación | Abrir un issue en el repo de docs | Typo, link roto, información faltante. |
| Solicitud de feature | Tu contacto del equipo de cuenta | Nuevo rail, nuevo endpoint, nuevo comportamiento. |
## Cómo escalar un incidente de producción [#cómo-escalar-un-incidente-de-producción]
Si tu integración está fallando de una manera que afecta clientes reales o mueve dinero incorrectamente:
1. **Detén el sangrado.** Pausa la operación afectada en tu aplicación si puedes.
2. **Captura evidencia.** Request IDs de Mono, timestamps, payloads, IDs de muestra de clientes/transacciones afectados.
3. **Envía un correo a [tech-support@mono.la](mailto:tech-support@mono.la)** con `[INCIDENT]` en el asunto. Incluye la evidencia.
4. **Notifica a tu contacto del equipo de cuenta** en paralelo — por teléfono, mensaje o correo.
5. **Mantente disponible.** Deja el mismo hilo abierto hasta que el incidente esté resuelto y se hayan fijado las expectativas del post-mortem.
## Siguientes pasos [#siguientes-pasos]
* [Estándares de API: Errores y reintentos](/docs/guides/api-standards/errors-and-retries) — envelope de errores, códigos de estado y política de reintentos.
* [Buenas prácticas de Banking](/docs/guides/banking/best-practices) — patrones de producción para evitar incidentes comunes.
* [Buenas prácticas de Core](/docs/guides/core/best-practices) — lo mismo, para Core.
# Empezar (/es/docs/guides)
Mono es infraestructura bancaria para América Latina. Conecta tu empresa con la red bancaria colombiana, incluyendo Bre-B, el esquema de pagos instantáneos, sin convertirte en banco. Mueve dinero dentro y fuera, mantén saldos, emite tarjetas y rastrea cada centavo.
Estas guías están escritas para todos los que trabajan con Mono, no solo para ingenieros. Si eres product manager evaluándonos, líder de finanzas resolviendo la conciliación, agente de soporte buscando un código de error, o developer conectando un webhook: todo está aquí.
## Qué puedes lograr con Mono [#qué-puedes-lograr-con-mono]
* **Recaudos** — Acepta pagos desde cualquier cuenta bancaria colombiana vía PSE.
* **Dispersiones** — Envía dinero a cuentas colombianas por ACH, Transfiya o Mono Turbo.
* **Tarjetas virtuales y físicas** — Emite tarjetas con saldo programable y controles de gasto en tiempo real.
* **Cuentas de ledger** — Mantén saldos para tus usuarios en un ledger de partida doble, totalmente auditable.
* **Integraciones con Bre-B** — Recibe y envía pagos interbancarios instantáneos vía Bre-B.
## Elige tu camino [#elige-tu-camino]
Cada sección abajo responde una pregunta distinta. Empieza donde esté tu pregunta.
Mono en una página — qué hacemos, a quién servimos y cómo operamos.
Casos de uso, criterios de ajuste y qué puedes construir que antes no podías.
Canales, tiempos de respuesta y cómo escalar un incidente de producción.
Autenticación, idempotencia, errores, formatos, paginación — convenciones que aplican a
través de los productos.
## Servicios [#servicios]
Mono está organizado en tres servicios. Cada uno tiene su propio árbol de guías.
Cuentas bancarias, transferencias (ACH, Transfiya, Mono Turbo), recaudos PSE y emisión de
tarjetas.
Ledger programable, emisión de tarjetas, controles de gasto, dispersiones.
Envía y recibe pagos instantáneos Bre-B a través de la API de Mono.
## Si quieres hacer X, lee Y [#si-quieres-hacer-x-lee-y]
| Quieres... | Empieza aquí |
| :---------------------------------------------------- | :-------------------------------------------------------------------------------------- |
| Recaudar un pago vía PSE en tu checkout de e-commerce | [Banking → Recaudo PSE](/docs/guides/banking/flows/pse-collection) |
| Enviar dispersiones a cuentas bancarias colombianas | [Banking → Envío de transferencias](/docs/guides/banking/flows/sending-transfers) |
| Construir una billetera con saldos y tarjetas | [Core → Ejemplo de integración](/docs/guides/core/integration-example) |
| Mantener saldos en un ledger programable | [Core → Ledger](/docs/guides/core/concepts/ledger) |
| Emitir tarjetas virtuales o físicas | [Banking → Tarjetas](/docs/guides/banking/concepts/cards) |
| Recibir pagos Bre-B vía QR | [Bre-B → Recaudo QR](/docs/guides/breb-participant/flows/qr-collection) |
| Enviar una transferencia Bre-B | [Bre-B → Transferencia saliente](/docs/guides/breb-participant/flows/outgoing-transfer) |
| Entender autenticación, errores, idempotencia | [Estándares de API](/docs/guides/api-standards/authentication) |
## Inicio rápido [#inicio-rápido]
De cero a tu primera llamada:
1. **Obtén credenciales.** Inicia sesión en el [Dashboard de Mono](https://mi.cuentamono.com) y genera una API key de sandbox para el servicio que vas a integrar.
2. **Lee la página de autenticación correspondiente.** [Banking](/docs/guides/banking/api/authentication) y [Core](/docs/guides/core/authentication) usan API keys; [Bre-B](/docs/guides/breb-participant/authentication) usa OAuth.
3. **Haz una llamada de prueba.** Usa el snippet de curl en la página de autenticación contra la URL base de sandbox.
4. **Conecta un webhook.** Cada servicio emite webhooks para cambios de estado — registra tu endpoint y verifica el primer evento.
5. **Ejecuta un flujo de punta a punta.** Elige el flujo que coincide con tu caso de uso en la tabla de arriba y síguelo a través de sandbox.
## Cobertura [#cobertura]
Hoy Mono opera en **Colombia.** Nos estamos expandiendo regionalmente; contacta a tu equipo de cuenta si estás evaluando Mono para un país que aún no hemos anunciado.
# Por qué usar Mono (/es/docs/guides/why-mono)
La respuesta a "por qué usar Mono" depende de lo que estés necesitando. Esta página presenta los casos de uso que servimos bien, los productos que compones para servirlos y los beneficios concretos. Léela antes de dimensionar una integración — te ahorrará tiempo de discovery después.
## Casos de uso principales [#casos-de-uso-principales]
| Caso de uso | Qué construyes | Productos de Mono que usas |
| :---------------------------------------- | :---------------------------------------------------------------- | :------------------------------------------------------ |
| Billetera de consumo | Cuenta, recargas, gasto con tarjeta, transferencias P2P | Banking (Tarjetas + PSE) + Core (Ledger) |
| Dispersiones a vendedores de marketplace | Saldos por vendedor, dispersiones programadas a cuentas bancarias | Banking (Transferencias) + Core (Ledger) |
| Checkout de e-commerce | Recaudo PSE, conciliación de settlement | Banking (Enlaces de recaudo) |
| Tarjetas corporativas / gestión de gastos | Tarjetas por empleado con reglas de categoría y límite | Banking (Tarjetas) + Core (Controles de gasto + Ledger) |
| Bre-B para un banco o SEDPE | Conexión programable a Bre-B para enviar y recibir | Bre-B Participant |
| Automatización de tesorería | Transferencias programáticas entre cuentas y rails | Banking (Transferencias) + Core (Ledger) |
| Desembolso de créditos | Empuja fondos aprobados a las cuentas bancarias de los deudores | Banking (Transferencias) |
| Nómina | Dispersiones masivas a cuentas bancarias de empleados | Banking (Transferencias en lote) |
## Beneficios concretos [#beneficios-concretos]
Lo que nuestros clientes ganan de forma medible al elegir Mono:
* **Tiempo de integración en semanas, no en trimestres.** Una API, un modelo de auth, un modelo de errores — a través de Banking, Core y Bre-B.
* **Settlement en tiempo real donde el rail lo permite.** Mono Turbo y Bre-B liquidan en segundos; ACH en el ciclo estándar del mismo día.
* **Bre-B nativo.** Como participante de Bre-B, Mono ofrece control total sobre las llaves de pago y settlement directo en vez de un intermediario.
* **Conciliación incluida.** El ledger de Core es de partida doble y append-only; la conciliación diaria es una consulta, no un trabajo a la medida.
* **Fallback multi-rail.** Una transferencia por Mono Turbo que no llega al banco destino reintenta automáticamente por ACH — tu código no tiene que hacerlo.
* **Observabilidad de producción.** Cada llamada a la API lleva un request ID; cada cambio de estado emite un webhook firmado.
## Cuándo Mono es la opción correcta [#cuándo-mono-es-la-opción-correcta]
Vas a sacarle el mayor provecho a Mono si:
* Operas en Colombia (hoy) o estás planeando un lanzamiento en Colombia.
* Necesitas al menos uno de: on/off-ramp a cuentas bancarias, Bre-B, saldos programables, emisión de tarjetas.
* Tienes capacidad de ingeniería para integrar una API y operar el producto resultante.
* Te importa la conciliación y los rastros de auditoría — no solo mover dinero una vez, sino rastrearlo para siempre.
## Siguientes pasos [#siguientes-pasos]
* [Banking](/docs/guides/banking) — recaudos y dispersiones.
* [Core](/docs/guides/core) — ledger, tarjetas, dispersiones.
* [Bre-B Participant](/docs/guides/breb-participant) — pagos interbancarios instantáneos.
* [Soporte técnico](/docs/guides/technical-support) — habla con nosotros.
# Getting started (/es/docs/api-reference)
Welcome to the Mono API Reference. This documentation provides comprehensive information about our APIs and services.
## Available services [#available-services]
Create and manage outgoing transfers and collections through the Bre-B system with real-time
webhook notifications
Essential financial infrastructure including ledger management, payouts, cards, and spending
controls
Banking operations including accounts, transfers, collection links, and banking cards
## Need Help? [#need-help]
If you have questions or need assistance integrating with our API, please contact our [support team](/docs/guides/technical-support).
# Ejemplo de integración (/es/docs/guides/banking/integration-example)
Esta página recorre una integración representativa de Banking de punta a punta: un **recaudador PSE para e-commerce**. Verás el camino completo desde que un cliente hace clic en "pagar" en tu checkout hasta que tu sistema marca la orden como pagada y despacha la mercancía.
El recaudo PSE es el caso de uso más común de Banking. El mismo patrón se transfiere a settlement de facturas, recargas de suscripción y cualquier pago iniciado por el pagador.
Esta página es un esqueleto de ejemplo de integración. La versión completa incluirá código por
paso (curl, Node.js, Python), un diagrama de secuencia, manejo de errores y patrones de
conciliación.
## Qué vas a construir [#qué-vas-a-construir]
Un checkout de e-commerce que:
* Genera un enlace de recaudo PSE cuando el cliente elige "pagar con cuenta bancaria".
* Redirige al cliente a PSE para la selección de banco y autorización.
* Recibe un webhook cuando el banco confirma el pago.
* Marca la orden como pagada y dispara el fulfillment.
* Maneja los casos de falla — abandonado, expirado, rechazado.
## Arquitectura [#arquitectura]
| Capa | Componente |
| :------- | :--------------------------------------------------------------------------------- |
| Frontend | UI del checkout, redirección a PSE, manejo de return URL |
| Backend | Creación de enlace de recaudo, receptor de webhook, máquina de estados de la orden |
| Mono | API de Banking + webhooks de enlace de recaudo |
| Externo | Red PSE + banco del cliente |
## Recorrido [#recorrido]
El recorrido completo está pendiente. Hasta que se publique, ver:
* [Enlaces de recaudo](/docs/guides/banking/concepts/collection-links)
* [Flujo de recaudo PSE](/docs/guides/banking/flows/pse-collection)
* [Webhooks de enlaces de recaudo](/docs/guides/banking/webhooks/collection-links)
## Siguientes pasos [#siguientes-pasos]
* [Flujo de recaudo PSE](/docs/guides/banking/flows/pse-collection)
* [Sandbox](/docs/guides/banking/sandbox)
* [Buenas prácticas](/docs/guides/banking/best-practices)
# Glosario (/es/docs/guides/banking/glossary)
Una referencia alfabética corta de los términos que vas a encontrar mientras integras Banking. Cada entrada es una definición de una o dos frases en lenguaje simple. Para el modelo de datos completo detrás de cualquiera de estos, sigue el enlace a la página de concepto correspondiente.
Este glosario es un punto de partida y crecerá. Si te falta un término, por favor abre un
issue en el repo de docs para que lo agreguemos.
## ACH [#ach]
El rail estándar de mismo día usado en Colombia para transferencias banco a banco. Las transferencias ACH no son en tiempo real; el settlement ocurre en ciclos durante horas bancarias.
## Clave de idempotencia [#clave-de-idempotencia]
Una etiqueta única que tu sistema adjunta a una solicitud de escritura para que Banking trate un reintento como la misma operación. Ver [Claves de idempotencia](/docs/guides/api-standards/idempotency-keys).
## Cuenta bancaria [#cuenta-bancaria]
Una cuenta de fondeo en un banco colombiano, identificada por un número de cuenta y un código de banco. Ver [Cuentas bancarias](/docs/guides/banking/concepts/accounts).
## Enlace de recaudo [#enlace-de-recaudo]
Un enlace de pago que tu negocio genera y envía a un cliente para recibir un pago PSE. Ver [Enlaces de recaudo](/docs/guides/banking/concepts/collection-links).
## Mono Turbo [#mono-turbo]
El rail de dispersión en tiempo real de Mono para transferencias a bancos colombianos participantes. Cae a ACH cuando el destino no soporta settlement en tiempo real.
## Producción [#producción]
El entorno en vivo donde Banking mueve dinero real. Las credenciales de producción y las de sandbox no son intercambiables. Ver [Autenticación](/docs/guides/api-standards/authentication).
## PSE (Pagos Seguros en Línea) [#pse-pagos-seguros-en-línea]
El sistema estándar en Colombia para pagar desde una cuenta bancaria en un sitio web. El equivalente local a Pix de Brasil o ACH Direct Debit de Estados Unidos. Ver [Enlaces de recaudo](/docs/guides/banking/concepts/collection-links).
## Routing [#routing]
La elección de qué rail (ACH, Transfiya, Mono Turbo) usa Mono para despachar una transferencia. Puedes solicitar un routing específico o dejar que Mono decida.
## Sandbox [#sandbox]
El entorno de prueba donde las integraciones de Banking pueden ejercitar la API completa sin mover dinero real. Las credenciales de sandbox solo funcionan contra las URLs base de sandbox.
## Settlement [#settlement]
El momento en que una transferencia o recaudo pasa de "aceptado" a un estado final — dinero acreditado en la cuenta destino. Mono confirma el settlement por webhook.
## Tarjeta [#tarjeta]
Un instrumento de pago emitido por Mono que gasta contra una cuenta Mono. Las tarjetas pueden ser virtuales o físicas y pueden ser débito (vinculadas al saldo de la cuenta) o prepago (con su propio saldo cargado). Ver [Tarjetas](/docs/guides/banking/concepts/cards).
## Tarjeta física [#tarjeta-física]
Una tarjeta plástica emitida por Mono. Se usa para pagos en tiendas físicas, retiros en cajeros y compras en línea. Requiere activación antes de usarla. Ver [Tarjetas](/docs/guides/banking/concepts/cards).
## Tarjeta virtual [#tarjeta-virtual]
Una tarjeta emitida por Mono que existe solo digitalmente. Disponible inmediatamente y usada para compras en línea o suscripciones digitales. Ver [Tarjetas](/docs/guides/banking/concepts/cards).
## Transferencia bancaria [#transferencia-bancaria]
Una dispersión desde tu cuenta Mono hacia la cuenta bancaria de un beneficiario, despachada por uno de los rails de Banking (ACH, Transfiya o Mono Turbo). Ver [Transferencias bancarias](/docs/guides/banking/concepts/transfers).
## Transfiya [#transfiya]
Un rail interbancario en tiempo real en Colombia para transferencias dentro de un clúster específico de bancos.
## Webhook [#webhook]
Una notificación HTTP automática que Mono envía a tu endpoint cuando ocurre un evento de Banking (transferencia settled, recaudo pagado, tarjeta creada, etc.). Ver [Webhooks](/docs/guides/banking/webhooks).
# Buenas prácticas (/es/docs/guides/banking/best-practices)
Esta página reúne las prácticas operativas que separan una integración de Banking que funciona en sandbox de una que sobrevive en producción. Son la forma de evitar transferencias duplicadas, recuperarte cuando un rail tiene timeout, y conciliar los libros al final de cada día.
Si estás desplegando Banking para mover dinero real, trata esta página como un checklist de lanzamiento y como referencia durante incidentes.
Esta página es un esqueleto de buenas prácticas. La versión completa incluirá ejemplos
concretos, antipatrones a evitar y runbooks para los modos de falla más comunes en producción.
## Idempotencia en cada transferencia [#idempotencia-en-cada-transferencia]
Cada solicitud de creación de transferencia debe incluir una [clave de idempotencia](/docs/guides/api-standards/idempotency-keys). Sin ella, un timeout de red duplicará silenciosamente el envío.
## Trata las respuestas síncronas como "aceptadas", no como "settled" [#trata-las-respuestas-síncronas-como-aceptadas-no-como-settled]
Un `200` al crear una transferencia bancaria significa que Mono aceptó la solicitud — no que el rail confirmó el crédito. Espera el webhook antes de marcar la operación como settled en tus libros.
## Planifica los fallbacks [#planifica-los-fallbacks]
Mono Turbo puede caer a ACH automáticamente. Tu conciliación debe manejar una transferencia solicitada como Mono Turbo que terminó settled como ACH — las tarifas y los tiempos a nivel de rail difieren. Lee el campo del rail en el webhook de confirmación.
## Concilia diariamente [#concilia-diariamente]
Corre una conciliación diaria que compare tu vista de cada transferencia (y su estado terminal) contra la de Mono. Saca a la luz cualquier discrepancia el mismo día que aparezca, no a la semana siguiente.
## Verifica las firmas de los webhooks [#verifica-las-firmas-de-los-webhooks]
Cada webhook de Banking viene firmado. Verifica la firma antes de confiar en el payload — revisa la documentación de webhooks.
## Rota las API keys [#rota-las-api-keys]
Rota las API keys al menos cada 90 días e inmediatamente después de cualquier exposición sospechada. Las claves no expiran por sí solas.
## Antipatrones a evitar [#antipatrones-a-evitar]
* Enviar una clave de idempotencia nueva en cada reintento.
* Tratar la creación de un enlace de recaudo PSE como confirmación de que el cliente pagó.
* Hardcodear elecciones de rail que bloquean el fallback.
* Loguear API keys, payloads firmados o PII.
## Siguientes pasos [#siguientes-pasos]
* [Claves de idempotencia](/docs/guides/api-standards/idempotency-keys)
* [Lista de verificación de producción](/docs/guides/banking/production)
* [Webhooks](/docs/guides/banking/webhooks)
# Pasar a producción (/es/docs/guides/banking/production)
Para migrar a producción la integración hecha en el entorno de sandbox, debes cumplir las pruebas establecidas por Mono dependiendo del producto que estés integrando. Sin embargo, hay algunas especificaciones que todos los clientes de Mono deben cumplir:
## Requisitos generales [#requisitos-generales]
### Configuración de respuestas de Mono [#configuración-de-respuestas-de-mono]
Necesitarás configurar todas las respuestas posibles que entrega Mono cada vez que haces una solicitud.
### Configuración de webhooks [#configuración-de-webhooks]
Todos los eventos de respuesta que envía Mono deben estar configurados. Con esto puedes tener control inmediato cada vez que hay un cambio en tu cuenta Mono.
***
## Enlaces de recaudo [#enlaces-de-recaudo]
Antes de mover tu integración de enlaces de recaudo a producción, vas a revisar con nuestro equipo de integración que las siguientes solicitudes estén configuradas de la mejor manera:
* Crear exitosamente un enlace de recaudo
* Listar todos los enlaces de recaudo en tu cuenta Mono
* Obtener un enlace de recaudo por su ID
***
## Transferencias bancarias [#transferencias-bancarias]
Antes de mover tu integración de transferencias bancarias a producción, vas a revisar con nuestro equipo de integración que las siguientes solicitudes estén configuradas de la mejor manera:
* Listar los bancos que usarás para las transferencias bancarias
* Obtener las transferencias bancarias hechas en tu cuenta
* Crear exitosamente una transferencia bancaria
***
## Ledger y tarjetas [#ledger-y-tarjetas]
Antes de mover tu integración de ledger a producción, vas a revisar con nuestro equipo de integración que las siguientes solicitudes estén configuradas de la mejor manera:
* Crear exitosamente un account holder
* Actualizar exitosamente el estado de un account holder
* Crear exitosamente una cuenta de ledger
* Actualizar exitosamente el estado de una cuenta de ledger
* Crear exitosamente una tarjeta vinculada a la cuenta de ledger
* Actualizar exitosamente una tarjeta
* Obtener el saldo por cuenta de ledger
* Obtener las operaciones que has hecho sobre el saldo de una tarjeta
* Agregar dinero a una cuenta de ledger
* Retirar dinero de una cuenta de ledger
* Obtener los movimientos por cuenta de ledger
* Obtener la información de configuración para tu cuenta de tenant
# Banking (/es/docs/guides/banking)
Banking te permite recibir dinero desde la cuenta bancaria de un pagador a través de PSE, enviar dinero a cuentas bancarias colombianas vía ACH, Transfiya o Mono Turbo, y emitir tarjetas virtuales y físicas que gastan contra el saldo de una cuenta Mono. Es la parte de Mono que conecta tu producto con la red bancaria colombiana. Explora las secciones de abajo para aprender los conceptos clave, integrar la API, manejar eventos de webhook y probar en sandbox antes de pasar a producción.
Aprende sobre cuentas, transferencias, enlaces de recaudo y tarjetas
Guías de autenticación e integración con la API
Configura y maneja eventos de webhook
Prueba tu integración en el entorno de sandbox
Requisitos y pruebas antes de migrar a producción
# Vista general de arquitectura (/es/docs/guides/banking/architecture)
# Dashboard (/es/docs/guides/banking/dashboard)
El Mono Dashboard es la interfaz web donde los roles que no son de desarrollo — operaciones, finanzas, soporte al cliente, cumplimiento — interactúan con Banking. Todo lo que expone la API se puede inspeccionar desde el Dashboard, y muchas acciones (crear una transferencia puntual, generar un enlace de recaudo, descargar un reporte de conciliación) se pueden ejecutar sin escribir código.
Esta página describe qué hace cada sección del Dashboard relacionada con Banking y qué rol la usa típicamente. Es el punto de partida correcto para alguien que se une al equipo y necesita operar Banking pero no escribe la integración.
Esta página es un esqueleto del dashboard. La versión completa incluirá capturas de pantalla
de cada sección, notas de acceso por rol y ejemplos de punta a punta.
## Secciones [#secciones]
| Sección | Qué muestra | Quién la usa |
| :----------------------- | :--------------------------------------------------------------------- | :---------------------- |
| Cuentas bancarias | Cuentas de origen y sus saldos. | Finanzas, ops |
| Transferencias bancarias | Transferencias salientes y entrantes, estado, rail usado, reintentos. | Finanzas, ops |
| Enlaces de recaudo | Enlaces de recaudo PSE activos e históricos, estado de pago. | Ops, soporte al cliente |
| Webhooks | Configuración de endpoints, entregas recientes, intentos de reintento. | Ingeniería |
| API keys | Gestión de claves, roles, rotación. | Ingeniería, seguridad |
| Reportes | Reportes diarios de conciliación, exportes de transacciones. | Finanzas |
## Operaciones comunes [#operaciones-comunes]
Los recorridos específicos (emitir una transferencia manual, reintentar una transferencia fallida, regenerar un enlace de recaudo, exportar un reporte de conciliación) se documentarán aquí.
## Configuración de webhooks [#configuración-de-webhooks]
### Crear un webhook [#crear-un-webhook]
La creación de webhooks se hace por la interfaz de usuario:
1. Ve a settings.
2. Haz clic en Personal.
3. Haz clic en API.
4. Haz clic en nuevo webhook.
5. Inserta la URL HTTPS/HTTP que ya configuraste para escuchar los eventos.
6. Haz clic en guardar.
Si todo está funcionando como se espera, aparecerá una alerta indicando que creamos exitosamente tu webhook, y deberías poder ver la URL listada en la página.
Las URLs de webhook son únicas — solo puedes crear un webhook por URL. Para reutilizar una URL, primero elimina el webhook existente y crea uno nuevo. Se generará un secreto nuevo.
Verifica siempre que tu endpoint esté recibiendo los eventos correctamente.
### Ver detalles del webhook [#ver-detalles-del-webhook]
Haz clic en la flecha al final de la fila donde se muestra tu URL para abrir la vista de detalle. La vista de detalle contiene el secreto del webhook, que está oculto por defecto. Haz clic en el **ojo mostrar/ocultar** para revelarlo.
Mantén seguro el secreto de tu webhook. No lo compartas.
## Siguientes pasos [#siguientes-pasos]
* [Convenciones de webhooks](/docs/guides/api-standards/webhooks) — verificación de firmas, política de reintentos y campos de dinero.
* [Arquitectura](/docs/guides/banking/architecture)
* [Buenas prácticas](/docs/guides/banking/best-practices)
# Ejemplo de integración (/es/docs/guides/breb-participant/integration-example)
Esta página recorre de extremo a extremo una integración Bre-B representativa: un **banco o entidad licenciada que quiere permitir a sus clientes enviar y recibir pagos Bre-B desde dentro de la app bancaria**. Vas a ver el camino completo desde el intercambio de tokens hasta la generación de QR, la recepción de pagos, la resolución de target, la transferencia saliente y el manejo de webhooks.
Si estás construyendo otro caso de uso Bre-B — un adquirente de marketplace, una billetera que conecta a Bre-B, un sistema de tesorería — los patrones se transfieren directamente; solo cambian los nombres de las entidades.
Esta página es un esqueleto de ejemplo de integración. La versión completa incluirá código por
paso, diagramas de secuencia para los flujos de payin y payout, y una arquitectura de
referencia completa.
## Qué vas a construir [#qué-vas-a-construir]
Una integración Bre-B participant que soporta:
* **Onboarding** — registrar una cuenta tenant que mantendrá saldos y recibirá pagos Bre-B.
* **Recibir pagos** — generar códigos QR de Bre-B y conciliar eventos de recaudo.
* **Enviar pagos** — resolver una llave de pago destino, confirmar con el usuario y despachar una transferencia saliente.
* **Ciclo de vida** — rastrear los estados de la transferencia saliente y manejar errores.
## Arquitectura [#arquitectura]
| Capa | Componente |
| :------- | :-------------------------------------------------------------------------------- |
| Frontend | UI para el cliente final que muestra QR e inicia transferencias salientes |
| Backend | Refresh OAuth, generación de recaudo, resolución + despacho, receptor de webhooks |
| Mono | API de Bre-B Participant + webhooks |
| Externo | Directorio central de Bre-B + bancos contraparte |
## Recorrido [#recorrido]
El recorrido completo está pendiente. Hasta que se publique, consulta:
* [Flujo de transferencia saliente](/docs/guides/breb-participant/flows/outgoing-transfer)
* [Flujo de recaudo por QR](/docs/guides/breb-participant/flows/qr-collection)
* [Estados de la transferencia saliente](/docs/guides/breb-participant/architecture/outgoing-transfer-states)
* [Payloads de webhook](/docs/guides/breb-participant/webhook-payloads)
## Siguientes pasos [#siguientes-pasos]
* [Flujo de recaudo por QR](/docs/guides/breb-participant/flows/qr-collection)
* [Flujo de transferencia saliente](/docs/guides/breb-participant/flows/outgoing-transfer)
* [Buenas prácticas](/docs/guides/breb-participant/best-practices)
# Glosario (/es/docs/guides/breb-participant/glossary)
Una breve referencia alfabética de los términos que vas a encontrar al integrar Bre-B Participant. Cada entrada es una definición en lenguaje plano de una o dos frases. Para el modelo de datos completo de cualquiera de ellos, sigue el enlace a la página de concepto correspondiente.
Este glosario es un punto de partida y va a crecer. Si te falta un término, por favor abre un
issue en el repo de docs para que lo agreguemos.
## Bre-B [#bre-b]
Esquema interbancario de pagos instantáneos de Colombia — un rail en tiempo real, 24/7, similar a Pix en Brasil o UPI en India.
## Cuenta tenant [#cuenta-tenant]
Una cuenta lógica en Mono que mantiene saldos y es origen/destino de operaciones Bre-B.
## Directorio central [#directorio-central]
El registro operado por Bre-B que mapea llaves de pago a la identidad del titular y al banco destino. Cada consulta de resolución va contra el directorio central.
## EDE (Entidad de Depósito Electrónico) [#ede-entidad-de-depósito-electrónico]
Categoría regulatoria colombiana para entidades licenciadas que captan depósitos. Muchos participantes de Bre-B son EDEs.
## Firma de webhook [#firma-de-webhook]
Una firma HMAC-SHA256 adjunta a cada webhook de Bre-B para que puedas verificar que el payload viene de Mono. Ver [Firmas de webhook](/docs/guides/breb-participant/webhook-signatures).
## Llave de pago [#llave-de-pago]
Un alias que identifica un destino en Bre-B — un número de teléfono, documento, email, un identificador alfanumérico emitido por Bre-B o un código de comercio. Se usa tanto para enviar (tú especificas la llave destino) como para recibir (tu tenant tiene llaves registradas).
## Participante [#participante]
Un banco o entidad licenciada autorizada a operar en Bre-B. Mono ofrece acceso como participante de Bre-B a través de su API. En el objeto `target`, el participante receptor aparece como `participant_nit` y `participant_name`; el nombre es `null` cuando Mono aún no lo tiene.
## QR (Bre-B QR) [#qr-bre-b-qr]
Un código QR conforme a EMVCo que codifica una instrucción de pago Bre-B. El cliente lo escanea desde su app bancaria para autorizar una transferencia.
## Recaudo [#recaudo]
Un pago Bre-B entrante a una de tus cuentas tenant. Recibes recaudos mostrando un código QR de Bre-B o compartiendo una llave de pago.
## Resolución [#resolución]
El proceso de buscar una llave de pago contra el directorio central para obtener el nombre del titular y el banco antes de despachar una transferencia saliente.
## Target [#target]
El destino resuelto de una transferencia saliente. Devuelto por una resolución exitosa y requerido para despachar.
## Transferencia saliente [#transferencia-saliente]
Un pago Bre-B que tu tenant inicia contra la llave de pago de otro participante.
# Referencia de payload de webhook (/es/docs/guides/breb-participant/webhook-payloads)
Esta guía describe el envelope que Mono usa para cada entrega de webhook de Bre-B
participant y lista los eventos de transferencia saliente y de resolución de target que tu
endpoint puede esperar.
Antes de confiar en cualquier payload, verifica el header `Mono-Signature`. Ver [Verificación
de firma de webhook](/docs/guides/breb-participant/webhook-signatures).
## Estructura del payload [#estructura-del-payload]
Todos los webhooks comparten el mismo envelope. El string `event.type` te dice qué
evento se disparó e implícitamente qué campos esperar dentro de `event.data`.
```ts
interface WebhookPayload {
timestamp: string; // Hora de entrega en ISO 8601
event: {
type: string; // e.g., "outgoing_transfer.created", "outgoing_transfer.failed"
data: {
id: string;
state: string;
// ...los campos adicionales dependen de event.type
};
};
}
```
Para la forma exacta del objeto `data` de cada evento, sigue el enlace en la
tabla de eventos abajo — cada evento tiene un schema dedicado en la referencia de API.
## Eventos de transferencia saliente [#eventos-de-transferencia-saliente]
| Evento | Descripción |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `outgoing_transfer.created` | La transferencia fue creada. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_created) |
| `outgoing_transfer.processing` | La transferencia se está procesando. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_processing) |
| `outgoing_transfer.target_resolved` | El target (llave Bre-B) fue resuelto. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_target_resolved) |
| `outgoing_transfer.held` | Los fondos fueron retenidos en la cuenta origen. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_held) |
| `outgoing_transfer.sent_to_breb_provider` | La transferencia fue enviada al proveedor Bre-B. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_send_to_breb_provider) |
| `outgoing_transfer.successful` | La transferencia se completó exitosamente. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_successful) |
| `outgoing_transfer.failed` | La transferencia falló. Revisa `state_reason` para la causa. [Schema](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_failed) |
Cuando recibes `outgoing_transfer.failed`, el campo `state_reason` te dice
por qué — ver [Códigos de error de transferencia saliente](/docs/guides/breb-participant/outgoing-transfer-errors)
para la lista completa y el manejo sugerido por código.
## Eventos de resolución de target [#eventos-de-resolución-de-target]
Cada request de resolución emite estos eventos — ya sea que lo hayas creado con
[Resolver target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target) o que una transferencia por llave lo haya creado por ti.
`event.data` es el request de resolución: el `expected_creditor` que enviaste (o `null`) y, una
vez resuelto, el `target`.
| Evento | Descripción |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `target_resolution.created` | El request de resolución fue creado. [Schema](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_created) |
| `target_resolution.resolved` | La llave fue resuelta; `data.target` lleva el destino. [Schema](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_resolved) |
| `target_resolution.failed` | La llave no pudo resolverse. `state_reason` es `target_creditor_mismatch` cuando el titular no coincidió con tu `expected_creditor`; no se persistió ningún target. [Schema](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_failed) |
# Autenticación (/es/docs/guides/breb-participant/authentication)
La API de Bre-B Participant usa **OAuth 2.0 client credentials** para autenticación. A diferencia de Banking y Core, que también aceptan API keys estáticas, Bre-B requiere que intercambies un client ID y un secret por un **access token** de corta duración, y luego adjuntes ese token a cada llamada. El token expira; tu código lo refresca automáticamente antes de que lo haga.
Si ya leíste [api-standards/authentication](/docs/guides/api-standards/authentication), esta página solo agrega lo específico de Bre-B: el endpoint de intercambio de tokens, la vida útil del token y el procedimiento de rotación.
Esta página es un esqueleto de autenticación. La versión completa documentará el request y la
respuesta de intercambio de tokens, el manejo de errores y los patrones recomendados de
cliente.
## Antes de empezar [#antes-de-empezar]
Necesitas:
* Un acuerdo de Bre-B participant con Mono.
* Credenciales de cliente (client ID y client secret) emitidas por Mono para tu tenant.
* La URL base del participant para tu entorno (sandbox o producción).
## Intercambio de tokens [#intercambio-de-tokens]
Aquí se documentarán el request completo de intercambio de tokens, la respuesta y la estrategia de refresh. Consulta la [referencia de la API de autenticación de Bre-B Participant](/docs/api-reference/breb-participant/authentication) para el contrato del endpoint.
## Enviando el token [#enviando-el-token]
```
Authorization: Bearer
```
## Scopes [#scopes]
Un scope es un permiso individual asignado a un access token. Pide los scopes más acotados que necesite tu integración, separados por espacios en la solicitud de token. Cada recurso tiene dos scopes:
* El scope sin sufijo, como `collections`, autoriza todas las acciones sobre ese recurso.
* El scope `:readonly` autoriza solo lecturas.
| Scope | Autoriza |
| :---------------------------- | :------------------------------------------------- |
| `outgoing_transfers` | Todos los permisos sobre transferencias salientes |
| `outgoing_transfers:readonly` | Leer transferencias salientes |
| `target_resolutions` | Resolver llaves Bre-B y leer sus resoluciones |
| `target_resolutions:readonly` | Leer resoluciones de target |
| `target_discoveries` | Descubrir llaves Bre-B y leer el catálogo bancario |
| `target_discoveries:readonly` | Leer el catálogo bancario |
| `collections` | Todos los permisos sobre recaudos |
| `collections:readonly` | Leer recaudos |
| `tenant_accounts` | Todos los permisos sobre cuentas tenant |
| `tenant_accounts:readonly` | Leer cuentas tenant |
Una llave Bre-B es un alias, como un número de celular, que apunta a una cuenta bancaria.
### Scopes de descubrimiento de llaves [#scopes-de-descubrimiento-de-llaves]
Usa `target_discoveries` para encontrar la llave Bre-B de un beneficiario a partir de sus datos de identidad y de cuenta, cuando no conoces la llave exacta. Usa `target_resolutions` cuando ya conoces la llave y llamas a
`POST /api/v1/targets/resolve`, y `outgoing_transfers` cuando creas el pago.
Consulta el [flujo de descubrimiento de
llaves](/docs/guides/breb-participant/flows/target-discovery) para ver la
secuencia completa.
## Fallas [#fallas]
| Código | Significado |
| :----------------- | :------------------------------------------------- |
| `401 Unauthorized` | Token faltante, expirado o revocado — refréscalo. |
| `403 Forbidden` | Token válido pero sin el scope para este endpoint. |
## Rotando credenciales [#rotando-credenciales]
Los tokens expiran automáticamente; la rotación en el sentido operativo significa rotar el client secret usado para obtenerlos. Rota el client secret al menos cada 90 días e inmediatamente después de cualquier sospecha de exposición.
## Siguientes pasos [#siguientes-pasos]
* [Arquitectura](/docs/guides/breb-participant/architecture)
* [Claves de idempotencia](/docs/guides/api-standards/idempotency-keys)
* [Buenas prácticas](/docs/guides/breb-participant/best-practices)
# Buenas prácticas (/es/docs/guides/breb-participant/best-practices)
Esta página reúne las prácticas operativas que diferencian una integración de Bre-B que funciona en sandbox de una que sobrevive en producción. Bre-B es 24/7 y en tiempo real — no hay una ventana batch nocturna donde los errores pasen desapercibidos. Las recomendaciones siguientes son cómo te mantienes fuera de problemas.
Si vas a desplegar Bre-B Participant a producción, trata esta página como checklist de lanzamiento y como referencia durante incidentes.
Esta página es un esqueleto de buenas prácticas. La versión completa incluirá ejemplos
concretos, antipatrones y runbooks para los modos de falla más comunes en producción.
## Siempre confirma con el pagador el nombre resuelto [#siempre-confirma-con-el-pagador-el-nombre-resuelto]
Una llave de pago Bre-B es un alias. La resolución devuelve el nombre del titular y la institución receptora (`participant_name`) — muéstraselos al pagador y exige confirmación explícita antes de despachar. Esto es tanto un requisito de prevención de fraude como una expectativa regulatoria.
## Idempotencia en cada despacho [#idempotencia-en-cada-despacho]
Cada request de transferencia saliente debe incluir una [clave de idempotencia](/docs/guides/api-standards/idempotency-keys). Un reintento de red sin ella es una transferencia duplicada.
## Verifica las firmas de webhook [#verifica-las-firmas-de-webhook]
Cada webhook de Bre-B está firmado con HMAC-SHA256. Verifica la firma antes de confiar en el payload — ver [Firmas de webhook](/docs/guides/breb-participant/webhook-signatures).
## Trata las respuestas síncronas como "aceptado", no "settled" [#trata-las-respuestas-síncronas-como-aceptado-no-settled]
Un `200` de un despacho de transferencia saliente significa que Mono aceptó el request. El estado terminal llega por webhook. No acredites ni debites nada en tus libros hasta recibir el evento de settlement.
## Maneja la máquina de estados completa [#maneja-la-máquina-de-estados-completa]
Las transferencias salientes de Bre-B atraviesan múltiples estados — pending, sent, accepted, settled, rejected. Mapea cada estado en tu aplicación. Surface los rechazos al usuario usando el [catálogo de errores](/docs/guides/breb-participant/outgoing-transfer-errors).
## Concilia todos los días [#concilia-todos-los-días]
Corre una conciliación diaria contra Mono. La naturaleza 24/7 de Bre-B hace que los problemas operativos se acumulen rápido sin ella.
## Rota el client secret [#rota-el-client-secret]
Los tokens OAuth expiran automáticamente. Rota el client secret usado para emitirlos al menos cada 90 días e inmediatamente tras cualquier sospecha de exposición.
## Antipatrones a evitar [#antipatrones-a-evitar]
* Saltarse la confirmación de nombre del target resuelto.
* Despachar sin clave de idempotencia.
* Confiar en un payload de webhook sin firmar.
* Tratar la aceptación del despacho como settlement.
* Hardcodear URLs de webhook sin un camino de rotación.
## Siguientes pasos [#siguientes-pasos]
* [Firmas de webhook](/docs/guides/breb-participant/webhook-signatures)
* [Errores de transferencia saliente](/docs/guides/breb-participant/outgoing-transfer-errors)
* [Claves de idempotencia](/docs/guides/api-standards/idempotency-keys)
# Códigos de error de transferencia saliente (/es/docs/guides/breb-participant/outgoing-transfer-errors)
Los errores al crear y procesar transferencias salientes caen en dos categorías
según **cuándo** se exponen:
1. **Motivos de rechazo** — errores de validación devueltos de forma **síncrona** cuando
creas un batch. La transferencia rechazada nunca se persiste.
2. **Razones de estado** — errores que ocurren durante el procesamiento **asíncrono**.
Te enteras por webhooks `outgoing_transfer.failed`, donde el campo
`state_reason` lleva el código.
## Motivos de rechazo [#motivos-de-rechazo]
Estos errores se devuelven en el arreglo `rejected_transfers` de la respuesta de
[crear transferencias salientes](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers).
Las transferencias rechazadas **no se crean** en el sistema — no se disparará
ningún webhook por ellas, así que tienes que manejarlas desde la respuesta síncrona.
### Ejemplo de respuesta [#ejemplo-de-respuesta]
```json
{
"id": "batch_123e4567-e89b-12d3-a456-426614174000",
"state": "created",
"transfers": [],
"rejected_transfers": [
{
"external_id": "transfer_001",
"reason": "amount_exceeds_max_limit"
},
{
"external_id": "transfer_002",
"reason": "target_not_found"
}
]
}
```
### Referencia de códigos [#referencia-de-códigos]
| Código | Descripción | Causa | Solución sugerida |
| -------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `amount_not_provided` | Monto no provisto | El campo `amount` es obligatorio pero faltó o vino en `null`. | Incluye `amount` con un valor válido. |
| `amount_exceeds_max_limit` | El monto supera el límite máximo | El monto de la transferencia supera el máximo permitido por transacción. | Reduce el monto o divídelo en varias transferencias. |
| `target_amount_mismatch` | El monto no coincide con el target | Al usar un `target_id` existente con `transaction_amount` fijo (por ejemplo, un QR estático), el monto enviado debe coincidir exacto. | Envía el monto del target, o omite `amount` para usar el valor del target. |
| `target_already_exists` | Target ya utilizado | Una transferencia activa (no fallida) ya usa este `target_id`. Cada target solo puede consumirse una vez. | Usa otro target o espera a que la transferencia en vuelo llegue a un estado terminal. |
| `target_not_found` | Target no encontrado | El `target_id` provisto no existe o no pertenece al tenant actual. | Verifica que el `target_id` sea correcto y pertenezca a tu tenant. |
## Razones de estado [#razones-de-estado]
Estos errores ocurren durante el procesamiento asíncrono y se entregan por el
webhook [`outgoing_transfer.failed`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_failed),
con el código legible por máquina en `event.data.state_reason`.
### Ejemplo de payload de webhook [#ejemplo-de-payload-de-webhook]
```json
{
"timestamp": "2025-12-17T20:14:01.947520Z",
"event": {
"type": "outgoing_transfer.failed",
"data": {
"id": "bbot_031uOJ6qb0sVclNseQfItQ",
"state": "failed",
"state_reason": "target_creditor_mismatch",
"external_id": "21752385-357e-450f-94a3-d9a0984da291",
"amount": { "currency": "COP", "amount": 100 },
"expected_creditor": {
"document_type": "CC",
"document_number": "21482961"
},
"target": {
"creditor": {
"document_type": "CC",
"document_number": "123143455",
"full_name": "Juan Carlos Perez Gomez"
}
}
}
}
}
```
### Errores de resolución de llave [#errores-de-resolución-de-llave]
Se levantan al resolver la llave Bre-B del destinatario.
| Código | Descripción | Causa | Solución sugerida |
| -------------------- | ------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `key_not_found` | Llave no encontrada | La llave Bre-B no existe en el sistema de pagos instantáneos. | Verifica la llave. El destinatario debe tener una llave registrada. |
| `key_suspended` | Llave suspendida | La llave Bre-B del destinatario está temporalmente suspendida. | Pide al destinatario que regularice su llave con su entidad financiera. |
| `invalid_key_format` | Formato inválido | El formato de la llave no cumple las reglas de Bre-B. | Verifica el formato contra la [tabla de tipos de llave](/docs/guides/breb-participant/concepts/payment-keys#tipos-de-llave). |
### Errores de validación del destinatario [#errores-de-validación-del-destinatario]
Se levantan cuando los datos del destinatario son inválidos o no coinciden con el dueño de la llave.
| Código | Descripción | Causa | Solución sugerida |
| ---------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `target_creditor_mismatch` | Creditor no coincide | El `expected_creditor` (tipo + número de documento) no coincide con el verdadero dueño de la llave Bre-B. | Verifica los datos del destinatario antes de enviar. Esta validación previene transferencias erradas. |
| `creditor_account_not_found` | Cuenta del creditor no encontrada | La cuenta bancaria asociada a la llave ya no existe o fue cerrada. | El destinatario debe verificar la cuenta con su entidad financiera. |
| `invalid_creditor_account` | Cuenta del creditor inválida | La cuenta del destinatario no puede recibir transferencias (bloqueada, inactiva, etc.). | El destinatario debe regularizar su cuenta bancaria. |
Envía `expected_creditor` a [Resolver
target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target) y el mismatch
aparece antes: la llamada responde `400` con `target_creditor_mismatch`, no se persiste ningún
target y el webhook
[`target_resolution.failed`](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_failed)
lleva el mismo `state_reason`. Un `target_id` resuelto de esta forma ya pertenece a la persona
esperada.
### Errores de fondos [#errores-de-fondos]
Se levantan durante el hold de fondos.
| Código | Descripción | Causa | Solución sugerida |
| ------------------------------ | ---------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `insufficient_funds` | Fondos insuficientes | La cuenta origen no tiene saldo suficiente para cubrir la transferencia. | Asegúrate de tener saldo disponible antes de crear la transferencia. |
| `amount_exceeds_balance_limit` | Monto supera límite de saldo | El monto llevaría al destinatario por encima del límite de saldo permitido. | Reduce el monto de la transferencia. |
### Errores del proveedor [#errores-del-proveedor]
Se levantan por problemas del proveedor Bre-B o de sistemas aguas arriba.
| Código | Descripción | Causa | Solución sugerida |
| ---------------------- | --------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `risk_control` | Rechazado por risk control | La transferencia fue rechazada por el sistema de risk control del proveedor. | Revisa los patrones de transacción. Puede requerir contactar soporte. |
| `breb_timeout` | Timeout del proveedor Bre-B | El proveedor Bre-B no respondió a tiempo. | Reintenta más tarde. Si persiste, contacta soporte. |
| `provider_unavailable` | Proveedor no disponible | El proveedor Bre-B está temporalmente fuera de servicio. | Reintenta más tarde. |
| `unknown` | Error desconocido | Ocurrió un error no categorizado. | Contacta a soporte técnico con el `id` de la transferencia. |
## Recomendaciones de integración [#recomendaciones-de-integración]
### 1. Valida antes de enviar [#1-valida-antes-de-enviar]
Antes de llamar al endpoint de creación de transferencias, valida:
* Que el monto esté dentro de los límites permitidos.
* Que los datos del destinatario sean correctos (si usas `expected_creditor`).
* Que la cuenta origen tenga saldo suficiente.
### 2. Maneja `rejected_transfers` desde la respuesta del batch [#2-maneja-rejected_transfers-desde-la-respuesta-del-batch]
```ts
const response = await createBatch(batchData);
if (response.rejected_transfers.length > 0) {
for (const rejected of response.rejected_transfers) {
console.error(`Transfer ${rejected.external_id} rejected: ${rejected.reason}`);
// Notifica al usuario o márcalo para revisión
}
}
```
### 3. Maneja los webhooks `outgoing_transfer.failed` [#3-maneja-los-webhooks-outgoing_transferfailed]
```ts
app.post('/webhook', (req, res) => {
const { event } = req.body;
if (event.type === 'outgoing_transfer.failed') {
const { id, external_id, state_reason } = event.data;
switch (state_reason) {
case 'insufficient_funds':
// Notifica al usuario que faltan fondos
break;
case 'target_creditor_mismatch':
// Pide al usuario reverificar los datos del destinatario
break;
case 'key_not_found':
// Indica que la llave es inválida
break;
default:
// Manejo por defecto
}
}
res.status(200).send();
});
```
### 4. Guía de reintentos [#4-guía-de-reintentos]
| Error | ¿Reintentable? | Recomendación |
| -------------------------- | -------------- | -------------------------------------------------- |
| `breb_timeout` | Sí | Reintenta después de 1–5 minutos. |
| `provider_unavailable` | Sí | Reintenta después de 5–15 minutos. |
| `insufficient_funds` | Sí\* | Reintenta cuando haya fondos disponibles. |
| `key_not_found` | No | Confirma la llave del destinatario con el usuario. |
| `target_creditor_mismatch` | No | Corrige los datos del destinatario. |
| `risk_control` | No | Contacta a soporte. |
Al contactar soporte sobre una transferencia fallida, provee el `id` de la transferencia (y el
id del `batch` si aplica), el `external_id` para correlación, y una descripción del escenario
y la frecuencia.
# Verificación de firma de webhook (/es/docs/guides/breb-participant/webhook-signatures)
Cada webhook entregado por Mono está firmado para que puedas confirmar que viene de
nosotros y que no fue alterado en tránsito. Esta guía recorre el algoritmo de
firma, una implementación de referencia en TypeScript y cómo conectarlo en
Express.js.
## Algoritmo de firma [#algoritmo-de-firma]
Los webhooks se firman con **HMAC-SHA256**. La firma se calcula sobre la
concatenación del timestamp de entrega y el body crudo del request:
```
signed_payload = "."
signature = HMAC-SHA256(secret, signed_payload)
```
### Header de firma [#header-de-firma]
El header `Mono-Signature` lleva tanto el timestamp como la firma:
```
Mono-Signature: t=,v1=
```
Ejemplo:
```
Mono-Signature: t=1766002441,v1=62afda2079925823b390e1199060d793aa50d64ec9d7bf184f5b7e96c8bf411c
```
## Implementación de referencia (TypeScript) [#implementación-de-referencia-typescript]
```ts
import { createHmac, timingSafeEqual } from 'crypto';
interface WebhookVerificationResult {
valid: boolean;
error?: string;
}
function parseSignatureHeader(header: string): {
timestamp: string | null;
signatures: string[];
} {
const parts = header.split(',');
let timestamp: string | null = null;
const signatures: string[] = [];
for (const part of parts) {
const [key, value] = part.split('=');
if (key === 't') timestamp = value;
else if (key === 'v1') signatures.push(value);
}
return { timestamp, signatures };
}
function computeSignature(timestamp: string, payload: string, secret: string): string {
const signedPayload = `${timestamp}.${payload}`;
return createHmac('sha256', secret).update(signedPayload).digest('hex');
}
/**
* @param signatureHeader El valor del header "Mono-Signature"
* @param rawBody El body crudo del request (deben ser los bytes exactos recibidos)
* @param secret El secret de firma del webhook (e.g., "whsec_xxx")
* @param toleranceSeconds Edad máxima del webhook en segundos (default: 300)
*/
export function verifyWebhookSignature(
signatureHeader: string,
rawBody: string,
secret: string,
toleranceSeconds: number = 300
): WebhookVerificationResult {
const { timestamp, signatures } = parseSignatureHeader(signatureHeader);
if (!timestamp) return { valid: false, error: 'Missing timestamp in signature header' };
if (signatures.length === 0) return { valid: false, error: 'Missing signature in header' };
const timestampNum = parseInt(timestamp, 10);
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestampNum) > toleranceSeconds) {
return { valid: false, error: 'Timestamp outside tolerance window' };
}
const expectedSignature = computeSignature(timestamp, rawBody, secret);
const expectedBuffer = Buffer.from(expectedSignature, 'hex');
for (const signature of signatures) {
const signatureBuffer = Buffer.from(signature, 'hex');
if (
expectedBuffer.length === signatureBuffer.length &&
timingSafeEqual(expectedBuffer, signatureBuffer)
) {
return { valid: true };
}
}
return { valid: false, error: 'Signature mismatch' };
}
```
## Usándolo en Express.js [#usándolo-en-expressjs]
Tienes que verificar contra los **bytes exactos** del request entrante. Si Express parsea y
re-serializa el JSON, el string resultante va a diferir (orden de keys, espacios) y la firma
ya no coincidirá. Usa `express.raw()` específicamente para la ruta del webhook.
```ts
import express from 'express';
import { verifyWebhookSignature } from './webhook_verification';
const app = express();
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString();
const signatureHeader = req.headers['mono-signature'] as string;
const secret = process.env.MONO_WEBHOOK_SECRET!;
const result = verifyWebhookSignature(signatureHeader, rawBody, secret);
if (!result.valid) {
return res.status(401).send(result.error);
}
const payload = JSON.parse(rawBody);
// Procesa payload...
res.status(200).send();
});
```
## Ejemplo trabajado [#ejemplo-trabajado]
**Entradas**
* Header: `t=1766002441,v1=62afda2079925823b390e1199060d793aa50d64ec9d7bf184f5b7e96c8bf411c`
* Secret: `whsec_1w5dFdWSaGV7qiTpf0VGqRk62rG2FSknb`
* Body crudo:
```json
{
"timestamp": "2025-12-17T20:14:01.947520Z",
"event": {
"data": {
"id": "bbot_031uOJ6qb0sVclNseQfItQ",
"state": "failed",
"description": "Comfama",
"query": { "value": "@SBV567", "format": "plain_key" },
"target": {
"id": "bbtgt_031uOJ6uISEH2QbuvZlW3M",
"key_value": "@SBV567",
"key_type": "alphanumeric",
"transaction_amount": null,
"spbvi": "CRB",
"resolution_type": "plain_key",
"payment_id": null,
"creditor_account": {
"type": "savings_account",
"number": "892893",
"currency_code": "COP"
},
"creditor": {
"type": "natural",
"full_name": "Juan Carlos Perez Gomez",
"document_type": "CC",
"document_number": "123143455"
},
"participant_nit": "1431701149"
},
"amount": { "currency": "COP", "amount": 100 },
"external_id": "21752385-357e-450f-94a3-d9a0984da291::5f283bc8-9ee7-404b-8952-5082e5c181a7",
"inserted_at": "2025-12-17T20:13:57.545628Z",
"updated_at": "2025-12-17T20:13:57.589888Z",
"payment_id": null,
"state_reason": "target_creditor_mismatch",
"resolution_request_id": "bbtgr_031uOJ6qOnaxydvH1CZB3q",
"timemarks": {},
"expected_creditor": { "document_type": "CC", "document_number": "21482961" }
},
"type": "outgoing_transfer.created"
}
}
```
**Pasos**
1. Extrae `timestamp = "1766002441"` y `signature = "62afda20...c8bf411c"` del header.
2. Construye `signed_payload = "1766002441.{raw_body}"`.
3. Calcula `HMAC-SHA256(secret, signed_payload)`.
4. Compara con la firma del header usando una comparación timing-safe.
La firma calculada coincide byte a byte con la firma del header, así que el
request es válido.
## Buenas prácticas [#buenas-prácticas]
1. **Usa el body crudo.** Nunca re-serialices JSON antes de verificar — el orden de keys y
los espacios no están garantizados.
2. **Aplica una tolerancia de timestamp.** El default de 5 minutos alcanza para
absorber el clock skew y rechazar webhooks replay. Ajústalo más arriba o más abajo
según tu modelo de amenazas.
3. **Compara con `timingSafeEqual`.** Las comparaciones de igualdad estándar filtran información
por canales laterales de timing.
4. **Protege tu secret.** El secret de firma (prefijo `whsec_`) es el secret compartido
entre Mono y tu servicio. Mantenlo fuera de logs y del control de versiones,
y rótalo si llega a quedar expuesto.
# Bre-B Participant (/es/docs/guides/breb-participant)
Bre-B Participant es la rampa de acceso programable de Mono a Bre-B, el esquema interbancario de pagos instantáneos de Colombia. Úsalo para recibir pagos Bre-B mediante códigos QR o llaves de pago registradas, y para despachar transferencias salientes contra cualquier llave de pago resuelta a través del directorio central.
Conoce cómo funciona el sistema Bre-B y cómo se integra con Mono en
[breb.app](https://breb.app/).
Cómo la API de Participant te conecta a la red Bre-B.
OAuth 2.0 client credentials para Bre-B Participant.
Llaves de pago, targets, cuentas tenant y recaudos.
Casos de uso de recaudo por QR y transferencia saliente de extremo a extremo.
Recorrido realista de una integración de Bre-B participant.
Prueba tu integración Bre-B con pagos y transferencias simuladas.
Máquina de estados, transiciones y estados terminales para transferencias salientes.
Motivos de rechazo, razones de estado y recomendaciones de manejo.
Verifica la autenticidad de los webhooks entrantes con HMAC-SHA256.
Envelope del payload y tipos de evento de transferencia saliente.
Recomendaciones de producción: confirmación de nombre, idempotencia, firmas.
Definiciones de términos Bre-B.
# Ejemplo de integración (/es/docs/guides/core/integration-example)
Esta página recorre la construcción de una **billetera de consumo** sobre Core de extremo a extremo: un producto donde los usuarios finales mantienen un saldo, lo recargan, envían dinero a otros usuarios y lo gastan con una tarjeta virtual. Es el ejemplo canónico para entender cómo los servicios de Core se componen en un producto real.
Una billetera es el caso de uso más común de Core. Si estás construyendo otra cosa — un programa de tarjetas corporativas, un saldo de vendedor de marketplace, un sistema de tesorería — los patrones aquí se transfieren directamente; solo cambian los nombres de las entidades.
Esta página es un esqueleto de ejemplo de integración. La versión completa incluirá una
narrativa completa, snippets de código por paso y un diagrama de arquitectura de referencia.
## Qué vas a construir [#qué-vas-a-construir]
Una billetera que soporta:
* **Onboarding** — un usuario se registra y obtiene una cuenta de ledger.
* **Recarga** — el usuario agrega fondos vía PSE (Banking lo maneja; Core registra el crédito).
* **Emisión de tarjeta** — el usuario solicita una tarjeta virtual que gasta contra su cuenta de ledger.
* **Gasto** — la tarjeta se autoriza en tiempo real contra el saldo del usuario y sus límites.
* **Transferencia W2W** — el usuario envía dinero a otro usuario de la billetera.
* **Dispersión** — el usuario retira a una cuenta bancaria colombiana (Banking maneja el riel; Core debita el ledger).
## Arquitectura [#arquitectura]
La billetera se apoya en tres servicios de Core y uno de Banking:
| Capa | Servicio | Rol |
| :----------- | :----------------------------------- | :----------------------------------------------- |
| Dinero entra | Banking — Recaudo PSE | Recarga iniciada por el pagador |
| Saldo | Core — Ledger | Cuenta por usuario, cada peso registrado |
| Gasto | Core — Tarjetas + Controles de gasto | Emisión de tarjeta + autorización en tiempo real |
| Dinero sale | Core — Dispersiones → Banking | Retiro a banco externo |
## Recorrido [#recorrido]
El recorrido completo — código de onboarding, manejo de recargas, emisión de tarjeta, autorización en tiempo real, transferencia W2W y dispersión — se agregará aquí.
## Siguientes pasos [#siguientes-pasos]
* [Ledger](/docs/guides/core/concepts/ledger) — el modelo de datos que ancla la billetera.
* [Tarjetas](/docs/guides/core/concepts/cards) — emisión y ciclo de vida.
* [Buenas prácticas](/docs/guides/core/best-practices) — consideraciones de producción.
# Glosario (/es/docs/guides/core/glossary)
Una referencia alfabética breve para los términos que encontrarás integrando Core. Cada entrada es una definición en lenguaje claro de una o dos frases. Para el modelo de datos completo detrás de cualquiera de estos términos, sigue el enlace a la página de concepto correspondiente.
Este glosario es un punto de partida y va a crecer. Si te falta un término, por favor abre un
issue en el repo de docs para que lo agreguemos.
## Autorización [#autorización]
La decisión en tiempo real que toma Core cuando una tarjeta se presenta para un pago. Core verifica el saldo del tarjetahabiente y los controles de gasto activos, y aprueba o rechaza la transacción.
## Clave de idempotencia [#clave-de-idempotencia]
Una etiqueta única que tu sistema adjunta a una petición de write para que Core trate un reintento como la misma operación. Ver [Idempotency Keys](/docs/guides/api-standards/idempotency-keys).
## Control de gasto [#control-de-gasto]
Una regla que limita lo que puede hacer una tarjeta — límites diarios, bloqueo de categorías, restricciones por país. Ver [Controles de gasto](/docs/guides/core/concepts/spending-controls).
## Cuenta de ledger [#cuenta-de-ledger]
Una cuenta lógica dentro del ledger. Cada usuario final, sub-negocio o bucket financiero recibe su propia cuenta de ledger.
## Cuenta funding [#cuenta-funding]
La cuenta bancaria real que respalda tu programa — donde finalmente se entrega el dinero de los pagos recibidos, y de donde sale el dinero de las dispersiones. Ver [Liquidación](/docs/guides/core/concepts/payins/settlement).
## Dispersión [#dispersión]
Un movimiento de fondos hacia afuera de una cuenta de ledger, hacia un destino externo (cuenta bancaria colombiana, otro participante, etc.). Ver [Dispersiones](/docs/guides/core/concepts/payouts).
## Fee [#fee]
Un cobro asociado a un movimiento de dinero — una dispersión, un pago o una transferencia entre subcuentas. Los fees con `owner: tenant` son lo que le cobras a tus usuarios o clientes; los fees con `owner: mono` son lo que Mono te cobra a ti. Ver [Fees](/docs/guides/core/concepts/fees).
## Hold [#hold]
Una reserva temporal de fondos sobre una cuenta de ledger, usada para garantizar que una autorización en curso podrá liquidarse. Los holds expiran si no se liquidan. Ver [Ledger](/docs/guides/core/concepts/ledger).
## Intención de pago (payin intent) [#intención-de-pago-payin-intent]
Un único pago esperado, declarado antes de que llegue el dinero, con su propio token de pago y ventana de expiración. Ver [Intenciones de pago](/docs/guides/core/concepts/payins/intents).
## Ledger [#ledger]
El sistema de contabilidad de partida doble de Core. Cada movimiento de dinero produce un par balanceado de entradas (débito y crédito). Ver [Ledger](/docs/guides/core/concepts/ledger).
## Lote de liquidación (settlement batch) [#lote-de-liquidación-settlement-batch]
El movimiento único de dinero que entrega los fondos de muchos pagos en una cuenta funding. Ver [Liquidación](/docs/guides/core/concepts/payins/settlement).
## Marca [#marca]
El aspecto reutilizable que viste el checkout hospedado de Mono por ti — nombre, texto del botón, color de acento, logo y mensaje del voucher. Una plantilla de recaudos apunta a una mediante `form_customization.brand_id`. Ver [Plantillas](/docs/guides/core/concepts/payins/templates#marcas).
## MCC (Merchant Category Code) [#mcc-merchant-category-code]
Un código de cuatro dígitos que identifica el tipo de negocio detrás de una transacción de tarjeta (supermercado, combustible, viajes, etc.). Los controles de gasto lo usan para permitir o bloquear categorías.
## Pago (payin) [#pago-payin]
Un pago recibido: dinero recaudado hacia una subcuenta a través de un método de pago como Bre-B. Ver [Pagos](/docs/guides/core/concepts/payins/payins).
## Plantilla (payin template) [#plantilla-payin-template]
Una configuración reutilizable atada a una subcuenta que define cómo se recaudan los pagos: métodos, límites, pagadores permitidos, expiración. Ver [Plantillas](/docs/guides/core/concepts/payins/templates).
## Política de fees [#política-de-fees]
La regla de precio detrás de un fee: cuánto cobrar por un tipo de fee y a quién aplica (su target) — todas las cuentas de tus usuarios o clientes, un programa, un propietario de cuenta o una sola cuenta. Gana la política más específica. Ver [Políticas de fees](/docs/guides/core/concepts/fees/policies).
## Programa [#programa]
Un grupo de cuentas que comparten una misma configuración: monedas, funcionalidades o servicios habilitados, métodos de pago y la cuenta funding con la que liquidan. Toda cuenta pertenece a un programa. Ver [Programas](/docs/guides/core/concepts/program).
## Settlement [#settlement]
El estado final de una autorización donde los fondos retenidos se deducen efectivamente de la cuenta de ledger. Las autorizaciones que no se liquidan dentro de la ventana del hold se revierten.
## Tarjeta [#tarjeta]
Una tarjeta de pago virtual o física emitida por Core que gasta contra una cuenta de ledger. Ver [Tarjetas](/docs/guides/core/concepts/cards).
## Tenant [#tenant]
Tu empresa como cliente de Mono. Todo recurso de Core — programas, cuentas, políticas, fees — pertenece a un tenant, y ningún tenant puede ver los de otro. Ver [Arquitectura](/docs/guides/core/architecture).
## Token de pago [#token-de-pago]
Aquello a lo que paga un pagador. Cada método emite el suyo — para Bre-B, una llave o un código QR emitido para una plantilla o una intención de pago. Ver [Tokens de pago](/docs/guides/core/concepts/payins/payment-tokens).
# Autenticación (/es/docs/guides/core/authentication)
Cada solicitud a la API de Core debe demostrar que viene de tu empresa. Puedes demostrarlo con una API key de larga duración, o con un access token de corta duración que tu servidor pide por su cuenta. Mono acepta ambos en todos los endpoints de Core, así que puedes elegir el que mejor encaje con tus prácticas de seguridad.
Una credencial pertenece a un solo servicio. Las credenciales de Core solo funcionan en la API
de Core, y una credencial de Banking enviada a un endpoint de Core es rechazada.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una organización de Mono con Core habilitado, y acceso a su Mono Dashboard.
* Un cliente HTTPS. Mono rechaza las solicitudes que no usan TLS.
* Un lugar seguro para los secretos, como un gestor de secretos. Nunca subas credenciales al control de versiones.
Las convenciones que comparten todos los productos de Mono, como el formato del header y los entornos, están en [Estándares de autenticación](/docs/guides/api-standards/authentication).
## Elige una credencial [#elige-una-credencial]
| | API key | Access token de OAuth 2.0 |
| :--------------- | :---------------------------- | :----------------------------------------------- |
| Cuánto dura | Hasta que la revoques | 30 minutos; después pides uno nuevo |
| Qué puede hacer | Lo que permiten sus roles | Lo que permiten sus scopes |
| Cómo la obtienes | Se crea en el Mono Dashboard | Tu servidor la pide con un client ID y un secret |
| Dónde va | `Authorization: Bearer ` | `Authorization: Bearer ` |
Un **rol** es un conjunto de permisos con nombre, como "Admin" o "Viewer", asignado a una API key. Un **scope** es un permiso individual, como `payouts:readonly`, asignado a un access token. Mono relaciona cada permiso de rol con los scopes que autorizan la misma acción, así que un endpoint acepta cualquiera de las dos credenciales para la misma operación.
## Opción 1: API keys [#opción-1-api-keys]
Una API key es una cadena secreta que sigue siendo válida hasta que la revoques. Para crear o revocar llaves, inicia sesión en el [Mono Dashboard](https://mi.cuentamono.com/log_in) y abre la [sección de gestión de API keys](https://mi.cuentamono.com/settings/api).
Al crear una llave, elige sus roles. La llave puede hacer exactamente lo que esos roles permiten.
Envía la llave en el header `Authorization` de cada solicitud:
```bash
curl https://api.cuentamono.com/v1/ledger/accounts \
-H "Authorization: Bearer $MONO_API_KEY"
```
Las llaves no expiran, así que rotarlas es tu responsabilidad: crea una llave nueva, despliégala y luego revoca la anterior.
## Opción 2: access tokens de OAuth 2.0 [#opción-2-access-tokens-de-oauth-20]
OAuth 2.0 es un estándar de la industria para emitir credenciales temporales. Mono usa su flujo de **client credentials**, pensado para integraciones de servidor a servidor. Tu servidor cambia un client ID y un client secret permanentes por un access token que dura 30 minutos. Piénsalo como cambiar el carné de empleado por un pase de un día: el pase vence y el carné se queda guardado en la caja fuerte.
### Crea una credencial [#crea-una-credencial]
En el Mono Dashboard, abre la sección **Credentials** de tu organización y crea una credencial:
1. Elige los scopes que la credencial puede pedir. Puedes cambiarlos después.
2. Copia el client ID y el client secret. El dashboard muestra el secret una sola vez.
3. Guarda ambos en tu gestor de secretos.
No eliges entre Core y Banking: una organización de Core siempre recibe credenciales de Core. Desde la misma sección también puedes desactivar, activar o eliminar una credencial.
### Pide un access token [#pide-un-access-token]
Llama al endpoint de token de Core con `grant_type=client_credentials`, tu client ID y tu secret, y los scopes que necesitas, separados por espacios:
```bash
curl https://api.cuentamono.com/v1/core/oauth/token \
-d grant_type=client_credentials \
-d client_id="$MONO_CLIENT_ID" \
-d client_secret="$MONO_CLIENT_SECRET" \
-d scope="ledger:readonly payouts"
```
El endpoint acepta `application/x-www-form-urlencoded`, como arriba, o los mismos campos en JSON. En sandbox, usa `https://api.sandbox.cuentamono.com`.
```json
{
"access_token": "",
"token_type": "Bearer",
"expires_in": 1800,
"refresh_token": "",
"scope": "ledger:readonly payouts"
}
```
Envía siempre `scope`. Si lo omites, Mono igual emite un token, pero sin scopes, y luego todos
los endpoints responden `403`. Si pides un scope que tu credencial no tiene, toda la solicitud
falla con `400 invalid_scope`; Mono nunca devuelve un token con solo una parte de lo que
pediste.
### Llama a la API [#llama-a-la-api]
Envía el access token en el header `Authorization`, igual que una API key:
```bash
curl https://api.cuentamono.com/v1/ledger/accounts \
-H "Authorization: Bearer $MONO_ACCESS_TOKEN"
```
### Renueva el token [#renueva-el-token]
`expires_in` es la vida restante del token en segundos: 1800 cuando se emite. Pide un token nuevo antes de que se acabe. Tienes dos formas:
* **Pide un token nuevo** con tus client credentials, igual que arriba. Es la opción más simple para un servidor que siempre tiene su secret.
* **Usa el refresh token.** La respuesta también trae un `refresh_token`, válido por un día. Cámbialo por un access token nuevo:
```bash
curl https://api.cuentamono.com/v1/core/oauth/token \
-d grant_type=refresh_token \
-d client_id="$MONO_CLIENT_ID" \
-d client_secret="$MONO_CLIENT_SECRET" \
-d refresh_token="$MONO_REFRESH_TOKEN"
```
Cada refresh devuelve un refresh token nuevo. Usa el nuevo la próxima vez y descarta el anterior.
### Consulta o revoca un token [#consulta-o-revoca-un-token]
Otros dos endpoints reciben tu client ID y tu secret, más el token:
* **Introspect** (`grant_type=introspect`) te dice si un token sigue activo, cuándo expira y qué scopes trae. Un token expirado o desconocido devuelve `{"active": false}`.
* **Revoke** (`grant_type=revoke`) invalida de inmediato un access token o un refresh token. Responde `200` aunque el token ya hubiera expirado.
Revoca los tokens que ya no necesites, por ejemplo cuando retires un servidor. Los contratos de los endpoints están en la [referencia de autenticación de Core](/docs/api-reference/core/authentication).
## Scopes [#scopes]
Pide los scopes más acotados que necesite tu integración. Dos reglas aplican a todo el catálogo:
* Un scope sin sufijo, como `cards`, autoriza todas las acciones sobre ese recurso, incluidas las acciones específicas que aparecen debajo.
* Un scope `:readonly` autoriza solo lecturas. No incluye las acciones específicas: `cards:readonly` no autoriza `cards:details`.
| Scope | Autoriza |
| :--------------------------- | :------------------------------------------------------------------------------------------------------------- |
| `ledger` | Todos los permisos sobre los recursos del ledger, y leer sus programas |
| `ledger:readonly` | Leer los recursos del ledger y sus programas |
| `payins` | Todos los permisos sobre pagos, sus plantillas y sus intenciones de pago, y leer marcas y lotes de liquidación |
| `payins:readonly` | Leer pagos, sus plantillas, sus intenciones de pago, marcas y lotes de liquidación |
| `payouts` | Todos los permisos sobre payouts, la resolución de destinos Bre-B y el catálogo de bancos |
| `payouts:readonly` | Leer payouts y el catálogo de bancos |
| `fees` | Todos los permisos sobre comisiones y las políticas que las cobran |
| `fees:readonly` | Leer comisiones y sus políticas |
| `fx_rates` | Leer las tasas de cambio de las monedas soportadas |
| `fx_rates:readonly` | Leer las tasas de cambio de las monedas soportadas |
| `cards` | Todos los permisos sobre tarjetas |
| `cards:readonly` | Leer tarjetas |
| `cards:details` | Leer los datos sensibles de una tarjeta: número, código de seguridad y vencimiento |
| `spending_controls` | Todos los permisos sobre los controles de gasto de tarjetas |
| `spending_controls:readonly` | Leer los controles de gasto de tarjetas |
Bre-B es el sistema de pagos inmediatos de Colombia, operado por el Banco de la República. La resolución de destinos convierte una llave Bre-B, como un número de celular, en la cuenta a la que apunta.
## Fallas [#fallas]
Los endpoints de token, introspect y revoke responden los errores en formato OAuth, con los campos `error` y `error_description`. Por ejemplo, `400 invalid_scope` significa que pediste un scope que tu credencial no tiene.
Todos los demás endpoints responden con el [envelope de error](/docs/guides/api-standards/errors-and-retries) estándar:
| Estado | Código | Qué pasó |
| :----- | :--------------- | :-------------------------------------------------------------------------- |
| `401` | `expired_token` | El token expiró, fue revocado o pertenece a otro servicio, como Banking. |
| `403` | `not_authorized` | La credencial es válida, pero sus scopes o roles no permiten este endpoint. |
| `403` | `not_authorized` | Una organización que no es de Core llamó a un endpoint del ledger. |
Un token de Banking enviado a un endpoint de Core recibe el mismo `401 expired_token` que un
token expirado. Si un token recién emitido es rechazado así, verifica que venga de
`/v1/core/oauth/token` y no de `/v1/oauth/token`.
## Siguientes pasos [#siguientes-pasos]
* [Referencia de autenticación](/docs/api-reference/core/authentication) — los endpoints de token, introspect y revoke en detalle.
* [Arquitectura](/docs/guides/core/architecture) — cómo fluye una solicitud después de la autenticación.
* [Buenas prácticas](/docs/guides/core/best-practices) — rotación de llaves, scopes mínimos y almacenamiento de credenciales.
# Buenas prácticas (/es/docs/guides/core/best-practices)
Esta página recoge las prácticas operativas que separan una integración de Core que funciona en sandbox de una que sobrevive en producción. La mayoría no son opcionales — así es como mantienes el ledger consistente, evitas duplicados cuando la red parpadea y respondes rápido cuando algo sale mal.
Si estás desplegando Core para mover dinero real, trata esta página como un checklist antes del lanzamiento y como referencia durante incidentes.
Esta página es un esqueleto de buenas prácticas. La versión completa incluirá ejemplos
concretos, antipatrones a evitar y runbooks para los modos de falla de producción más comunes.
## Idempotencia en cada write [#idempotencia-en-cada-write]
Cada write a Core — crear una transacción de ledger, emitir una tarjeta, disparar una dispersión — debe incluir una [clave de idempotencia](/docs/guides/api-standards/idempotency-keys). Sin ella, un timeout de red duplicará la escritura en silencio.
## Concilia contra el ledger, no contra tu caché [#concilia-contra-el-ledger-no-contra-tu-caché]
El ledger de Core es la fuente de verdad. La base de datos de tu aplicación puede desincronizarse; el ledger no. Construye un job de conciliación que compare periódicamente tu vista de saldos contra el ledger y exponga cualquier desviación.
## Los webhooks no reemplazan al polling [#los-webhooks-no-reemplazan-al-polling]
Los webhooks se disparan de forma async y pueden llegar fuera de orden o ser reintentados. Para operaciones en estado final (transacciones liquidadas, dispersiones completadas), consulta la API en el momento en que tu lógica de negocio depende del resultado. Usa webhooks para despertar tu sistema; usa la API para confirmar.
## Rota las claves con un calendario [#rota-las-claves-con-un-calendario]
Las API keys no expiran automáticamente. Rótalas al menos cada 90 días e inmediatamente después de cualquier exposición sospechada. Mantén un procedimiento de rotación documentado que no dependa de la memoria humana.
## Logea el request ID [#logea-el-request-id]
Cada respuesta de Core lleva un request ID. Logéalo en cada petición e inclúyelo en tu reporte de errores. Cuando escales un problema a soporte de Mono, el request ID es el camino más corto al diagnóstico.
## Antipatrones a evitar [#antipatrones-a-evitar]
* Reutilizar una sola API key entre staging y producción.
* Hardcodear URLs de webhook sin una forma de rotarlas.
* Tratar la respuesta síncrona de una dispersión como confirmación de settlement.
* Mantener entradas de ledger en memoria entre procesos — siempre escribe en el ledger.
## Siguientes pasos [#siguientes-pasos]
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys)
* [Arquitectura](/docs/guides/core/architecture)
* [Lista de verificación de producción](/docs/guides/core/production)
# Salir a producción (/es/docs/guides/core/production)
Salir a producción con Core significa asumir la responsabilidad de mover dinero real. Antes de prender el switch, cada ítem de esta lista debe ser verdadero: rotaste claves, firmaste acuerdos, validaste la conciliación, configuraste monitoreo y ensayaste un incidente.
Esta página está pensada para abrirse el día antes del lanzamiento y recorrerse ítem por ítem. Si algo está sin marcar, no lances.
Esta página es un esqueleto de preparación para producción. La versión completa incluirá la
lista legal, operativa y de ingeniería, además del proceso de certificación que Mono ejecuta
antes de activar credenciales de producción.
## Checklist de ingeniería [#checklist-de-ingeniería]
* [ ] API keys de producción generadas, almacenadas en un secrets manager y rotadas.
* [ ] Claves de idempotencia enviadas en cada petición de write.
* [ ] Endpoint de webhook desplegado, con verificación de firma y tolerancia a reintentos.
* [ ] Job de conciliación corriendo al menos diariamente y alertando sobre desviaciones.
* [ ] Logging que incluye el request ID de Mono en cada llamada a la API.
* [ ] Monitoreo y alertas sobre respuestas `5xx`, fallas de webhook y desviaciones de conciliación.
* [ ] Runbook de incidentes documentado y ensayado.
## Checklist operativo [#checklist-operativo]
* [ ] Soporte al cliente con acceso al Mono Dashboard con los scopes apropiados.
* [ ] Finanzas con el procedimiento de conciliación documentado y dueña del cierre del ledger.
* [ ] Cumplimiento ha revisado el modelo de datos y la política de retención.
## Certificación Mono [#certificación-mono]
Mono realiza una revisión de certificación antes de activar credenciales de producción. Los detalles se documentarán aquí.
## Siguientes pasos [#siguientes-pasos]
* [Buenas prácticas](/docs/guides/core/best-practices)
* [Sandbox](/docs/guides/core/sandbox)
* [Webhooks](/docs/guides/core/webhooks)
# Core (/es/docs/guides/core)
Core es la parte de Mono que te permite construir productos financieros sobre primitivas programables. Mientras Banking maneja el dinero que entra y sale de las cuentas bancarias colombianas, Core maneja **lo que pasa con el dinero una vez es tuyo para administrar**: un ledger de partida doble que rastrea cada peso, emisión de tarjetas virtuales y físicas, controles de gasto, recaudos y dispersión de pagos.
Usa Core para construir billeteras, saldos de vendedores en marketplaces, productos de gestión de gastos, experiencias de neobanking y cualquier otro caso que te exija mantener valor, contabilizarlo y permitir que tus usuarios finales lo gasten o lo muevan.
Cómo encajan los servicios de Core y cómo fluye una petición de extremo a extremo.
Cómo autenticarte contra la API de Core.
Ledger, programas, fees, tarjetas, controles de gasto, dispersiones y tasas FX.
Recibe dinero en subcuentas — plantillas, intenciones de pago, pagos y liquidación.
Casos de uso de extremo a extremo — emisión de tarjetas, contabilidad de ledger,
dispersiones.
Un recorrido realista para construir una billetera sobre Core.
Eventos disparados durante transacciones de tarjeta y movimientos de ledger.
Prueba Core sin mover dinero real.
Recomendaciones de producción: idempotencia, conciliación, rotación de claves.
Administra tarjetas, cuentas de ledger y dispersiones desde el Mono Dashboard.
Lista de verificación antes de pasar de sandbox a producción.
## Usa Core para: [#usa-core-para]
* Mantener saldos de tus usuarios finales en un ledger programable y auditable.
* Recibir dinero en subcuentas — recargas de billetera y pagos de clientes por Bre-B.
* Emitir tarjetas virtuales o físicas y decidir qué transacciones autorizar en tiempo real.
* Definir límites de gasto por tarjeta, por usuario o por categoría.
* Dispersar pagos a cuentas internas de ledger o a destinos externos.
* Conciliar cada movimiento contra un registro inmutable.
## Casos de uso principales [#casos-de-uso-principales]
| Caso de uso | Lo que construyes | Productos de Core que usas |
| :---------------------------------------- | :------------------------------------------------------- | :------------------------------------- |
| Billetera de consumo | Cuenta, recargas, tarjeta, transferencias entre usuarios | Ledger + Tarjetas + Dispersiones |
| Saldo de vendedores en marketplace | Ledger por vendedor, dispersiones programadas | Ledger + Dispersiones |
| Tarjetas corporativas / gestión de gastos | Tarjetas por empleado con límites por categoría | Tarjetas + Controles de gasto + Ledger |
| Tesorería / neobanco multi-cuenta | Cuentas multi-divisa con FX | Ledger + Tasas FX + Dispersiones |
# Resumen de arquitectura (/es/docs/guides/core/architecture)
Core se compone de cinco servicios que trabajan juntos para mantener saldos, emitir tarjetas, definir límites y mover dinero hacia adentro y hacia afuera: un **ledger** que lleva la contabilidad, un servicio de **emisión de tarjetas** que crea y autoriza tarjetas, un servicio de **controles de gasto** que decide qué pueden hacer las tarjetas, un servicio de **recaudos** que trae el dinero y un servicio de **dispersiones** que lo saca. Cada llamada a la API pasa por el mismo pipeline de autenticación, idempotencia y enrutamiento antes de llegar al servicio dueño de la petición.
Esta página describe ese pipeline. Es el punto de partida correcto si quieres entender qué pasa entre la petición de un cliente y el webhook que recibe tu sistema.
## Servicios [#servicios]
| Servicio | Responsabilidad |
| :----------------- | :----------------------------------------------------------------------------------------------------------------- |
| Ledger | Contabilidad de partida doble — cuentas, transacciones, holds, settlements, conciliación. |
| Tarjetas | Emisión de tarjetas virtuales y físicas, activación, congelamiento, reposición, provisión a billeteras. |
| Controles de gasto | Decisiones de autorización en tiempo real por tarjeta según límites, categorías y reglas. |
| Recaudos | Pagos entrantes (recaudos y recargas) — reglas de recaudo, crédito a subcuentas y liquidación a tu cuenta funding. |
| Dispersiones | Dispersión a cuentas internas de ledger o a destinos externos a través de los rieles de Banking. |
## Ciclo de vida de una petición [#ciclo-de-vida-de-una-petición]
Cada petición a Core sigue los mismos cinco pasos:
1. **Autenticar** — se valida el API key del header `Authorization`.
2. **Verificar idempotencia** — si `X-Idempotency-Key` está presente, se devuelve la respuesta cacheada para claves conocidas.
3. **Enrutar al servicio dueño** — según el path de la URL.
4. **Ejecutar y responder** — el servicio escribe en el ledger, devuelve el resultado y hace commit de forma síncrona.
5. **Disparar webhooks** — los eventos async se despachan a tu endpoint registrado.
## Modelo multi-tenant [#modelo-multi-tenant]
Cada cliente de Mono es un tenant. Todos los recursos de Core — cuentas de ledger, tarjetas, recaudos, dispersiones — tienen scope de tenant. No hay visibilidad entre tenants.
## Siguientes pasos [#siguientes-pasos]
* [Autenticación](/docs/guides/core/authentication) — credenciales y headers.
* [Ledger](/docs/guides/core/concepts/ledger) — la base sobre la que se construye Core.
* [Ejemplo de integración](/docs/guides/core/integration-example) — recorrido realista.
# Dashboard (/es/docs/guides/core/dashboard)
El Mono Dashboard es la interfaz web donde los roles no técnicos — operaciones, finanzas, soporte al cliente, cumplimiento — interactúan con Core. Todo lo que expone la API se puede inspeccionar desde el Dashboard, y muchas acciones (crear controles de gasto, congelar una tarjeta, disparar una dispersión manual) se pueden ejecutar sin tocar código.
Esta página describe qué hace cada sección del Dashboard y qué rol la usa típicamente. Es el punto de partida correcto para alguien que se une al equipo y necesita operar Core pero no escribe la integración.
Esta página es un esqueleto del dashboard. La versión completa incluirá capturas de cada
sección, notas de acceso por rol y ejemplos de extremo a extremo (por ejemplo, "cómo emitir
una reposición de tarjeta de emergencia").
## Secciones [#secciones]
| Sección | Qué muestra | Quién la usa |
| :----------------- | :--------------------------------------------------------------------- | :---------------------- |
| Ledger | Saldos por cuenta, historial de transacciones, holds, settlements. | Finanzas, ops |
| Tarjetas | Tarjetas activas e inactivas, congelar/descongelar, reposiciones. | Soporte al cliente, ops |
| Controles de gasto | Límites y reglas asociadas a tarjetas. | Ops, cumplimiento |
| Dispersiones | Dispersiones programadas y ad-hoc, estado, fallas. | Finanzas, ops |
| Webhooks | Configuración de endpoints, entregas recientes, intentos de reintento. | Ingeniería |
| API keys | Gestión de claves, roles, rotación. | Ingeniería, seguridad |
| Credentials | Credenciales OAuth 2.0: crear, asignar scopes, desactivar, eliminar. | Ingeniería, seguridad |
## Operaciones comunes [#operaciones-comunes]
Los recorridos específicos (emitir una tarjeta, congelar una tarjeta, disparar manualmente una dispersión, investigar una autorización fallida) se documentarán aquí.
## Configuración de webhooks [#configuración-de-webhooks]
### Crear un webhook [#crear-un-webhook]
La creación de webhooks se hace a través de la interfaz de usuario:
1. Ve a settings.
2. Haz clic en Personal.
3. Haz clic en API.
4. Haz clic en nuevo webhook.
5. Inserta la URL HTTPS/HTTP que hayas configurado para escuchar los eventos.
6. Haz clic en guardar.
Si todo funciona como se espera, aparecerá una alerta indicando que tu webhook fue creado correctamente, y deberías poder ver la URL listada en la página.
Las URLs de webhook son únicas — solo puedes crear un webhook por URL. Para reutilizar una URL, primero elimina el webhook existente y crea uno nuevo. Se generará un nuevo secret.
Verifica siempre que tu endpoint esté recibiendo eventos correctamente.
### Ver detalles del webhook [#ver-detalles-del-webhook]
Haz clic en la flecha al final de la fila donde se muestra tu URL para abrir la vista de detalle. La vista de detalle contiene el secret del webhook, que está oculto por defecto. Haz clic en el **ojito de mostrar/ocultar** para revelarlo.
Mantén seguro el secret de tu webhook. No lo compartas.
## Siguientes pasos [#siguientes-pasos]
* [Webhook conventions](/docs/guides/api-standards/webhooks) — verificación de firma, política de reintentos y campos de dinero.
* [Arquitectura](/docs/guides/core/architecture)
* [Buenas prácticas](/docs/guides/core/best-practices)
# Autenticación (/es/docs/guides/api-standards/authentication)
La autenticación es la forma en que Mono confirma que una solicitud viene de tu sistema y no de alguien más. Cada producto de Mono requiere autenticación en cada solicitud — no hay acceso anónimo. Esta página cubre las convenciones que aplican a todos los productos. El tipo exacto de credencial (API key vs. token OAuth) depende de qué producto estés integrando.
Mono usa dos esquemas de autenticación: **API keys estáticas** y **tokens OAuth 2.0** de corta
duración. Banking y Core aceptan ambos, en todos sus endpoints. Bre-B Participant y Wallet
usan solo tokens OAuth 2.0. Ambos esquemas comparten el transporte y el header descritos
abajo.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una cuenta de Mono activa con credenciales para el entorno al que quieres llamar (sandbox o producción).
* Soporte HTTPS en tu cliente. Mono rechaza solicitudes que no usen TLS.
* Un lugar seguro para almacenar credenciales — un gestor de secretos, variables de entorno en un runtime gestionado, o equivalente. Nunca las commitees al control de versiones.
## Cómo viajan las credenciales [#cómo-viajan-las-credenciales]
Todas las solicitudes autenticadas llevan la credencial en el header `Authorization` usando el esquema `Bearer`.
```
Authorization: Bearer
```
Esto aplica sin importar si la credencial es una API key estática o un token OAuth de corta duración. El nombre del header y el esquema son fijos; solo cambia el valor entre productos.
```bash
curl https://api.cuentamono.com/v1/bank_accounts \
-H "Authorization: Bearer $MONO_API_KEY"
```
## Entornos [#entornos]
Mono separa las credenciales de producción de las de sandbox. No son intercambiables:
* Las llaves de **sandbox** solo funcionan contra las URLs base de sandbox y nunca mueven dinero real.
* Las llaves de **producción** mueven dinero real. Trátalas con el mismo cuidado que una contraseña bancaria.
Cada página de producto describe la URL base exacta para sus entornos de sandbox y producción.
## Fallas [#fallas]
Cuando la autenticación falla, Mono devuelve uno de dos códigos de estado HTTP:
| Estado | Significado | Causa típica |
| :----------------- | :---------------------------------------------------------------- | :----------------------------------------------------------------------------- |
| `401 Unauthorized` | La credencial falta, está malformada, expirada o revocada. | Header no enviado, token OAuth expirado, API key revocada. |
| `403 Forbidden` | La credencial es válida pero no tiene permiso sobre este recurso. | El rol o el scope no autoriza la acción, o el recurso pertenece a otro tenant. |
Ambas respuestas siguen el [envelope de error](/docs/guides/api-standards/errors-and-retries) estándar. No reintentes un `401` o `403` sin antes refrescar o reemplazar la credencial — los reintentos con la misma credencial mala seguirán fallando y pueden disparar penalizaciones por límite de tasa.
## Rotación de credenciales [#rotación-de-credenciales]
Rota las credenciales en un cronograma y de inmediato tras cualquier sospecha de exposición (un log filtrado, un ex-empleado, un laptop comprometido). El procedimiento de rotación depende del producto:
* **API keys (Banking, Core)**: genera una nueva llave en el Dashboard de Mono, despliégala, y luego revoca la antigua. Las llaves no expiran automáticamente; la rotación es tu responsabilidad.
* **Tokens OAuth (Banking, Core, Bre-B Participant, Wallet)**: los tokens expiran solos. La rotación en sentido operativo significa rotar el client secret o la contraseña subyacentes que se usan para obtener nuevos tokens.
## Particularidades por producto [#particularidades-por-producto]
Las reglas entre productos de arriba son universales. El tipo exacto de credencial, cómo obtenerla y cualquier scope específico del producto se documentan en la página de autenticación de cada producto:
API keys y OAuth 2.0 client credentials para Banking.
API keys y OAuth 2.0 client credentials para Core.
OAuth 2.0 client credentials para Bre-B Participant.
## Siguientes pasos [#siguientes-pasos]
* [Claves de idempotencia](/docs/guides/api-standards/idempotency-keys) — cómo reintentar operaciones de escritura de forma segura.
* [Errores y reintentos](/docs/guides/api-standards/errors-and-retries) — el envelope de error estándar devuelto en fallas de auth, más la política de reintentos.
* [Formatos de datos](/docs/guides/api-standards/data-formats) — convenciones para fechas, dinero y números de teléfono.
# Errores y reintentos (/es/docs/guides/api-standards/errors-and-retries)
Cada respuesta de Mono lleva un código de estado HTTP. Las llamadas exitosas devuelven `2xx`; las fallas devuelven `4xx` (tu solicitud estaba mal) o `5xx` (algo salió mal del lado de Mono). Esta página documenta los códigos de estado que usa Mono, el envelope de error que puedes esperar en cada falla, y la política de reintentos que convierte fallas transitorias en éxito eventual sin duplicar efectos secundarios.
Los límites de tasa aún no se aplican uniformemente en todos los endpoints de Mono, pero `429`
va a empezar a aparecer a medida que los ajustemos. Las reglas de reintento de abajo describen
el contrato contra el que deberías programar hoy, para que tu integración ya se comporte
correctamente cuando los límites entren en vigor.
## Códigos de estado HTTP [#códigos-de-estado-http]
| Estado | Significado | ¿Reintentable? |
| :------------------------ | :----------------------------------------------------------------------------------------------------------------------------------- | :--------------------------- |
| 200 (OK) | La solicitud se completó correctamente. | n/a |
| 201 (Created) | Se creó un nuevo recurso. | n/a |
| 204 (NoContent) | La solicitud tuvo éxito; no hay cuerpo que devolver. | n/a |
| 400 (BadRequest) | JSON malformado, campo faltante o header no conforme. La solicitud en sí está mal. | No |
| 401 (Unauthorized) | Header `Authorization` faltante o inválido. | No (corrige credencial) |
| 403 (Forbidden) | Autenticado, pero sin scope para este recurso. | No (corrige scope) |
| 404 (NotFound) | El recurso no existe. | No |
| 408 (RequestTimeout) | Mono no recibió la solicitud completa a tiempo. | Sí, con backoff |
| 409 (Conflict) | Colisión de clave de idempotencia o conflicto de estado — ver [Claves de idempotencia](/docs/guides/api-standards/idempotency-keys). | No (lógica de aplicación) |
| 422 (UnprocessableEntity) | La estructura de la solicitud es válida, pero el contenido fue rechazado (validación de negocio). | No |
| 429 (TooManyRequests) | Límite de tasa excedido. Respeta el header `Retry-After`. | Sí, después de `Retry-After` |
| 500 (InternalServerError) | Falla inesperada del lado del servidor. | Sí, con backoff |
| 502, 503, 504 | Fallas transitorias upstream/gateway. | Sí, con backoff |
## Envelope de error [#envelope-de-error]
Cada respuesta de falla lleva un envelope JSON estándar para que los clientes puedan parsear errores de forma uniforme:
```json
{
"code": "400 Bad Request",
"errors": [
{
"error_code": "item_not_found",
"message": "Item doesn't exist",
"path": "#/item/id",
"url": "https://api.mono.co/docs#errors"
}
],
"id": "log_7MkWaFqvfosB8fzHhb1Eql",
"message": "Malformed request"
}
```
Campos:
* **`code`** — el estado HTTP y su frase de razón (por ejemplo, `"404 Not Found"`).
* **`errors[]`** — una entrada por cada falla de validación. Útil cuando una sola solicitud tiene varios campos inválidos.
* `error_code` — código estable y legible por máquina para esta falla específica.
* `message` — descripción humana corta.
* `path` — puntero JSON al payload de la solicitud que identifica el campo ofensor.
* `url` — link a documentación extendida para este código de error, cuando esté disponible.
* **`id`** — identificador de solicitud generado por Mono. **Inclúyelo cuando contactes a soporte** — nos permite localizar la solicitud exacta en nuestros logs.
* **`message`** — descripción humana de alto nivel apta para mostrar a operadores (no a usuarios finales).
Las respuestas de `429` y otros límites de tasa usan el mismo envelope con `error_code: "rate_limited"`.
## Cuándo reintentar [#cuándo-reintentar]
Reintenta solo en fallas transitorias, nunca en errores de aplicación:
| Respuesta | ¿Reintentar? | Por qué |
| :----------------- | :---------------------------- | :-------------------------------------------------------------------------------------------- |
| `429` | Sí, después de `Retry-After`. | Transitorio. La solicitud **no** fue procesada; nada cambió del lado de Mono. |
| `408`, `500`–`504` | Sí, con backoff exponencial. | Transitorio. La solicitud puede o no haber sido procesada — la clave de idempotencia protege. |
| `400`, `422` | No. | La solicitud en sí está malformada. Reintentar no cambiará la respuesta. |
| `401`, `403` | No. | Problema de credenciales o de scope. Corrige la auth, luego envía una solicitud nueva. |
| `404`, `409` | No. | Recurso faltante o conflicto de estado. La lógica de la aplicación debe reaccionar. |
Cuatro reglas aplican a cada reintento:
1. **Respeta `Retry-After`.** Si el header está presente (siempre en `429`, a veces en `503`), espera al menos esos segundos antes de reintentar. No reintentes antes.
2. **Usa backoff exponencial con jitter cuando `Retry-After` esté ausente.** Empieza en 1 segundo, dobla en cada falla subsecuente, agrega 0–500 ms de jitter aleatorio, y limita el delay a 30 segundos. El jitter previene el problema de thundering herd donde muchos clientes reintentan en sincronía.
3. **Limita el presupuesto de reintentos.** Hasta 5 reintentos por operación lógica es un valor por defecto razonable. Después de eso, surface la falla — reintentar para siempre solo desplaza el problema.
4. **Reusa la clave de idempotencia.** Cada reintento debe llevar la misma [`X-Idempotency-Key`](/docs/guides/api-standards/idempotency-keys) que el intento original. De lo contrario, un reintento exitoso tras un `5xx` ambiguo podría cobrarle dos veces a un cliente.
Un bucle de reintento mínimo en Python:
```python
import random
import time
import httpx
RETRYABLE_STATUSES = {408, 429, 500, 502, 503, 504}
def request_with_backoff(client, method, url, **kwargs, max_retries=5):
delay = 1.0
for attempt in range(max_retries + 1):
response = client.request(method, url, **kwargs)
if response.status_code not in RETRYABLE_STATUSES:
return response
retry_after = response.headers.get("Retry-After")
wait = float(retry_after) if retry_after else delay + random.uniform(0, 0.5)
time.sleep(min(wait, 30.0))
delay = min(delay * 2, 30.0)
return response # last response, still failing
```
## Diseñar para resiliencia desde el inicio [#diseñar-para-resiliencia-desde-el-inicio]
Incluso antes de que los límites de tasa se vuelvan estrictos, tres hábitos mantienen las cargas en ráfaga fuera de problemas:
* **Distribuye el trabajo en ráfaga.** Trabajos por lotes que disparan miles de llamadas — corridas de nómina, conciliación de fin de día, emisión masiva de tarjetas — deberían ser ritmados del lado del cliente en lugar de dispararse en paralelo. Unas pocas solicitudes por segundo sostenidas le ganan a mil al tiempo seguidas de una ventana de recuperación.
* **Cachea lecturas.** Los datos de referencia (catálogos, esquemas de tarifas, lookups de capacidades) rara vez cambian dentro de un solo ciclo de solicitud. Cachea la respuesta y solo pega a Mono cuando falla el cache.
* **Separa rutas síncronas de las asíncronas.** Una llamada de checkout cara a un usuario no debería compartir su presupuesto con una exportación nocturna. Usa API keys distintas para cargas distintas cuando sea posible, para que un trabajo por lotes desbocado no agote el límite que necesita un cliente real.
## Errores comunes [#errores-comunes]
| Error | Síntoma | Solución |
| :------------------------------------------------------- | :------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- |
| Reintentar de inmediato en `429`. | `429` repetidos y mayor tiempo total de recuperación. | Espera al menos los segundos de `Retry-After` antes del siguiente intento. |
| Ignorar el header `Retry-After`. | Los reintentos caen antes de que el límite se haya reseteado. | Trata `Retry-After` como autoritativo cuando esté presente. |
| Reintentar sin clave de idempotencia. | Transferencias duplicadas cuando el reintento eventualmente acierta. | Genera la clave una vez, envíala en cada intento. |
| Reintentar `400`/`422` como si fueran transitorios. | Intentos desperdiciados; el mismo error vuelve cada vez. | Solo reintenta en `408`, `429` y `5xx`. |
| Sin tope de reintentos. | Workers atorados, colas bloqueadas, fatiga de alertas. | Limita a un número pequeño de reintentos (5 es un default razonable) y surface arriba. |
| Compartir una llave entre servicios y clases de tráfico. | Un trabajo batch ruidoso limita por tasa el tráfico de checkout cara al usuario. | Emite API keys separadas para cargas separadas. |
| Omitir el campo `id` al reportar un problema. | Soporte no puede encontrar la solicitud en logs. | Siempre incluye el `id` del envelope de error cuando escales. |
## Siguientes pasos [#siguientes-pasos]
* [Claves de idempotencia](/docs/guides/api-standards/idempotency-keys) — requeridas en cada escritura reintentada.
* [Autenticación](/docs/guides/api-standards/authentication) — manejo de credenciales para los casos `401` y `403`.
* [Webhooks](/docs/guides/api-standards/webhooks) — la política de reintentos de Mono para los eventos que envía a tu sistema.
# Claves de idempotencia (/es/docs/guides/api-standards/idempotency-keys)
Una **clave de idempotencia** es una etiqueta única que tu sistema adjunta a una solicitud de escritura para que Mono pueda reconocer un reintento como la misma operación en vez de una nueva. Funciona como un número de orden en un formulario en papel: si envías el mismo formulario dos veces por error, el funcionario receptor ve el número de orden y solo lo procesa una vez. Sin una clave de idempotencia, un problema de red en una solicitud de transferencia podría crear silenciosamente dos transferencias.
Usa una clave de idempotencia en cada solicitud de escritura que mueva dinero o cree una
tarjeta, transferencia, enlace de recaudo o dispersión. El costo de olvidarla es una
transacción duplicada; el costo de recordarla es un header extra.
## Antes de empezar [#antes-de-empezar]
Esta página asume que has leído [Autenticación](/docs/guides/api-standards/authentication) y que haces solicitudes `POST`, `PUT` o `PATCH` contra la API de Mono. La idempotencia no aplica a solicitudes `GET` — esas ya son seguras de reintentar por definición.
## Cuándo enviarla [#cuándo-enviarla]
Envía una clave de idempotencia cuando un reintento de la misma solicitud no debe producir un segundo efecto secundario. En la práctica, esto es **cada solicitud de escritura a Mono**. Ejemplos:
* Crear una transferencia bancaria.
* Crear un enlace de recaudo.
* Crear una tarjeta.
* Disparar una dispersión.
* Enviar una transferencia saliente Bre-B.
También puedes enviar una en solicitudes que teóricamente son seguras de reintentar (como updates que convergen al mismo estado). Nunca está mal incluir el header.
## Cómo enviarla [#cómo-enviarla]
Agrega el header `X-Idempotency-Key` a tu solicitud. El valor es una cadena que tú generas, única por operación lógica, de hasta 255 caracteres.
```bash
curl https://api.cuentamono.com/v1/bank_transfers \
-H "Authorization: Bearer $MONO_API_KEY" \
-H "X-Idempotency-Key: payroll-2026-05-06-emp-1042" \
-H "Content-Type: application/json" \
-d '{
"amount": 1500000,
"currency": "COP",
"routing": "turbo",
"destination": { ... }
}'
```
## Cómo elegir una buena clave [#cómo-elegir-una-buena-clave]
Una buena clave de idempotencia es **determinística para una operación lógica dada** y **opaca para humanos**. Formatos recomendados:
* Un UUID v4 generado una vez por operación lógica y almacenado junto a la operación en tu base de datos. Persiste la clave antes del primer intento para que los reintentos la reusen.
* Un compuesto de identificadores que tu sistema ya controla — por ejemplo `transfer:${order_id}` para una transferencia por orden. Evita timestamps a menos que los controles con precisión; los relojes derivan entre reintentos.
Qué evitar:
* **Reusar la misma clave para operaciones distintas.** Mono tratará la segunda como un duplicado y responderá con la respuesta cacheada de la primera, lo cual casi seguramente no es lo que quieres.
* **Generar una clave nueva en cada reintento.** Eso anula el propósito completo — los reintentos se vuelven operaciones nuevas.
* **Incluir secretos o PII en la clave.** Las claves aparecen en logs y dashboards.
## Qué hace Mono con ella [#qué-hace-mono-con-ella]
Cuando Mono recibe una solicitud con una clave de idempotencia, busca la clave en su almacén de corto plazo:
1. **Vista por primera vez** — Mono ejecuta la solicitud, almacena la respuesta y la devuelve.
2. **Ya vista, mismo payload** — Mono devuelve la respuesta almacenada sin ejecutar la solicitud de nuevo. Tu reintento es seguro.
3. **Ya vista, payload distinto** — Mono rechaza la solicitud con `409 Conflict`. Esto te protege de un bug donde dos operaciones no relacionadas comparten una clave por accidente.
Las respuestas cacheadas se retienen por **24 horas** desde la primera solicitud. Después de eso la clave se olvida y una solicitud nueva con la misma clave inicia una operación nueva.
## Cuándo tienen sentido los reintentos [#cuándo-tienen-sentido-los-reintentos]
Reintenta solo en fallas transitorias — timeouts de red, `502`, `503`, `504`. No reintentes en `400`, `401`, `403`, `404` o `422`; esos indican que la solicitud está mal, y reintentar no cambiará la respuesta.
Usa backoff exponencial con jitter, y limita el presupuesto de reintentos. Un patrón común: 3 reintentos, empezando en 1s, doblando cada vez, con 0–500ms de jitter.
## Errores comunes [#errores-comunes]
| Error | Síntoma | Solución |
| :---------------------------------------------------------- | :-------------------------------------------------------- | :---------------------------------------------------------- |
| Generar una clave nueva en cada reintento | Transferencias duplicadas, cobros dobles. | Genera la clave una vez, almacénala, reúsala al reintentar. |
| Reusar una clave entre operaciones lógicas | La segunda operación devuelve la respuesta de la primera. | Haz la clave única por operación. |
| Enviar la clave solo en reintentos, no en el primer intento | El primer intento queda sin protección. | Envía siempre la clave en el primer intento. |
| Confiar en la idempotencia para reintentos de `GET` | Sin efecto. | `GET` ya es idempotente; el header se ignora. |
## Siguientes pasos [#siguientes-pasos]
* [Errores y reintentos](/docs/guides/api-standards/errors-and-retries) — cómo Mono señala fallas reintentables vs. terminales y la política estándar de backoff.
* [Autenticación](/docs/guides/api-standards/authentication) — manejo y rotación de credenciales.
* [Paginación y ordenamiento](/docs/guides/api-standards/pagination-and-sorting) — convenciones de cursor para endpoints de lectura.
# Formatos de datos (/es/docs/guides/api-standards/data-formats)
## Fechas y horas [#fechas-y-horas]
Para nuestra API usamos ISO 8601. ISO 8601 es un estándar para representar fechas y horas en un formato legible por máquina. Especifica un conjunto de representaciones para fechas, horas e intervalos, incluyendo formatos estándar para representar fechas y horas con o sin zona horaria.
El formato consta de una serie de campos separados por puntuación, con los campos más significativos al inicio. El formato básico para representar una fecha y hora es:
`YYYY-MM-DDTHH:MM:SSZ`
Donde:
* `YYYY` es el año, representado en cuatro dígitos
* `MM` es el mes, representado en dos dígitos (01-12)
* `DD` es el día del mes, representado en dos dígitos (01-31)
* `T` es un separador que indica el inicio de la porción de hora del valor fecha-hora
* `HH` es la hora, representada en dos dígitos (00-23)
* `MM` es el minuto, representado en dos dígitos (00-59)
* `SS` es el segundo, representado en dos dígitos (00-59)
* `Z` es la zona horaria
Por ejemplo, la fecha y hora `2022-12-29T12:34:56Z` representaría el 29 de diciembre de 2022 a las 12:34:56 (12:34 PM).
La API usa UTC, o tiempo universal coordinado, para el campo de fecha/hora. UTC es una referencia de tiempo estandarizada que es la misma en todo el mundo. Es el estándar primario por el cual el mundo regula relojes y tiempo. UTC está basado en relojes atómicos, que son relojes altamente precisos que miden las vibraciones de los átomos para determinar el paso del tiempo. Como UTC está basado en relojes atómicos, es extremadamente estable y preciso.
## Valores monetarios [#valores-monetarios]
Para nuestra API, todo valor monetario debe estar en ISO 4217, que es un estándar internacional para representar monedas. Especifica un código de tres letras para cada moneda, así como la unidad menor de la moneda. Por ejemplo, el código para dólares estadounidenses es "USD".
Además, el campo `amount` para dinero se representa en centavos en lugar de la unidad base de la moneda. Esto permite una representación más precisa del monto, así como un cálculo y almacenamiento más sencillos de los valores monetarios. Por ejemplo, si quieres representar $10.50 en una API, almacenarías el monto como 1050 (en lugar de 10.50).
Esto se representaría así:
```json
{
"amount": 10000,
"currency": "COP"
}
```
Esto representa un monto de 10000 centavos, o 100 pesos colombianos. La moneda se especifica usando el código ISO 4217 para pesos colombianos, que es "COP".
## Números telefónicos [#números-telefónicos]
Tanto los números telefónicos nacionales como los internacionales se representan con E.164.
E.164 es un estándar para el formato de números telefónicos. Especifica un formato estandarizado que incluye un código de país, un código de destino nacional y un número de suscriptor local. El formato es el siguiente:
```
+[country code][national destination code][local subscriber number]
```
Por ejemplo, un número telefónico en Colombia podría tener el formato `+57 300 555 0000`, donde `+57` es el código de país de Colombia, "300" es el código de destino nacional, y "555 0000" es el número de suscriptor local.
# Paginación y ordenamiento (/es/docs/guides/api-standards/pagination-and-sorting)
## Paginación [#paginación]
La paginación es una técnica que se usa para dividir muchos resultados en fragmentos más pequeños, o páginas, para una navegación más sencilla. Al usar nuestra API de Mono, el cliente puede especificar dos parámetros: `page_size` y `page_number` en los endpoints que listan objetos. Estos parámetros pueden pasarse como query parameters en la solicitud a la API.
El parámetro `page_size` determina la cantidad de resultados que se deben devolver en cada página. Por ejemplo, si `page_size` se ajusta en 10, la API devolverá 10 resultados por página.
El parámetro `page_number` determina qué página de resultados se debe devolver. Por ejemplo, si `page_number` se ajusta en 2, la API devolverá la segunda página de resultados.
Cada lista de elementos devuelta por la API debe incluir un objeto de paginación con la siguiente estructura:
```json
{
"items": [],
"pagination": {
"page_number": 1,
"page_size": 10,
"total_items": 100,
"total_pages": 10
}
}
```
El campo `items` contiene la lista de objetos devueltos por la API para la página actual. El objeto `pagination` contiene metadatos sobre los resultados, incluyendo el `page_number` y `page_size` actuales, así como `total_items` y `total_pages`.
El campo `total_items` indica la cantidad total de objetos en el conjunto de resultados, y `total_pages` indica la cantidad total de páginas necesarias para mostrar todos los resultados. El cliente puede usar esta información para construir controles de UI que naveguen entre las páginas de resultados.
## Ordenamiento [#ordenamiento]
El ordenamiento es una técnica que se usa para reorganizar los elementos de una lista en un orden particular, ascendente o descendente. Al usar una API de ordenamiento, el cliente puede especificar un parámetro de sort en un query string.
El parámetro de sort debe incluir dos campos: `type` y `field`. El campo `type` determina el orden y puede tener los valores `asc` para ascendente o `desc` para descendente. El campo `field` especifica el campo por el cual se deben ordenar los elementos; los valores para este campo están documentados en cada endpoint que soporta estos parámetros de ordenamiento.
Por ejemplo, el siguiente query string ordenaría los elementos en orden ascendente por el campo id:
```
?sort[type]=asc&sort[field]=id
```
# Webhooks (/es/docs/guides/api-standards/webhooks)
Un webhook es una notificación automática que Mono envía a tu sistema en el momento en que algo ocurre — una transferencia se aprueba, una tarjeta se carga, un recaudo se acredita. En vez de preguntarle a Mono repetidamente si algo cambió, tu servidor recibe una sola solicitud HTTPS apenas ocurre el evento.
Estas convenciones aplican a cada producto de Mono: Banking, Core y Bre-B Participant. Cada producto tiene su propio catálogo de eventos; esta página documenta la mecánica compartida que los rige a todos.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Un endpoint HTTPS accesible públicamente (no `localhost`, no `127.0.0.1`).
* La capacidad de verificar una firma HMAC-SHA256 en cada solicitud entrante.
* Un handler de eventos idempotente — Mono reintenta en fallas, así que el mismo evento puede llegar más de una vez.
## Envelope [#envelope]
Cada cuerpo de solicitud de webhook sigue el mismo envelope JSON:
```json
{
"event": {
"data": { ... },
"type": "event_type_or_name"
},
"timestamp": "2022-12-29T15:42:08.325158Z"
}
```
* `event.type` — el nombre estable del evento (p. ej. `bank_transfer_approved`).
* `event.data` — el payload del evento; su esquema depende del tipo de evento.
* `timestamp` — timestamp ISO 8601 UTC de cuándo se generó el evento.
## Firma [#firma]
Cada solicitud está firmada con un **webhook secret** que obtienes desde el Dashboard. La firma y el timestamp viajan juntos en un header personalizado llamado `Mono-Signature`:
```
Mono-Signature: t=1672328528,v1=662255ca79c4b21914894d32da10189a1482cd0fee56b2765a8b472662ce55ac
```
* `t` — timestamp Unix. Úsalo para protegerte contra [ataques de repetición](https://en.wikipedia.org/wiki/Replay_attack) rechazando solicitudes cuyo timestamp caiga fuera de tu ventana de tolerancia.
* `v1` — la firma HMAC-SHA256. Mono actualmente solo usa la versión `v1`.
## Verificación de firma [#verificación-de-firma]
### Paso 1: extraer timestamp y firma [#paso-1-extraer-timestamp-y-firma]
Divide `Mono-Signature` por `,` para obtener:
```
t=1672328528
v1=662255ca79c4b21914894d32da10189a1482cd0fee56b2765a8b472662ce55ac
```
### Paso 2: construir el string del payload firmado [#paso-2-construir-el-string-del-payload-firmado]
Concatena el timestamp, un `.` literal, y el cuerpo crudo de la solicitud:
```
.
```
### Paso 3: calcular la firma esperada [#paso-3-calcular-la-firma-esperada]
Usa HMAC-SHA256 con tu webhook secret como llave y el string del payload firmado como mensaje:
```ruby
require 'openssl'
timestamp = 1672774221
raw_payload = '{"respose_body": "example"}'
signed_payload = "#{timestamp}.#{raw_payload}"
secret = "whsec_example"
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), secret, signed_payload)
# => 652fdc1742906b4b23ce2a5f4ac417b52c264fea0207920a5e76330a87239924
```
### Paso 4: comparar y validar [#paso-4-comparar-y-validar]
Compara el HMAC calculado contra el valor `v1` usando una **comparación de tiempo constante** para prevenir ataques de temporización. Luego verifica que la diferencia entre el tiempo actual y `t` esté dentro de tu ventana aceptable (típicamente 5 minutos).
## Política de reintentos [#política-de-reintentos]
Mono reintenta solicitudes fallidas (cualquier respuesta que no sea `2xx`) hasta diez veces con backoff exponencial. Tras el décimo intento, el evento no se reintenta más.
| Intento | Delay desde el anterior |
| ------- | ----------------------- |
| 1 | inmediato |
| 2 | 30 segundos |
| 3 | 1.5 minutos |
| 4 | 3.5 minutos |
| 5 | 7.5 minutos |
| 6 | 15.5 minutos |
| 7 | 31.5 minutos |
| 8 | 1.06 horas |
| 9 | 2.13 horas |
| 10 | 4.26 horas |
Los diez intentos se completan en aproximadamente 8.4 horas.
## Campos de dinero [#campos-de-dinero]
Los montos monetarios siempre se representan en la unidad menor de la moneda (centavos para COP y USD):
* `240000` = 2,400.00
* `160` = 1.60
La excepción es `fx_rates`, que lleva la tasa de cambio decimal directamente: `"4002.24"` = 4002.24.
## Restricciones de URL [#restricciones-de-url]
Tu endpoint de webhook debe:
* Empezar con `http` o `https`.
* No usar `localhost` ni `127.0.0.1` como host.
* Ser una URL HTTP/HTTPS válida y alcanzable.
## Siguientes pasos [#siguientes-pasos]
* [Webhooks de Banking](/docs/guides/banking/webhooks) — catálogo de eventos para transferencias bancarias y enlaces de recaudo.
* [Webhooks de Core](/docs/guides/core/webhooks) — catálogo de eventos para transacciones de tarjeta y transacciones de cuenta de ledger.
* [Webhooks de Bre-B Participant](/docs/guides/breb-participant/webhooks) — catálogo de eventos para transferencias salientes y recaudos Bre-B.
# Banking API (/es/docs/api-reference/banking)
The Banking API enables banking operations including authentication, account management, bank transfers, collection links, card issuing and management, and spending controls.
## Endpoints [#endpoints]
Obtain, inspect, and revoke OAuth 2.0 access tokens
Manage bank accounts and balances
Create and manage bank transfers
List the banks you can send bank transfers to
Generate and manage collection links for payments
Create and track collection intents
Issue banking cards and manage their prepaid balance
Manage cards, cardholders, and digital wallet provisioning
Set up rules and controls for card spending
## Webhooks [#webhooks]
Notifications about bank transfer approval, rejection, routing, and account credit events
Notifications about bank transfer batch lifecycle events
Notifications about collection link payment intent events
Notifications about card activation and tokenization events
Notifications about card transaction authorization, adjustment, and reversal events
# Core API (/es/docs/api-reference/core)
The Core API provides essential financial infrastructure functionality including ledger management, payins, payouts, card operations, and spending controls.
## Endpoints [#endpoints]
Obtain, inspect, and revoke OAuth 2.0 access tokens
Manage account holders, ledger accounts, and financial transactions
List the programs your ledger accounts run under
Manage the templates that configure collections and read the resulting payins
Create and manage payouts with target resolution
List the banks you can send payouts to
List the fees charged on your accounts and manage the policies that price them
Issue cards against a ledger account
Manage cards, cardholders, and digital wallet provisioning
Set up rules and controls for card spending
## Webhooks [#webhooks]
Notifications about card activation and tokenization events
Notifications about card transaction authorization, adjustment, and reversal events
Notifications about ledger account transaction events
# Bre-B Participant API (/es/docs/api-reference/breb-participant)
The Bre-B Participant API enables you to create and manage outgoing transfers, resolve transfer targets, and receive real-time webhook notifications about transfer status changes.
Learn how the Bre-B system works and how it integrates with Mono at
[breb.app](https://breb.app/).
## Authentication [#authentication]
This API uses OAuth 2.0 with Client Credentials flow. Request tokens from `/api/v1/oauth/token`.
### Security Schemes [#security-schemes]
The API defines two security schemes that you'll see referenced in the endpoint documentation:
* **oauth**: Full access scheme for operations that create, update, or delete resources.
* **oauth\_readonly**: Read-only access scheme for operations that only retrieve data.
### Scopes [#scopes]
| Scope | Scheme | Description |
| ----------------------------- | ---------------- | ------------------------------------------------- |
| `tenant_accounts` | `oauth` | Allow all permissions for tenant accounts |
| `tenant_accounts:readonly` | `oauth_readonly` | Permissions to read tenant accounts |
| `outgoing_transfers` | `oauth` | Allow all permissions for outgoing transfers |
| `outgoing_transfers:readonly` | `oauth_readonly` | Permissions to read outgoing transfers |
| `target_resolutions` | `oauth` | Permission to read and resolve target resolutions |
| `target_resolutions:readonly` | `oauth_readonly` | Permission to read target resolutions |
| `collections` | `oauth` | Allow all permissions for collections |
| `collections:readonly` | `oauth_readonly` | Permissions to read collections |
## Endpoints [#endpoints]
OAuth 2.0 authentication with Client Credentials flow
Retrieve and list tenant accounts associated with your tenant
Create and manage outgoing transfers through the Bre-B system
Create and manage collections for receiving payments through Bre-B
Test payment flows in the sandbox environment
## Webhooks [#webhooks]
Notifications about transfer target resolution events
Notifications about outgoing transfer status changes
Notifications about outgoing transfer batch events
Notifications about collection lifecycle and payment attempts
# Vista general del ciclo de vida (/es/docs/guides/banking/lifecycle)
# Ciclo de vida de transferencia bancaria (/es/docs/guides/banking/lifecycle/bank-transfer-states)
# Transferencias bancarias (/es/docs/guides/banking/concepts/transfers)
Una transferencia bancaria en Mono es como enviar un paquete de un lado a otro, pero con dinero. Es una transacción financiera en la que los fondos se mueven de una cuenta bancaria a otra.
Las transferencias bancarias se usan comúnmente para pagar facturas, transferir dinero entre cuentas, hacer pagos de nómina o enviar dinero a otros. Se pueden hacer entre cuentas del mismo banco o de distintos bancos.
Con Mono puedes crear transferencias bancarias por API, subiendo un archivo Excel desde el Mono Dashboard o llenando un formulario manualmente.
Actualmente solo soportamos transferencias bancarias dentro de la red bancaria colombiana.
## Routing [#routing]
Mono soporta tres tipos de transferencias bancarias, cada uno con comportamientos, ventajas y desventajas diferentes. Para dar una vista de alto nivel, el siguiente diagrama ilustra cómo se mueve el dinero según el routing de la transferencia bancaria:

### Turbo-transferencias [#turbo-transferencias]
Una Turbo-transferencia es una transferencia bancaria en tiempo real desde tu cuenta Mono hacia la cuenta de cualquier persona abierta en los siguientes bancos:
* Bancolombia
* Nequi
* Davivienda
* Daviplata
* Banco AV Villas
* Banco de Bogota
* Bancolombia ahorro a la mano
* Movii
* Dale!
Puedes iniciar una transferencia bancaria `turbo` usando el endpoint de creación de transferencia bancaria agregando `turbo` en el campo `routing`.
Una vez inicias un turbo, debes esperar una respuesta por webhook que te notifique si la transferencia bancaria fue acreditada exitosamente en la cuenta del beneficiario.
Este routing de transferencia bancaria opera sobre los nuevos rails de ACH Colombia, y se espera que su aceptación aumente con el tiempo. Puede haber casos donde una transferencia bancaria `turbo` no se envíe exitosamente porque la cuenta del beneficiario aún no soporta este tipo de transacción. (Ver más abajo intentos de fallback)
### Transfiya [#transfiya]
[Transfiya](https://www.transfiya.com.co/home) es un servicio de transferencias bancarias desarrollado en Colombia que permite a los usuarios enviar y recibir dinero en tiempo real entre distintas instituciones financieras usando solo el número de teléfono del receptor. Este servicio es parte de la Red ACH.
#### Cómo hacer una transferencia Transfiya en Mono [#cómo-hacer-una-transferencia-transfiya-en-mono]
Puedes iniciar una transferencia Transfiya usando el endpoint de creación de transferencia bancaria y agregando `transfiya` en el campo `routing`. Al hacer una transferencia transfiya no necesitas incluir el esquema de cuenta bancaria del beneficiario porque la transferencia se hará usando el número de teléfono del beneficiario.
Una vez inicies una transferencia Transfiya, el beneficiario recibirá un mensaje de texto notificando la transferencia, y tendrá 12 horas para aceptar la cuenta bancaria que prefiera.
#### Diferencia entre transferencias Turbo y Transfiya [#diferencia-entre-transferencias-turbo-y-transfiya]
Tanto las transferencias Turbo como Transfiya son transferencias en tiempo real, sin embargo hay una diferencia entre ellas.
Las transferencias Turbo se hacen en tiempo real a una cuenta bancaria, y el beneficiario no tiene que aceptar las transferencias; simplemente recibe el dinero al instante. Por otro lado, las transferencias Transfiya se hacen en tiempo real usando el número de teléfono del beneficiario. El beneficiario recibirá un mensaje y debe aceptar la transferencia en el banco que prefiera.
### ACH [#ach]
ACH Colombia y ACH CENIT son cámaras de compensación que manejan la interoperabilidad de los bancos colombianos, permitiendo la comunicación y el settlement entre ellos. Cuando creas una transferencia bancaria seleccionando `ACH` en el campo `routing`, el dinero se debitará de tu cuenta y se enviará al beneficiario por los ciclos de ACH Colombia o ACH CENIT, que no son instantáneos. Si la transferencia bancaria va por ACH Colombia o ACH CENIT depende del banco de la cuenta destino. La mayoría de las transacciones irán por ACH Colombia, excepto algunos bancos listados abajo que usan ACH CENIT.
#### Velocidad de transacción de ACH Colombia [#velocidad-de-transacción-de-ach-colombia]
Las transacciones ACH en Colombia no se hacen en tiempo real. Pueden tomar unas horas o hasta 1 día hábil para ser acreditadas en la cuenta destino, e incluso más para recibir la confirmación.
En Mono hemos desarrollado un sistema de confirmación implícita para transferencias bancarias ACH. Este sistema funciona así: si no recibimos respuesta de rechazo para una transferencia dentro de un cierto periodo, la transferencia se considera aprobada.
**Ciclos ACH sin confirmación implícita:**
| Hora de transacción | Dinero acreditado | Respuesta implícita |
| ---------------------------- | --------------------------------------- | ------------------------------------------ |
| Desde 00:00 y antes de 08:28 | 10:20 a 11:00 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 08:28 y antes de 10:58 | 12:45 a 13:00 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 10:58 y antes de 12:43 | 15:15 a 16:00 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 12:43 y antes de 15:38 | 17:10 a 18:00 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 15:38 al día siguiente | Siguiente día hábil entre 10:20 y 11:00 | Día hábil subsiguiente entre 15:10 y 15:45 |
**Ciclos ACH con confirmación implícita:**
| Hora de transacción | Dinero acreditado | Respuesta implícita |
| ------------------------ | --------------------------------------- | ---------------------------------- |
| Desde 00:00 a 8:25 am | 10:20 a 11:00 | 8:00 pm del mismo día |
| Desde 8:26 am a 10:25 am | 12:45 a 13:00 | 10:00 pm del mismo día |
| Desde 10:26 am a 1:25 pm | 15:15 a 16:00 | Siguiente día hábil a las 11:00 am |
| Desde 1:26 pm a 3:40 pm | 17:10 a 18:00 | Siguiente día hábil a la 1:00 pm |
| Desde 3:41 am a 11:59 pm | Siguiente día hábil entre 10:20 y 11:00 | Siguiente día hábil a las 8:00 pm |
#### Velocidad de transacción de ACH CENIT [#velocidad-de-transacción-de-ach-cenit]
**Ciclos CENIT sin confirmación implícita:**
| Hora de transacción | Dinero acreditado | Respuesta implícita |
| ---------------------------- | --------------------------------------- | ------------------------------------------ |
| Desde 00:00 y antes de 09:58 | 10:30 a 11:00 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 09:58 y antes de 12:28 | 13:00 a 13:30 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 12:28 y antes de 14:28 | 15:00 a 15:45 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 14:28 y antes de 15:43 | 17:00 a 18:00 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 15:43 al día siguiente | Siguiente día hábil entre 10:30 y 11:00 | Día hábil subsiguiente entre 15:10 y 15:45 |
**Ciclos CENIT con confirmación implícita:**
| Hora de transacción | Dinero acreditado | Respuesta implícita |
| ---------------------------- | --------------------------------------- | --------------------------------------- |
| Desde 00:00 y antes de 09:55 | 10:30 a 11:00 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 09:55 y antes de 12:25 | 13:00 a 13:30 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 12:26 y antes de 14:25 | 15:00 a 15:45 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 14:26 y antes de 15:40 | 17:00 a 18:00 | Siguiente día hábil entre 15:10 y 15:45 |
| Desde 15:41 a 11:59 pm | Siguiente día hábil entre 10:30 y 11:00 | Siguiente día hábil a las 10:00 pm |
Existe la posibilidad, aunque muy rara, de que después de darte una respuesta exitosa, la
transacción sea rechazada más adelante. En esos casos te notificaremos inmediatamente vía
webhook y también te enviaremos un email.
**Bancos que operan a través de ACH CENIT:**
* BANCO FALABELLA
* BANCO FINANDINA
* BANCO GNB SUDAMERIS
* BANCO MUNDO MUJER
* BANCO PROCREDIT
* BANCO SANTANDER DE NEGOCIOS
* BBVA
* CFA FINANCIERA ANTIOQUIA
* COOFINEP
* BANCOOMEVA
* COTRAFA ENTIDAD FINANCIERA
* FINANCIERA JURISCOOP
* J.P. MORGAN COLOMBIA
* MIBANCO
## Intentos de fallback [#intentos-de-fallback]
Considerando que las transferencias bancarias Transfiya no son adoptadas plenamente por todas las cuentas en Colombia, puedes encontrarte con casos donde la transacción es rechazada. Para resolver esto, implementamos intentos de fallback que reintentan automáticamente la transacción usando rutas alternativas.
Para usar estos intentos de fallback en las solicitudes a la API, puedes usar el campo `fallback_routing` y configurarlo con el valor correspondiente.
### Turbo a Transfiya [#turbo-a-transfiya]
Si una transferencia Turbo es rechazada y el fallback routing es Transfiya, enviaremos automáticamente la transferencia por esta vía. Esto significa que el beneficiario recibirá un mensaje notificando que puede recibir la transferencia y qué necesita hacer para recibir el dinero al instante. La transferencia de fallback por transfiya se hará usando el número de teléfono del beneficiario, así que asegúrate de incluirlo en la petición.
### Turbo a ACH [#turbo-a-ach]
Si una transferencia bancaria Turbo es rechazada, y el fallback routing es ACH, intentaremos automáticamente hacer una transferencia bancaria ACH. Esto significa que si una transferencia bancaria Turbo falla, redirigiremos la transacción usando los rails de ACH.
### Transfiya a ACH [#transfiya-a-ach]
Si una transferencia Transfiya es rechazada, y el fallback routing es ACH, enviaremos automáticamente la transferencia por esta vía. Esto significa que si la transferencia transfiya falla, redirigiremos la transacción usando los rails de ACH.
### Sin fallback [#sin-fallback]
También puedes configurar transferencias para que no hagan nada en caso de que fallen.
## Estados de transferencia bancaria [#estados-de-transferencia-bancaria]
Las transferencias bancarias pueden tener los siguientes estados:
| Estado | Descripción |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `created` | Indica que la transferencia fue creada en nuestro sistema, pero aún no fue enviada al banco para procesamiento. |
| `in_progress` | Indica que la transferencia está siendo procesada por el banco. Esto incluye pasos como verificar la información de la cuenta, transferir los fondos entre cuentas y actualizar los saldos de las cuentas. |
| `approved` | Indica que la transferencia fue aprobada por el banco y se ejecutó exitosamente. Los fondos se transfirieron de la cuenta origen a la cuenta destino. |
| `declined` | Indica que la transferencia fue rechazada por el banco y no se ejecutará. Esto puede ocurrir por fondos insuficientes, información de cuenta inválida o restricciones regulatorias. Estas razones se almacenan en el campo `declination_reason`. |
| `cancelled` | Indica que la transferencia fue cancelada por un usuario antes de ser enviada al banco. |
| `duplicated` | Indica que la transferencia ya fue recibida en nuestro sistema, y el registro repetido no será considerado. Esto puede ser porque la transferencia fue enviada varias veces con el mismo campo `entity_id`. |
## Lote de transferencias bancarias [#lote-de-transferencias-bancarias]
Un lote es un grupo de transferencias bancarias que se procesan juntas al mismo tiempo.
Los lotes de transferencias bancarias son útiles en varias situaciones, como pagos de nómina a múltiples empleados, transferencias entre varias cuentas o pagos regulares al mismo receptor. Los lotes de transferencias bancarias ahorran tiempo y esfuerzo al permitir hacer múltiples transferencias a la vez en lugar de iniciar cada una por separado.
En Mono, cada envío de transferencias creará un lote, incluso cuando creas transferencias individualmente.
### Estados del lote [#estados-del-lote]
| Estado | Descripción |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `created` | Indica que el lote fue recibido y aún no está autorizado o procesado. Cada transferencia bancaria estará en estado `created`. |
| `pending_otp` | Indica que el lote necesita ser autorizado por un usuario Administrador. |
| `verified_otp` | Indica que el lote fue autorizado y está en preparación para ser procesado. |
| `canceled` | Indica que el lote fue cancelado por un usuario Administrador durante la preparación. Cancelar un lote cancela sus transferencias. Cada transferencia bancaria estará en estado `canceled`. Enviará un evento de webhook llamado `batch_canceled`. |
| `processing_transactions` | Indica que el lote está en proceso de aplicar sus transferencias. Cada transferencia bancaria estará en estado `in_progress`. Enviará un evento de webhook llamado `batch_sent`. |
| `partially_approved` | Indica que algunas de las transferencias del lote fueron aprobadas por los bancos destino. Algunas transferencias se marcarán como `approved` o `declined`. |
| `declined` | Indica que todas las transferencias del lote fueron rechazadas. Las transferencias también se marcarán como `declined`. |
| `approved` | Indica que todas las transferencias del lote fueron aprobadas por los bancos destino. Las transferencias también se marcarán como `approved`. |
| `duplicated` | Indica que todas las transferencias del lote fueron marcadas como duplicadas porque fueron recibidas antes en nuestro sistema. Cada transferencia bancaria estará en estado `duplicated`. Enviará un evento de webhook llamado `batch_duplicated`. |
# Enlaces de recaudo (/es/docs/guides/banking/concepts/collection-links)
Los enlaces de recaudo son un método simple y conveniente para solicitar y procesar pagos rápidamente a través de PSE. Tus clientes o proveedores pueden pagarte desde cualquier banco colombiano donde esté activado PSE.
## Tipos de enlaces de recaudo [#tipos-de-enlaces-de-recaudo]
En Mono hay dos tipos de enlaces de recaudo:
### Enlaces de recaudo de Mono [#enlaces-de-recaudo-de-mono]
Este tipo de enlace redirige al pagador al formulario de enlace de pago de Mono, donde puede hacer el pago. Tiene los siguientes campos personalizables que necesitas definir:
* Monto
* Nombre del enlace
* Tipo de uso
* Información del pagador:
* Documento
* Email
* Nombre del pagador
* Teléfono
* Notas
* Referencia
* URL de redirección
* Tipo de enlace (reutilizable o de un solo uso)
Este tipo de enlace se puede generar vía API o desde la app de Mono.
#### Generación vía API [#generación-vía-api]
Una vez completes el proceso de autenticación, puedes usar el endpoint de intención de recaudo para generar los enlaces de recaudo necesarios. La respuesta de la API te dará el enlace de pago, que vas a compartir con el pagador.
### Enlaces de recaudo personalizados [#enlaces-de-recaudo-personalizados]
La diferencia entre los enlaces de recaudo de Mono y los enlaces de recaudo personalizados es que con los personalizados tú defines el front end; tú decides cómo va a verse tu formulario de enlace de pago y no se redirige al enlace de Mono.
Este tipo de enlace se puede generar vía API o desde la app de Mono.
#### Generación vía API [#generación-vía-api-1]
Creación de enlaces de recaudo personalizados: este tipo de enlace solo se puede generar vía API. Después de completar el proceso de autenticación, puedes usar el endpoint de enlace de recaudo.
## ¿Cómo puedo recibir notificación de eventos del enlace de pago? [#cómo-puedo-recibir-notificación-de-eventos-del-enlace-de-pago]
Independientemente del método que uses para crear tus enlaces, puedes configurar webhooks para recibir eventos del enlace de pago de dos maneras:
| Evento | Descripción |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Collection Intent Credited** | Si el pago hecho usando el enlace de pago es exitoso, recibirás un evento con la información del pagador, datos de referencia, el tiempo en que se hizo, el monto y el estado del enlace. |
| **Collection Intent Confirmed** | Si el pago hecho usando el enlace de pago es exitoso, cuando el pago está confirmado por nuestro proveedor pero aún no ha sido acreditado en tu cuenta, recibirás este evento con la información del pagador, datos de referencia, el tiempo en que se hizo, el monto y el estado del enlace. |
| **Collection Intent Failed** | En caso de que el pago no sea exitoso, recibirás información sobre el enlace del pagador, referencia, el tiempo del intento y el estado rechazado. |
# Cuenta bancaria (/es/docs/guides/banking/concepts/accounts)
Una cuenta bancaria es el lugar donde puedes manejar y mantener el dinero de tu organización.
Tu organización puede manejar varias cuentas, cada una con su propio saldo y estado independientes según tus objetivos y proyectos. Cuando abres una cuenta en Mono, también estás abriendo un depósito electrónico en Banco Cooperativo Coopcentral, que va a guardar tu dinero.
## Estados de cuenta [#estados-de-cuenta]
Las cuentas en Mono tienen los siguientes estados:
| Estado | Descripción |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `active` | Cuenta en buen estado y puede usarse para transferencias. |
| `canceled` | Cuenta cerrada por un miembro de la organización o por Mono por inactividad. Una vez cancelada, la cuenta no puede reactivarse. |
# Tarjetas (/es/docs/guides/banking/concepts/cards)
Las tarjetas son un método de pago que te permite hacer pagos por productos y servicios. Las tarjetas de Mono están en alianza con Visa y pueden ser débito o prepago.
## Tipos de tarjetas [#tipos-de-tarjetas]
### Débito [#débito]
Estas tarjetas funcionan con el saldo asociado a tu cuenta Mono. No necesitas recargar la tarjeta de antemano; solo necesitas tener dinero en tu cuenta.
### Prepago [#prepago]
Estas tarjetas funcionan con el saldo que decides cargar. Si no le pones dinero a la tarjeta, no se activará. Para cargar o retirar dinero de la tarjeta, debes usar el endpoint de topup/withdrawal de tarjetas.
## Presentación de la tarjeta [#presentación-de-la-tarjeta]
### Tarjetas físicas [#tarjetas-físicas]
Se pueden usar para hacer pagos en tiendas físicas, retirar dinero en cajeros, e incluso hacer compras en línea. Nuestras tarjetas físicas pueden incluir tu logo o tener el diseño clásico de Mono.
#### ¿Cómo puedes obtenerlas? [#cómo-puedes-obtenerlas]
Puedes solicitar tarjetas físicas en lotes o individualmente. Solicita tus tarjetas físicas desde la Mono Web App yendo a la sección Tarjetas > Tarjeta física. Contacta a nuestro equipo de ventas para solicitar tarjetas en lotes.
#### ¿Cómo activo una tarjeta física? [#cómo-activo-una-tarjeta-física]
Una vez recibas las tarjetas, inicia sesión en la Mono Web App, ve a la sección Tarjetas y verás el mensaje: `Tienes 1 tarjeta en camino. ¿Llegó? Actívala ahora`. Ahí debes ingresar el alias de la tarjeta, el cupo deseado y un PIN.
### Tarjetas virtuales [#tarjetas-virtuales]
Están disponibles inmediatamente y se pueden usar para compras y pagos en plataformas digitales. Con un CVV, son perfectas para pagar servicios como Facebook, Google, Slack, publicidad, entre otros. Nuestras tarjetas virtuales se pueden personalizar con el diseño que solicites o tener el diseño clásico de Mono. Similar a las tarjetas físicas, te guiamos en el proceso para asegurar que todo se ajuste a tus preferencias.
#### ¿Cómo puedes obtenerlas? [#cómo-puedes-obtenerlas-1]
Puedes solicitar tarjetas virtuales desde la aplicación web de Mono o vía API.
#### Crear una tarjeta vía API [#crear-una-tarjeta-vía-api]
Después del proceso de autenticación, puedes usar el endpoint de creación de tarjeta para generar las tarjetas virtuales que necesites. Para generar una (o varias) tarjetas virtuales en Mono vía API, necesitas definir los siguientes aspectos:
* ID de la cuenta a la que se asociará la tarjeta (puedes encontrar tu ID de cuenta en el endpoint de cuentas).
* Información del tarjetahabiente.
* Monto inicial si la tarjeta es prepago.
* Alias y cupo de la tarjeta.
* Las tarjetas virtuales se emiten en estado `active` y pueden usarse inmediatamente. Las tarjetas físicas se emiten en estado `created` y deben activarse antes de poder usarse.
## Estados de tarjeta [#estados-de-tarjeta]
Tus tarjetas Mono pueden estar en uno de los siguientes estados:
| Estado | Descripción |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `created` | Este estado solo aplica a tarjetas físicas. El estado en el que está tu tarjeta antes de ser activada en la app de Mono. Si tu tarjeta está en este estado, no funcionará en ningún comercio. |
| `active` | Este es el único estado en el que tu tarjeta funcionará para pagos. |
| `frozen` | En este estado, tu tarjeta está "suspendida" para operación normal. Puedes reactivarla vía API usando el endpoint o desde tu app de Mono. |
| `canceled` | Una vez cancelas una tarjeta, no puedes usarla ni activarla de nuevo. Úsalo cuando ya no quieras usar esa tarjeta. |
# Conceptos (/es/docs/guides/banking/concepts)
Entiende los bloques de construcción de la API de Banking. Estos conceptos explican cómo se componen las cuentas bancarias, transferencias, enlaces de recaudo y tarjetas para mover dinero dentro y fuera de la red bancaria colombiana y gastarlo a través de tarjetas emitidas por Mono.
Entendiendo las cuentas bancarias y sus estados
Solicita y procesa pagos a través de PSE
Tarjetas virtuales y físicas — tipos, presentación y estados
Opciones de routing, fallbacks y estados de transferencia
# Enlaces de recaudo (/es/docs/guides/banking/sandbox/collection-links)
Usa estos casos de prueba para validar cómo tu integración maneja eventos de enlaces de recaudo — pagos exitosos, expiraciones y rechazos.
Esta página es un esqueleto de sandbox. La versión completa incluirá casos de prueba
deterministas (valores específicos de campos que disparan cada resultado de enlace de recaudo)
y los eventos de webhook correspondientes.
## Escenarios de prueba [#escenarios-de-prueba]
| Escenario | Descripción | Caso de prueba |
| :-------------- | :--------------------------------------------------------------------------------- | :------------- |
| Pago exitoso | El enlace de recaudo se paga y se dispara un webhook `collection_intent_credited`. | Por documentar |
| Enlace expirado | El enlace de recaudo alcanza su tiempo de expiración sin pago. | Por documentar |
| Pago rechazado | El banco del pagador rechaza la transacción PSE. | Por documentar |
## Siguientes pasos [#siguientes-pasos]
* [Flujo de recaudo PSE](/docs/guides/banking/flows/pse-collection) — la secuencia completa de recaudo en producción.
* [Webhooks de Banking](/docs/guides/banking/webhooks) — eventos disparados para cada resultado de recaudo.
# Recaudo PSE (/es/docs/guides/banking/sandbox/pse-collection)
Usa estos casos de prueba para validar cómo tu integración maneja las intenciones de recaudo PSE — selección de banco, pagos exitosos, rechazos del usuario y timeouts de red.
Esta página es un esqueleto de sandbox. La versión completa incluirá la lista de bancos
disponibles en sandbox, casos de prueba deterministas para cada resultado y los eventos de
webhook disparados en cada paso.
## Bancos de sandbox [#bancos-de-sandbox]
El sandbox de PSE provee un conjunto de bancos de prueba que simulan distintos comportamientos de respuesta. El catálogo completo de códigos de banco de sandbox se documentará aquí.
## Escenarios de prueba [#escenarios-de-prueba]
| Escenario | Descripción | Caso de prueba |
| :------------------ | :----------------------------------------------------------------- | :------------- |
| Pago exitoso | La transacción PSE es aceptada y los fondos se acreditan. | Por documentar |
| Rechazo del usuario | El account holder rechaza el pago en el portal de su banco. | Por documentar |
| Timeout | La transacción PSE expira sin una respuesta del banco. | Por documentar |
| Banco no disponible | El banco seleccionado está temporalmente no disponible en sandbox. | Por documentar |
## Siguientes pasos [#siguientes-pasos]
* [Flujo de recaudo PSE](/docs/guides/banking/flows/pse-collection) — la secuencia completa de recaudo en producción.
* [Enlaces de recaudo](/docs/guides/banking/sandbox/collection-links) — prueba el enlace de recaudo que envuelve la intención PSE.
# Transferencias bancarias (/es/docs/guides/banking/sandbox/bank-transfers)
En el entorno de sandbox puedes simular transferencias bancarias exitosas y rechazadas. Para desarrollar tu sistema correctamente, ten en cuenta que dos validaciones ocurren antes de que el dinero se acredite en la cuenta del beneficiario.
1. Para simular casos ACH o Turbo, configura el valor correspondiente en el campo `routing`.
2. La simulación evalúa el campo `payee.document_number`. 3. Para `document_type: CC`,
`payee.document_number` debe tener **5 a 10 dígitos**. Usa un valor en ese rango que cumpla el
patrón (por ejemplo, `8881423000`). Los valores de más de 10 dígitos se rechazan con un error
de validación `422` antes de que se ejecute el escenario.
## ACH [#ach]
Estos casos están disponibles para simular para ACH:
### Validaciones del banco de origen [#validaciones-del-banco-de-origen]
La primera validación la hace nuestro banco aliado para asegurar que tienes saldo suficiente, incluyendo el GMF, y que no has excedido los límites transaccionales de la cuenta de origen (cualquier usuario admin puede ajustar estos límites directamente en la interfaz de usuario). Las declinaciones del banco de origen en producción usualmente se informan en menos de 10 minutos a través de un webhook.
**Caso de prueba**: `payee.document_number` empieza con `888` y termina con `000` (por ejemplo, **888**1423**000**)
### Validaciones del banco destino [#validaciones-del-banco-destino]
La segunda validación la hace el banco destino para revisar la información y el estado de la cuenta. En producción, estas declinaciones toman hasta tres ciclos ACH, o alrededor de 12 horas hábiles, para ser recibidas. En el sandbox se simulan en 2 minutos.
**Caso de prueba**: `payee.document_number` empieza con `999` y termina con `000` (por ejemplo, **999**1423**000**)
### Transferencia bancaria ACH exitosa [#transferencia-bancaria-ach-exitosa]
La transferencia debe ser aprobada.
**Caso de prueba**: `payee.document_number` no cumple el criterio de ninguno de los otros casos de prueba (por ejemplo, `1234567890`).
***
## Turbo [#turbo]
Estos casos están disponibles para simular para Turbo:
| Escenario | Descripción | Caso de prueba |
| -------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Límite global excedido | La transferencia excede el límite global permitido para la cuenta. | Empieza con `777`, termina con `000` |
| Fondos insuficientes | La cuenta origen no tiene fondos suficientes para completar la transferencia. | Empieza con `777`, termina con `111` **o** empieza con `111`, termina con `000` |
| Turbo no soportado | El banco destino no soporta transferencias Turbo (signer no encontrado). | Empieza con `777`, termina con `222` |
| Deadline de transacción excedido | La transferencia fue rechazada por el banco destino por timeout. | Empieza con `777`, termina con `333` **o** empieza con `100`, termina con `001` |
| Cuenta deshabilitada | La cuenta destino está deshabilitada. | Empieza con `777`, termina con `444` |
| Transferencia Turbo exitosa | La transferencia debe ser aprobada. | No cumple ningún otro criterio |
Para una transferencia Turbo exitosa, usa cualquier `payee.document_number` `CC` válido (5 a 10 dígitos) que no cumpla ninguno de los patrones anteriores, por ejemplo `1234567890`.
***
## Transfiya [#transfiya]
Estos casos están disponibles para simular para Transfiya:
| Escenario | Descripción | Caso de prueba |
| -------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------ |
| Límite global excedido | La transferencia excede el límite global permitido para la cuenta. | Empieza con `555`, termina con `000` |
| Cuenta deshabilitada | La cuenta destino está deshabilitada. | Empieza con `555`, termina con `444` |
| Fondos insuficientes | La cuenta origen no tiene fondos suficientes para completar la transferencia. | Empieza con `555`, termina con `111` |
| Deadline de transacción excedido | La transferencia fue rechazada por el banco destino por timeout. | Empieza con `555`, termina con `222` |
| Transacción no completada | El usuario rechazó o no completó la transacción a través de Transfiya. | Empieza con `555`, termina con `333` |
| Transferencia Transfiya exitosa | La transferencia debe ser aprobada. | No cumple ningún otro criterio |
"global\_limit\_exceeded" es el único caso que permite simulación de routing con un error
permitido para fallbacks.
***
## CoopCentral Bre-B [#coopcentral-bre-b]
CoopCentral Bre-B provee transferencias instantáneas a través de la red Bre-B. El sandbox te permite simular distintos escenarios usando patrones específicos en la **llave Bre-B** (para búsquedas de llave) y el campo **descripción** (para operaciones de transacción).
### Proceso de transferencia Bre-B [#proceso-de-transferencia-bre-b]
Una transferencia Bre-B sigue un proceso de tres pasos:
1. **Búsqueda de llave**: primero, el sistema valida la llave Bre-B y recupera la información de cuenta asociada desde la red.
2. **Inicio de transacción**: una vez validada la llave, la transferencia se inicia con el participante destino.
3. **Verificación de estado**: finalmente, se chequea el estado de la transacción para confirmar si se completó exitosamente o falló.
Las secciones de simulación de abajo corresponden a cada uno de estos pasos, permitiéndote probar distintos escenarios en cada etapa del proceso de transferencia.
Para transferencias Bre-B exitosas en sandbox, cuando envíes información del documento de identidad, debes usar:
* `document_type`: `CC`
* `document_number`: `12345678`
**Importante**: si envías información de identidad del documento y difiere de estos valores, la transferencia generará errores. También puedes elegir no enviar la información de identidad del documento para saltarte esta validación.
### Simulación de búsqueda de llave Bre-B [#simulación-de-búsqueda-de-llave-bre-b]
Estos casos están disponibles para simular la búsqueda de llave Bre-B usando **patrones de llave Bre-B** específicos:
| Escenario | Descripción | Caso de prueba |
| -------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------- |
| Llave no encontrada | La llave Bre-B no existe en la red. | La llave contiene `key_not_found` |
| Participante no disponible | El participante/banco destino está temporalmente no disponible. | La llave empieza con `key_unavailable` |
| Llave bloqueada o inactiva | La llave Bre-B está bloqueada o inactiva en la red. | La llave contiene `key_blocked` |
| Timeout | La solicitud de búsqueda de llave tuvo timeout. | La llave contiene `key_timeout` |
| Búsqueda exitosa | La búsqueda de llave Bre-B debe ser exitosa y devolver información de cuenta. | La llave no coincide con ningún patrón de error |
### Simulación de inicio de transacción Bre-B [#simulación-de-inicio-de-transacción-bre-b]
Estos casos están disponibles para simular el inicio de transacción Bre-B usando **patrones de descripción** específicos:
| Escenario | Descripción | Caso de prueba |
| --------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------- |
| Timeout | La solicitud de inicio de transacción tuvo timeout. | La descripción contiene `tx_timeout` |
| Cuenta deshabilitada | La cuenta destino está deshabilitada. | La descripción contiene `tx_disabled` |
| Llave Bre-B inválida | La llave Bre-B provista es inválida. | La descripción contiene `tx_invalid_key` |
| Límite de monto diario excedido | La transferencia excede el límite de monto diario. | La descripción contiene `tx_limit_daily_amount` |
| Límite de conteo diario excedido | La cuenta excedió el límite diario de conteo de transacciones. | La descripción contiene `tx_limit_daily_count` |
| Fraude detectado | La transacción fue marcada como fraudulenta. | La descripción contiene `tx_fraud` |
| Fondos insuficientes | La cuenta origen no tiene fondos suficientes. | La descripción contiene `tx_insufficient` |
| Límite de monto mensual excedido | La transferencia excede el límite de monto mensual. | La descripción contiene `tx_limit_monthly_amount` |
| Límite de conteo mensual excedido | La cuenta excedió el límite mensual de conteo de transacciones. | La descripción contiene `tx_limit_monthly_count` |
| Cuenta inválida | La información de la cuenta destino es inválida. | La descripción contiene `tx_invalid_account` |
| Cuenta bloqueada | La cuenta destino está bloqueada. | La descripción contiene `tx_blocked` |
| Error desconocido | Ocurrió un error desconocido durante el procesamiento. | La descripción contiene `tx_error` |
| Inicio exitoso | La transacción debe iniciarse exitosamente. | La descripción no coincide con ningún patrón de error |
### Simulación de estado de transacción Bre-B [#simulación-de-estado-de-transacción-bre-b]
Estos casos están disponibles para simular chequeos de estado de transacción Bre-B usando **patrones de llave Bre-B** específicos:
La simulación de estado de transacción usa la misma llave Bre-B que se usó durante el paso de
búsqueda de llave. El sistema valida el patrón de la llave Bre-B para determinar qué estado
devolver.
| Escenario | Descripción | Caso de prueba | Código de estado |
| -------------------------- | ----------------------------------------------------------- | ----------------------------------------------- | ---------------- |
| Transacción no encontrada | El ID de transacción no existe. | La llave contiene `status_not_found` | - |
| Timeout | La solicitud de chequeo de estado tuvo timeout. | La llave contiene `status_timeout` | - |
| Participante no disponible | El participante no está disponible para chequeos de estado. | La llave contiene `status_unavailable` | - |
| Transacción fallida | La transacción falló. | La llave contiene `status_failed` | `91` |
| Transacción pendiente | La transacción aún está pendiente. | La llave contiene `status_pending` | - |
| Transacción exitosa | La transacción se completó exitosamente. | La llave no coincide con ningún patrón de error | `00` |
# Sandbox (/es/docs/guides/banking/sandbox)
Usa el entorno de sandbox para probar tu integración de Banking de punta a punta sin procesar transacciones reales. El sandbox simula el ciclo de vida completo de transferencias, enlaces de recaudo, tarjetas y webhooks para que valides tu implementación antes de pasar a producción.
## URL base [#url-base]
```
https://api.sandbox.cuentamono.com
```
## Escenarios de prueba [#escenarios-de-prueba]
Simula transferencias bancarias exitosas y rechazadas
Simula pagos y expiraciones de enlaces de recaudo
Simula selección de banco PSE, aceptación y rechazos
Simula creación de tarjetas y controles (sandbox de Core)
## Siguientes pasos [#siguientes-pasos]
* [Convenciones de webhooks](/docs/guides/api-standards/webhooks) — verificación de firmas para eventos de webhook en sandbox.
* [Lista de verificación de producción](/docs/guides/banking/production) — pasos a seguir antes de salir a producción.
# Enlaces de recaudo (/es/docs/guides/banking/webhooks/collection-links)
Cuando una persona inicia un pago usando un enlace de recaudo, se genera una solicitud de webhook basada en el estado de la intención de pago.
## Tipos de evento [#tipos-de-evento]
| Evento | Descripción |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `collection_intent_credited` | Disparado cuando una intención de pago fue acreditada exitosamente a la cuenta bancaria del receptor. Significa una transacción exitosa donde los fondos se transfirieron exitosamente. |
| `collection_intent_failed` | Disparado cuando una intención de pago no pudo ser procesada por el proveedor de pagos y, como resultado, el pago fue rechazado. |
## Ejemplo de payload [#ejemplo-de-payload]
```json
{
"event": {
"data": {
"account_id": "acc_223RxfTSugBz8KfLSWoEnT",
"amount": {
"amount": 10000000,
"currency": "COP"
},
"collection_link_id": "clink_3QVH4A9L4QQzAajaK4VMrB",
"external_id": "b3852866-33b2-452c-b269-33591a38c4d0",
"id": "cint_5dHsMj4uwhhujcVUTuw1OP",
"inserted_at": "2023-01-04T17:16:32.812292Z",
"note": "a Note",
"payer": {
"document_number": "1033711400",
"document_type": "CC",
"name": "peter rosas"
},
"payment": {
"provider": "pse",
"transaction_id": "3052043"
},
"reference": "REF #132ABC",
"state": "account_credited",
"updated_at": "2023-01-04T17:16:58.071790Z"
},
"type": "collection_intent_credited"
},
"timestamp": "2023-01-04T17:17:01.101130Z"
}
```
El campo `external_id` puede ser útil para asociar con algún identificador en tu sistema.
Puedes configurar este campo al crear un enlace de recaudo vía API.
# Transferencias bancarias (/es/docs/guides/banking/webhooks/bank-transfers)
Cuando se crean transferencias bancarias y luego son aprobadas, rechazadas o reintentadas con un fallback routing, se envía un evento de webhook a una URL especificada con los detalles de la transferencia y su estado actual. Esto permite a los clientes recibir actualizaciones en tiempo real del estado de sus transferencias y tomar las acciones apropiadas según los eventos recibidos.
## Tipos de evento [#tipos-de-evento]
Los siguientes tipos de evento están relacionados con transferencias bancarias:
| Evento | Descripción |
| ---------------------------------- | -------------------------------------------------------------------------------------- |
| `bank_transfer_approved` | Disparado cuando una transferencia bancaria es aprobada. |
| `bank_transfer_rejected` | Disparado cuando una transferencia bancaria es rechazada. |
| `bank_transfer_fallback_routing` | Disparado cuando una transferencia bancaria es reintentada con un routing diferente. |
| `bank_transfer_change_final_state` | Disparado cuando una transferencia ya en estado final pasa a un estado final distinto. |
| `account_credited` | Disparado cuando una transferencia entrante es acreditada exitosamente a tu cuenta. |
## Ejemplo de payload [#ejemplo-de-payload]
```json
{
"event": {
"data": {
"amount": {
"amount": 1600000,
"currency": "COP"
},
"batch": {
"account_id": "acc_16ktUqSO7G0qTHDz8I3qrG",
"id": "bat_2DpSchlriwoCuyGMOoIuwp",
"inserted_at": "2022-12-28T16:19:52.471575Z",
"origin": "api",
"state": "declined",
"total_amount": {
"amount": 1600000,
"currency": "COP"
},
"updated_at": "2022-12-28T16:40:06.822840Z"
},
"declination_reason": null,
"description": "some description",
"entity_id": "123456789",
"id": "trn_2PVWOx9dZKJMBZw7opjrrs",
"inserted_at": "2022-12-28T16:19:52.530158Z",
"payee": {
"bank_account": {
"bank_code": "007",
"number": "000000009",
"type": "savings_account"
},
"document_number": "1033711400",
"document_type": "CC",
"email": "someone@gmail.com",
"name": "patricio rothschild"
},
"recipient_handle": null,
"reference": "ref # 1231",
"state": "approved",
"updated_at": "2022-12-28T16:40:06.812742Z",
"routing": "ach",
"fallback_routing": []
},
"type": "bank_transfer_approved"
},
"timestamp": "2022-12-28T16:54:38.660650Z"
}
```
## Transferencia rechazada [#transferencia-rechazada]
Cuando el tipo de evento es `bank_transfer_rejected`, el campo `declination_reason` será un string indicando la razón del rechazo:
```json
{
"event": {
"data": {
"declination_reason": "insufficient_funds",
"state": "declined"
},
"type": "bank_transfer_rejected"
},
"timestamp": "2022-12-28T16:54:38.660650Z"
}
```
***
## Eventos de lote [#eventos-de-lote]
Se envía una solicitud de webhook relacionada con un lote cuando:
* El lote fue creado con todas sus transferencias como duplicadas (con `entity_id` previamente registrado)
* El lote fue solicitado para autorización
* El lote fue enviado a procesar
* El lote fue cancelado
### Ejemplo de payload del lote [#ejemplo-de-payload-del-lote]
```json
{
"event": {
"data": {
"account_id": "acc_16ktUqSO7G0qTHDz8I3qrG",
"id": "bat_5d60uR36JfH4J4uN9ZEYhL",
"inserted_at": "2022-12-28T18:01:21.549268Z",
"origin": "api",
"state": "duplicated",
"total_amount": {
"amount": 100000,
"currency": "COP"
},
"transfers": [
{
"amount": {
"amount": 100000,
"currency": "COP"
},
"declination_reason": null,
"description": "eaque qui dolorem",
"entity_id": "d31d1c0d-395b-43f4-b57b-a3ae4b8c34d6",
"id": "trn_5NYRqKnE17nE0MswylZzq1",
"inserted_at": "2022-12-28T18:01:21.602854Z",
"payee": {
"bank_account": {
"bank_code": "007",
"number": "123456789",
"type": "savings_account"
},
"document_number": "123456789",
"document_type": "CC",
"email": "someone@gmail.com",
"name": "Ginger Gutmann"
},
"recipient_handle": "573004366648",
"reference": "quasi sint inventore",
"state": "duplicated",
"updated_at": "2022-12-28T18:01:21.602854Z"
}
],
"updated_at": "2022-12-28T18:01:21.765972Z"
},
"type": "batch_duplicated"
},
"timestamp": "2022-12-28T18:01:36.586019Z"
}
```
El tipo de evento del webhook variará según lo que le pasó al lote:
| Evento | Descripción |
| ------------------------------- | ------------------------------------------------------------------------------ |
| `batch_duplicated` | Todas las transferencias del lote ya fueron recibidas (`entity_id` duplicado). |
| `batch_authorization_requested` | El lote requiere autorización OTP de un administrador antes de ser enviado. |
| `batch_sent` | El lote fue autorizado y despachado para procesamiento. |
| `batch_canceled` | El lote fue cancelado por un administrador antes de ser procesado. |
***
## Cuenta acreditada [#cuenta-acreditada]
Cuando una transferencia es exitosa y el dinero está en tu cuenta, se enviará una solicitud de webhook.
### Ejemplo de payload [#ejemplo-de-payload-1]
```json
{
"event": {
"data": {
"account": {
"id": "acc_16ktUqSO7G0qTHDz8I3qrG",
"number": "095000036"
},
"amount": {
"amount": 105000,
"currency": "COP"
},
"description": "some description, abcde...",
"extra": {
"entity_id": null,
"reference": "some ref: #123..."
},
"id": "tx_3RJ9CbZLYq7r1MxH8vAm7Y",
"payer": {
"document_number": "9013980684",
"name": "MONO COLOMBIA S.A.S.",
"bank": {
"name": "BANCO COOPERATIVO COOPCENTRAL",
"code": "066"
}
},
"transaction_at": "2023-03-08T17:20:55.809787Z",
"type": "bank_transfer_mono"
},
"type": "account_credited"
},
"timestamp": "2023-03-08T17:21:01.673116Z"
}
```
### Tipos de transferencia [#tipos-de-transferencia]
Hay dos tipos de transferencias, que se identifican por el campo `type` en el objeto `data`:
| Tipo | Descripción |
| -------------------- | ---------------------------------------------------------------------- |
| `bank_transfer_mono` | Una transferencia en tiempo real hecha por el sistema interno de Mono. |
| `incoming_wire` | Una transferencia hecha vía ACH. |
Si el tipo es `incoming_wire`, el campo `extra` en el objeto `data` estará vacío.
### Campos del objeto extra [#campos-del-objeto-extra]
El objeto `extra` en el objeto `data` contiene los siguientes campos:
* `entity_id`: el ID de la entidad para la transferencia.
* `reference`: la referencia de la transferencia.
Estos campos se configuran cuando creas una transferencia bancaria usando la API.
# Webhooks (/es/docs/guides/banking/webhooks)
Los webhooks notifican a tu sistema en el momento que ocurre un evento de Banking — una transferencia aprobada, un enlace de recaudo pagado. Para la mecánica compartida que rige todos los webhooks de Mono (verificación de firma, política de reintentos, campos de dinero, restricciones de URL), ver [Convenciones de webhooks](/docs/guides/api-standards/webhooks).
## Eventos de webhook [#eventos-de-webhook]
Aprobaciones, rechazos, fallback routing y lotes de transferencia
Eventos de pago de enlaces de recaudo
## Siguientes pasos [#siguientes-pasos]
* [Convenciones de webhooks](/docs/guides/api-standards/webhooks) — verificación de firma, política de reintentos y campos de dinero.
* [Dashboard](/docs/guides/banking/dashboard) — configura endpoints de webhook e inspecciona entregas recientes.
# Vista general de arquitectura (/es/docs/guides/banking/architecture/index)
Banking es el puente de Mono entre tu producto y la red bancaria colombiana. Cuando llamas a un endpoint de Banking, la solicitud pasa por autenticación, chequeo de idempotencia y routing antes de ser despachada a uno de los rails de dispersión disponibles: **ACH**, **Transfiya** o **Mono Turbo**. Los recaudos (dinero entrando) siguen el rail **PSE**.
Esta página es el punto de partida correcto si quieres entender el camino que toma una solicitud por Banking, el rail al que llega y por qué una sola llamada de transferencia puede terminar usando un rail diferente al que solicitaste (Mono cae automáticamente).
## Rails [#rails]
| Rail | Tipo | Uso típico |
| :--------- | :------------------------ | :------------------------------------------------------------------- |
| Mono Turbo | Dispersión en tiempo real | Transferencias en tiempo real a bancos colombianos participantes |
| ACH | Dispersión estándar | Transferencias de mismo día a cualquier banco colombiano |
| Transfiya | Dispersión en tiempo real | Transferencias dentro de un clúster específico de bancos |
| PSE | Recaudo | Transferencia bancaria iniciada por el cliente (checkout e-commerce) |
## Ciclo de vida de la solicitud [#ciclo-de-vida-de-la-solicitud]
1. **Autenticar** — se valida la API key del header `Authorization`.
2. **Chequear idempotencia** — `X-Idempotency-Key` se busca en el caché.
3. **Enrutar** — la solicitud aterriza en el rail que solicitaste o su fallback.
4. **Despachar** — Mono envía la operación al rail.
5. **Confirmar** — los webhooks se disparan a medida que el rail confirma o rechaza.
## Fallbacks [#fallbacks]
Mono Turbo puede caer a ACH cuando el destino no soporta settlement en tiempo real. El árbol de decisión del fallback se documentará aquí.
## Referencias del ciclo de vida [#referencias-del-ciclo-de-vida]
Una vez que una solicitud entra a la plataforma, se mueve por una máquina de estados determinista, y cada transición emite un webhook. Las páginas de abajo documentan cada estado, sus transiciones y el evento correspondiente — léelas junto con la arquitectura de arriba para entender a qué debe estar escuchando tu integración.
* [Ciclo de vida de transferencia bancaria](/docs/guides/banking/architecture/bank-transfer-states) — estados, eventos de webhook y resultados terminales para transferencias y lotes.
## Siguientes pasos [#siguientes-pasos]
* [Autenticación](/docs/guides/banking/api/authentication)
* [Transferencias bancarias](/docs/guides/banking/concepts/transfers)
* [Sandbox](/docs/guides/banking/sandbox)
# Ciclo de vida de transferencia bancaria (/es/docs/guides/banking/architecture/bank-transfer-states)
Una transferencia bancaria es una dispersión: el dinero sale de tu cuenta Mono y llega a la cuenta bancaria colombiana de un beneficiario por uno de tres rails — ACH, Transfiya o Mono Turbo. Cada transferencia que creas vía `POST /transfers` se agrupa automáticamente en un **lote**, y tanto la transferencia como el lote se mueven por sus propias máquinas de estados en paralelo. Esta página explica qué significa cada estado, qué causa cada transición y qué evento de webhook se dispara en cada paso.
Entender ambas máquinas de estados importa porque tu handler de webhooks recibirá eventos del lote (¿fue autorizado? ¿enviado?) y eventos de la transferencia (¿fue aprobada o rechazada la transferencia individual?) en un flujo intercalado.
## Máquina de estados de la transferencia [#máquina-de-estados-de-la-transferencia]
### Estados de la transferencia de un vistazo [#estados-de-la-transferencia-de-un-vistazo]
| Estado | ¿Terminal? | Evento de webhook |
| ------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `created` | No | — (respuesta síncrona de la API) |
| `in_progress` | No | [`bank_transfer_fallback_routing`](/docs/guides/banking/webhooks/bank-transfers) se dispara si ocurre un reintento de routing |
| `approved` | **Sí** | [`bank_transfer_approved`](/docs/guides/banking/webhooks/bank-transfers) |
| `declined` | **Sí** | [`bank_transfer_rejected`](/docs/guides/banking/webhooks/bank-transfers) |
| `cancelled` | **Sí** | — (iniciado por el usuario antes del despacho; sin evento asíncrono) |
| `duplicated` | **Sí** | — (se expone vía [`batch_duplicated`](/docs/guides/banking/webhooks/bank-transfers) a nivel del lote) |
El evento `bank_transfer_change_final_state` se dispara en el caso raro en que una
transferencia ya en `approved` o `declined` es movida a un estado final distinto por el rail.
Maneja siempre este evento de forma idempotente — actualiza tu registro local al nuevo estado
incluso si ya lo considerabas settled.
## Máquina de estados del lote [#máquina-de-estados-del-lote]
Cada llamada a `POST /transfers` crea un lote, incluso cuando envías una sola transferencia. El lote es la unidad que pasa por la autorización del admin (OTP) si tu cuenta lo requiere.
### Estados del lote de un vistazo [#estados-del-lote-de-un-vistazo]
| Estado | ¿Terminal? | Evento de webhook |
| ------------------------- | ---------- | ----------------------------------------------------------------------------------- |
| `created` | No | — (respuesta síncrona de la API) |
| `pending_otp` | No | [`batch_authorization_requested`](/docs/guides/banking/webhooks/bank-transfers) |
| `verified_otp` | No | — (interno; transiciona inmediatamente a `processing_transactions`) |
| `processing_transactions` | No | [`batch_sent`](/docs/guides/banking/webhooks/bank-transfers) |
| `approved` | **Sí** | — (las transferencias individuales emiten `bank_transfer_approved`) |
| `partially_approved` | **Sí** | — (mezcla de `bank_transfer_approved` y `bank_transfer_rejected` por transferencia) |
| `declined` | **Sí** | — (las transferencias individuales emiten `bank_transfer_rejected`) |
| `duplicated` | **Sí** | [`batch_duplicated`](/docs/guides/banking/webhooks/bank-transfers) |
| `canceled` | **Sí** | [`batch_canceled`](/docs/guides/banking/webhooks/bank-transfers) |
## Recorrido detallado [#recorrido-detallado]
### Transferencia: `created` [#transferencia-created]
Estado inicial. La fila de la transferencia existe en el sistema con un `id` que puedes usar para consultas. Mono aceptó la solicitud pero aún no la ha enviado al rail. Cada transferencia del lote entra a este estado al mismo tiempo.
**Qué pasa después:** una vez que el lote pasa cualquier gate de OTP y llega a `processing_transactions`, cada transferencia pasa a `in_progress`.
### Transferencia: `in_progress` [#transferencia-in_progress]
La transferencia fue despachada al rail seleccionado — ACH, Mono Turbo o Transfiya. Los fondos están en tránsito; el saldo de la cuenta origen fue debitado pero el destino aún no ha confirmado el crédito.
Si el rail primario rechaza el intento y configuraste un `fallback_routing`, Mono reintenta automáticamente. Esto dispara `bank_transfer_fallback_routing` sin cambiar el estado de la transferencia — la transferencia se queda en `in_progress` durante el reintento.
**Estados siguientes:**
* → `approved` cuando el rail confirma el crédito.
* → `declined` cuando el rail rechaza y ningún fallback tiene éxito.
### Transferencia: `approved` (terminal) [#transferencia-approved-terminal]
El banco destino confirmó el crédito. Los fondos están en la cuenta del beneficiario. El webhook `bank_transfer_approved` se dispara con el routing final y el monto. No ocurren más transiciones bajo circunstancias normales.
### Transferencia: `declined` (terminal) [#transferencia-declined-terminal]
La transferencia fue rechazada. El campo `declination_reason` en el payload del webhook lleva la razón del rechazo (por ejemplo, `insufficient_funds` o `invalid_account_information`). El webhook `bank_transfer_rejected` se dispara. No ocurren más transiciones bajo circunstancias normales.
### Transferencia: `cancelled` (terminal) [#transferencia-cancelled-terminal]
La transferencia fue cancelada por un usuario antes de que el lote llegara a `processing_transactions`. Cancelar un lote cancela todas sus transferencias. No se dispara un webhook asíncrono para las transferencias individuales — el lote emite `batch_canceled`.
### Transferencia: `duplicated` (terminal) [#transferencia-duplicated-terminal]
El `entity_id` de esta transferencia ya se usó en un lote anterior. La transferencia no se procesa. El lote emite `batch_duplicated`.
***
### Lote: `created` [#lote-created]
La fila del lote existe en el sistema. Todas sus transferencias están en `created`. Este estado es síncrono — obtienes el `id` del lote en la respuesta de la API.
**Qué pasa después:** Mono chequea si el lote requiere autorización OTP.
### Lote: `pending_otp` [#lote-pending_otp]
El lote requiere autorización explícita de un usuario Administrador antes de poder ser procesado. El webhook `batch_authorization_requested` se dispara. Ninguna transferencia se mueve hasta que un admin autorice.
**Estados siguientes:**
* → `verified_otp` cuando un admin completa la autorización.
* → `canceled` cuando un admin cancela.
### Lote: `verified_otp` [#lote-verified_otp]
El admin autorizó. Mono prepara el lote para el despacho. Este estado es transitorio — pasa a `processing_transactions` en segundos y no se dispara webhook para `verified_otp`.
### Lote: `processing_transactions` [#lote-processing_transactions]
El lote está siendo despachado. Cada transferencia pasa a `in_progress`. El webhook `batch_sent` se dispara. Desde este punto, los webhooks individuales de transferencia (`bank_transfer_approved` / `bank_transfer_rejected`) guían la conciliación.
### Lote: `approved` (terminal) [#lote-approved-terminal]
Todas las transferencias del lote fueron aprobadas. Los eventos individuales `bank_transfer_approved` ya se dispararon para cada transferencia.
### Lote: `partially_approved` (terminal) [#lote-partially_approved-terminal]
Algunas transferencias fueron aprobadas y otras rechazadas. Cada transferencia ya emitió su propio evento `bank_transfer_approved` o `bank_transfer_rejected`. Concilia transferencia por transferencia usando los eventos individuales.
### Lote: `declined` (terminal) [#lote-declined-terminal]
Todas las transferencias del lote fueron rechazadas. Los eventos individuales `bank_transfer_rejected` ya se dispararon.
### Lote: `duplicated` (terminal) [#lote-duplicated-terminal]
Cada transferencia del lote tenía un `entity_id` que ya existía. Ninguna transferencia se procesa. `batch_duplicated` se dispara. Las transferencias individuales pasan a `duplicated`.
### Lote: `canceled` (terminal) [#lote-canceled-terminal]
Un admin canceló el lote antes de que llegara a `processing_transactions`. Todas las transferencias pasan a `cancelled`. `batch_canceled` se dispara.
## Cómo trabajar con estas máquinas de estados en tu integración [#cómo-trabajar-con-estas-máquinas-de-estados-en-tu-integración]
1. **Apóyate en webhooks, no en polling.** Cada transición significativa emite un webhook. Suscríbete a eventos de transferencia y de lote — llevan información distinta y llegan en orden intercalado.
2. **Maneja entregas fuera de orden.** Las entregas de webhooks son at-least-once y pueden llegar fuera de orden. Si recibes `bank_transfer_approved` antes que `batch_sent`, conserva el estado más reciente e ignora el anterior. Regla segura: si una transferencia ya está en estado terminal localmente, ignora los webhooks no terminales para ella.
3. **Diseña para el gate de OTP.** Si tu cuenta Mono requiere autorización de lote, tu lote se quedará en `pending_otp` hasta que un admin actúe. Planea tu UX alrededor de esto: expón el estado del lote a los operadores para que sepan cuándo se requiere acción.
4. **Distingue el estado de la transferencia del estado del lote.** Un lote en `partially_approved` no te dice qué transferencias tuvieron éxito — debes inspeccionar los eventos individuales de transferencia. Concilia transferencia por transferencia, no lote por lote.
5. **Maneja `bank_transfer_change_final_state`.** Es raro, pero una transferencia ya en `approved` o `declined` puede pasar a un estado final distinto. Actualiza tu registro local cuando llegue este evento; no trates un estado terminal como inmutable hasta que dejes de recibir eventos para esa transferencia.
6. **Usa `entity_id` para idempotencia.** Enviar el mismo `entity_id` dos veces resulta en `duplicated`. Esta es tu red de seguridad contra el doble envío — pero también significa que debes generar un `entity_id` nuevo para cada transferencia genuinamente nueva.
## Siguientes pasos [#siguientes-pasos]
* [Webhooks: transferencias bancarias](/docs/guides/banking/webhooks/bank-transfers) — los esquemas de payload completos de cada evento anterior.
* [Flujo de envío de transferencias](/docs/guides/banking/flows/sending-transfers) — la secuencia de punta a punta que mueve una transferencia por estos estados.
* [Sandbox: transferencias bancarias](/docs/guides/banking/sandbox/bank-transfers) — simula el ciclo de vida completo sin mover dinero real.
# Autenticación (/es/docs/guides/banking/api/authentication)
Cada solicitud a la API de Banking debe demostrar que viene de tu empresa. Puedes demostrarlo con una API key de larga duración, o con un access token de corta duración que tu servidor pide por su cuenta. Mono acepta ambos en todos los endpoints de Banking, así que puedes elegir el que mejor encaje con tus prácticas de seguridad.
Una credencial pertenece a un solo servicio. Las credenciales de Banking solo funcionan en la
API de Banking, y una credencial de Core enviada a un endpoint de Banking es rechazada.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Acceso al Mono Dashboard de tu organización.
* Un cliente HTTPS. Mono rechaza las solicitudes que no usan TLS.
* Un lugar seguro para los secretos, como un gestor de secretos. Nunca subas credenciales al control de versiones.
Las convenciones que comparten todos los productos de Mono, como el formato del header y los entornos, están en [Estándares de autenticación](/docs/guides/api-standards/authentication).
## Elige una credencial [#elige-una-credencial]
| | API key | Access token de OAuth 2.0 |
| :--------------- | :---------------------------- | :----------------------------------------------- |
| Cuánto dura | Hasta que la revoques | 30 minutos; después pides uno nuevo |
| Qué puede hacer | Lo que permiten sus roles | Lo que permiten sus scopes |
| Cómo la obtienes | Se crea en el Mono Dashboard | Tu servidor la pide con un client ID y un secret |
| Dónde va | `Authorization: Bearer ` | `Authorization: Bearer ` |
Un **rol** es un conjunto de permisos con nombre, como "Admin" o "Viewer", asignado a una API key. Un **scope** es un permiso individual, como `transfers:readonly`, asignado a un access token. Mono relaciona cada permiso de rol con los scopes que autorizan la misma acción, así que un endpoint acepta cualquiera de las dos credenciales para la misma operación.
## Opción 1: API keys [#opción-1-api-keys]
Una API key es una cadena secreta que sigue siendo válida hasta que la revoques. Para crear o revocar llaves, inicia sesión en el [Mono Dashboard](https://mi.cuentamono.com/log_in) y abre la [sección de gestión de API keys](https://mi.cuentamono.com/settings/api).
Al crear una llave, elige sus roles. La llave puede hacer exactamente lo que esos roles permiten. Por ejemplo, una llave con el rol "Viewer" puede leer datos, pero no mover dinero.
Envía la llave en el header `Authorization` de cada solicitud:
```bash
curl https://api.cuentamono.com/v1/accounts \
-H "Authorization: Bearer $MONO_API_KEY"
```
Las llaves no expiran, así que rotarlas es tu responsabilidad: crea una llave nueva, despliégala y luego revoca la anterior.
## Opción 2: access tokens de OAuth 2.0 [#opción-2-access-tokens-de-oauth-20]
OAuth 2.0 es un estándar de la industria para emitir credenciales temporales. Mono usa su flujo de **client credentials**, pensado para integraciones de servidor a servidor. Tu servidor cambia un client ID y un client secret permanentes por un access token que dura 30 minutos. Piénsalo como cambiar el carné de empleado por un pase de un día: el pase vence y el carné se queda guardado en la caja fuerte.
### Obtén una credencial [#obtén-una-credencial]
Las credenciales OAuth de Banking todavía no son de autoservicio: el Mono Dashboard no puede crearlas para organizaciones de Banking. Para obtener una, contacta a tu equipo de cuenta en Mono e indícale qué scopes necesita tu integración.
Mono te entrega un client ID y un client secret. Guarda ambos en tu gestor de secretos apenas los recibas. Para cambiar los scopes después, o para desactivar o eliminar la credencial, contacta al mismo equipo.
### Pide un access token [#pide-un-access-token]
Llama al endpoint de token con `grant_type=client_credentials`, tu client ID y tu secret, y los scopes que necesitas, separados por espacios:
```bash
curl https://api.cuentamono.com/v1/oauth/token \
-d grant_type=client_credentials \
-d client_id="$MONO_CLIENT_ID" \
-d client_secret="$MONO_CLIENT_SECRET" \
-d scope="accounts:readonly transfers"
```
El endpoint acepta `application/x-www-form-urlencoded`, como arriba, o los mismos campos en JSON. En sandbox, usa `https://api.sandbox.cuentamono.com`.
```json
{
"access_token": "",
"token_type": "Bearer",
"expires_in": 1800,
"refresh_token": "",
"scope": "accounts:readonly transfers"
}
```
Envía siempre `scope`. Si lo omites, Mono igual emite un token, pero sin scopes, y luego todos
los endpoints responden `403`. Si pides un scope que tu credencial no tiene, toda la solicitud
falla con `400 invalid_scope`; Mono nunca devuelve un token con solo una parte de lo que
pediste.
### Llama a la API [#llama-a-la-api]
Envía el access token en el header `Authorization`, igual que una API key:
```bash
curl https://api.cuentamono.com/v1/accounts \
-H "Authorization: Bearer $MONO_ACCESS_TOKEN"
```
### Renueva el token [#renueva-el-token]
`expires_in` es la vida restante del token en segundos: 1800 cuando se emite. Pide un token nuevo antes de que se acabe. Tienes dos formas:
* **Pide un token nuevo** con tus client credentials, igual que arriba. Es la opción más simple para un servidor que siempre tiene su secret.
* **Usa el refresh token.** La respuesta también trae un `refresh_token`, válido por un día. Cámbialo por un access token nuevo:
```bash
curl https://api.cuentamono.com/v1/oauth/token \
-d grant_type=refresh_token \
-d client_id="$MONO_CLIENT_ID" \
-d client_secret="$MONO_CLIENT_SECRET" \
-d refresh_token="$MONO_REFRESH_TOKEN"
```
Cada refresh devuelve un refresh token nuevo. Usa el nuevo la próxima vez y descarta el anterior.
### Consulta o revoca un token [#consulta-o-revoca-un-token]
Otros dos endpoints reciben tu client ID y tu secret, más el token:
* **Introspect** (`grant_type=introspect`) te dice si un token sigue activo, cuándo expira y qué scopes trae. Un token expirado o desconocido devuelve `{"active": false}`.
* **Revoke** (`grant_type=revoke`) invalida de inmediato un access token o un refresh token. Responde `200` aunque el token ya hubiera expirado.
Revoca los tokens que ya no necesites, por ejemplo cuando retires un servidor. Los contratos de los endpoints están en la [referencia de autenticación de Banking](/docs/api-reference/banking/authentication).
## Scopes [#scopes]
Pide los scopes más acotados que necesite tu integración. Dos reglas aplican a todo el catálogo:
* Un scope sin sufijo, como `transfers`, autoriza todas las acciones sobre ese recurso, incluidas las acciones específicas que aparecen debajo.
* Un scope `:readonly` autoriza solo lecturas. No incluye las acciones específicas: `cards:readonly` no autoriza `cards:details`.
| Scope | Autoriza |
| :--------------------------- | :------------------------------------------------------------------------------------------- |
| `accounts` | Todos los permisos sobre cuentas |
| `accounts:readonly` | Leer cuentas |
| `accounts:balances` | Leer el saldo de una cuenta |
| `transfers` | Todos los permisos sobre transferencias bancarias, y leer el catálogo de bancos |
| `transfers:readonly` | Leer transferencias bancarias y el catálogo de bancos |
| `transfers:prepare` | Crear transferencias bancarias que quedan esperando aprobación |
| `collection_links` | Todos los permisos sobre links de cobro e intenciones de cobro, y leer el catálogo de bancos |
| `collection_links:readonly` | Leer links de cobro, intenciones de cobro y el catálogo de bancos |
| `cards` | Todos los permisos sobre tarjetas |
| `cards:readonly` | Leer tarjetas |
| `cards:details` | Leer los datos sensibles de una tarjeta: número, código de seguridad y vencimiento |
| `spending_controls` | Todos los permisos sobre los controles de gasto de tarjetas |
| `spending_controls:readonly` | Leer los controles de gasto de tarjetas |
## Fallas [#fallas]
Los endpoints de token, introspect y revoke responden los errores en formato OAuth, con los campos `error` y `error_description`. Por ejemplo, `400 invalid_scope` significa que pediste un scope que tu credencial no tiene.
Todos los demás endpoints responden con el [envelope de error](/docs/guides/api-standards/errors-and-retries) estándar:
| Estado | Código | Qué pasó |
| :----- | :--------------- | :-------------------------------------------------------------------------- |
| `401` | `expired_token` | El token expiró, fue revocado o pertenece a otro servicio, como Core. |
| `403` | `not_authorized` | La credencial es válida, pero sus scopes o roles no permiten este endpoint. |
Un token de Core enviado a un endpoint de Banking recibe el mismo `401 expired_token` que un
token expirado. Si un token recién emitido es rechazado así, verifica que venga de
`/v1/oauth/token` y no de `/v1/core/oauth/token`.
## Siguientes pasos [#siguientes-pasos]
* [Referencia de autenticación](/docs/api-reference/banking/authentication) — los endpoints de token, introspect y revoke en detalle.
* [Estándares de autenticación](/docs/guides/api-standards/authentication) — header, entornos y reglas de rotación que comparten todos los productos.
* [Errores y reintentos](/docs/guides/api-standards/errors-and-retries) — el envelope de error y cuándo reintentar.
# API (/es/docs/guides/banking/api)
Aprende a autenticarte e interactuar con las APIs de Banking y Core. Estas guías cubren las credenciales y headers que necesitas para hacer solicitudes autorizadas.
Autentica tus solicitudes a la API usando API keys
# Recaudo PSE (/es/docs/guides/banking/flows/pse-collection)
**PSE (Pagos Seguros en Línea)** es el sistema estándar en Colombia para pagar desde una cuenta bancaria en un sitio web — el equivalente local a Pix en Brasil o ACH Direct Debit en Estados Unidos. Un recaudo PSE es el flujo que tu negocio corre cuando quieres que un cliente te pague desde su cuenta bancaria: generas un enlace de pago, el cliente lo sigue hasta su banco, el banco autoriza el pago y Mono te notifica el resultado.
Este flujo es el centro de la capacidad de recaudo de Banking. Úsalo para checkout de e-commerce, settlement de facturas, recargas de suscripción o cualquier escenario donde el pagador inicie el pago.
Esta página es un esqueleto de flujo y se expandirá con ejemplos de código por paso, webhooks
esperados y errores comunes. Sigue el progreso en el roadmap de documentación.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una cuenta Mono con Banking habilitado y una API key para el entorno al que estás llamando. Ver [Autenticación](/docs/guides/api-standards/authentication).
* Una cuenta bancaria receptora configurada en tu dashboard de Mono.
* Un endpoint de webhook que pueda recibir [eventos de enlaces de recaudo](/docs/guides/banking/webhooks/collection-links).
* Familiaridad con el [concepto de enlaces de recaudo](/docs/guides/banking/concepts/collection-links).
## Vista general de la secuencia [#vista-general-de-la-secuencia]
1. **Crea una intención de recaudo** — tu backend llama a Mono para generar una intención de pago con el monto, la moneda y una referencia que tú controlas. Mono devuelve una URL de redirección apuntando al formulario de pago respaldado por PSE.
2. **Redirige al pagador** — tu frontend envía al cliente a esa URL. PSE maneja la selección de banco y la autorización.
3. **Recibe el resultado** — Mono dispara un webhook `collection_intent_credited` a tu endpoint cuando el pago se settle, o `collection_intent_failed` si es rechazado.
4. **Concilia** — tu sistema actualiza la orden y acredita al cliente.
## Pasos [#pasos]
El paso a paso completo (con ejemplos en curl, Node.js y Python) está pendiente. Hasta que se publique, ver:
* La [página de concepto de enlaces de recaudo](/docs/guides/banking/concepts/collection-links) para el modelo de datos y el ciclo de vida.
* La [página de webhooks de enlaces de recaudo](/docs/guides/banking/webhooks/collection-links) para los payloads de los eventos `collection_intent_credited` y `collection_intent_failed`.
## Siguientes pasos [#siguientes-pasos]
* [Envío de transferencias](/docs/guides/banking/flows/sending-transfers) — la contraparte de dispersión de este flujo.
* [Webhooks de Banking](/docs/guides/banking/webhooks) — los eventos que guían la conciliación.
* [Sandbox](/docs/guides/banking/sandbox) — prueba el flujo sin mover dinero real.
# Emisión de tarjetas (/es/docs/guides/banking/flows/cards)
Una **tarjeta** es una tarjeta débito — virtual o física — que Mono aprovisiona contra una de tus cuentas de Banking en alianza con Visa. La emisión de tarjetas es el flujo que tu negocio corre cuando quieres darle a un account holder una forma de gastar su saldo en cualquier comercio habilitado para Visa directamente desde Mono, sin requerir una transferencia separada.
Usa este flujo para tarjetas de gastos de empleados, tarjetas prepago para clientes, tarjetas de gasto para vendedores de un marketplace, o cualquier caso donde un account holder necesite una credencial de tarjeta vinculada a su cuenta Mono.
Esta página es un esqueleto de flujo. La versión completa incluirá ejemplos de código para
creación, activación y controles de tarjeta (freeze, actualización de cupos), webhooks
esperados y errores comunes.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una cuenta Mono con Banking (Tarjetas) habilitado y una API key. Ver [Autenticación](/docs/guides/api-standards/authentication).
* Una cuenta de Banking ya creada a la que la tarjeta estará vinculada.
* Un endpoint de webhook suscrito a [eventos de transacciones de tarjeta](/docs/guides/core/webhooks/card-transactions) para la actividad de gasto una vez la tarjeta esté emitida.
* Una [clave de idempotencia](/docs/guides/api-standards/idempotency-keys) por cada solicitud de creación de tarjeta para que los reintentos sean seguros.
## Vista general de la secuencia [#vista-general-de-la-secuencia]
1. **Solicita una tarjeta** — tu operador o aplicación dispara la creación de tarjeta para un account holder específico y un tipo de tarjeta (virtual o física).
2. **Llama a Mono** — tu backend envía la solicitud de creación de tarjeta con el id de la cuenta, datos del holder y una clave de idempotencia.
3. **Mono aprovisiona con la red de tarjetas** — Mono solicita las credenciales a Visa y recibe un número de tarjeta tokenizado, fecha de expiración y CVV.
4. **Recibe la tarjeta** — Mono devuelve síncronamente el id de la tarjeta y el PAN enmascarado. Las tarjetas virtuales regresan en estado `active` y se pueden usar inmediatamente. Las tarjetas físicas regresan en estado `created` y deben ser activadas por el holder en la app de Mono antes de que funcionen.
5. **Entrega al holder** — tu backend expone los detalles de la tarjeta al operador o al account holder. Para tarjetas físicas, también comunica el paso de activación.
## Pasos [#pasos]
El paso a paso completo (con ejemplos en curl, Node.js y Python para creación, configuración de PIN y controles de tarjeta) está pendiente. Hasta que se publique, ver:
* La [página de webhooks de transacciones de tarjeta](/docs/guides/core/webhooks/card-transactions) para los payloads de eventos de gasto y autorización.
* [Claves de idempotencia](/docs/guides/api-standards/idempotency-keys) para reintentos seguros en solicitudes de creación.
## Siguientes pasos [#siguientes-pasos]
* [Recaudo PSE](/docs/guides/banking/flows/pse-collection) — acepta pagos a la cuenta que fondea la tarjeta.
* [Webhooks de Banking](/docs/guides/banking/webhooks) — los eventos que rastrean el ciclo de vida y la actividad de gasto de la tarjeta.
* [Sandbox](/docs/guides/banking/sandbox) — emite una tarjeta de prueba sin activar credenciales reales de la red de tarjetas.
# Flujos (/es/docs/guides/banking/flows)
Un **flujo** es un caso de uso completo, de punta a punta, que compone varios conceptos de Banking. Mientras que la sección [Conceptos](/docs/guides/banking/concepts) responde "¿qué es esta entidad?", los flujos responden "¿cómo meto o saco dinero usando estas entidades?".
Los flujos se agrupan por capacidad comercial:
* **Recaudo** — entrar dinero a tu cuenta desde un cliente.
* **Dispersión** — enviar dinero desde tu cuenta a un beneficiario.
* **Tarjetas** — emitir tarjetas débito vinculadas a una cuenta de Banking.
## Flujos disponibles [#flujos-disponibles]
Genera un enlace de pago, redirige al pagador a su banco y concilia el evento de settlement.
Despacha transferencias puntuales o por lote por los rails ACH, Transfiya y Mono Turbo con
manejo de estado y confirmación.
Aprovisiona una tarjeta débito virtual o física contra una cuenta de Banking y rastrea su
ciclo de vida vía webhooks.
## Siguientes pasos [#siguientes-pasos]
* [Conceptos de Banking](/docs/guides/banking/concepts) — las entidades que cada flujo compone.
* [Webhooks de Banking](/docs/guides/banking/webhooks) — eventos que recibes durante un flujo.
* [Sandbox](/docs/guides/banking/sandbox) — prueba un flujo sin mover dinero real.
# Envío de transferencias (/es/docs/guides/banking/flows/sending-transfers)
Una **transferencia bancaria** es una dispersión: el dinero sale de tu cuenta Mono y llega a la cuenta bancaria de un beneficiario. Mono enruta transferencias por tres rails — **ACH** para transferencias estándar de mismo día, **Transfiya** para transferencias dentro de un clúster específico de bancos, y **Mono Turbo** para transferencias en tiempo real a bancos participantes — y cae automáticamente cuando un rail no soporta un destino.
Usa este flujo para corridas de nómina, settlement a proveedores, dispersiones a vendedores de marketplace, devoluciones o cualquier caso donde tu negocio empuja dinero hacia una cuenta bancaria conocida.
Esta página es un esqueleto de flujo y se expandirá con ejemplos de código para transferencias
puntuales y por lote, webhooks esperados, errores comunes y guía de conciliación.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una cuenta Mono con Banking habilitado y una API key. Ver [Autenticación](/docs/guides/api-standards/authentication).
* Saldo suficiente en la cuenta origen para cubrir la transferencia más cualquier comisión.
* Un endpoint de webhook que pueda recibir [eventos de transferencia bancaria](/docs/guides/banking/webhooks/bank-transfers).
* Familiaridad con el [concepto de transferencias](/docs/guides/banking/concepts/transfers).
* Una [clave de idempotencia](/docs/guides/api-standards/idempotency-keys) por cada transferencia para que los reintentos sean seguros.
## Vista general de la secuencia [#vista-general-de-la-secuencia]
1. **Prepara la transferencia** — recolecta la cuenta destino, el monto, la moneda y la preferencia de routing (`turbo`, `transfiya` o `ach`). Genera una clave de idempotencia.
2. **Envía** — llama `POST /transfers`. Mono crea la transferencia en estado `created` y la agrupa en un lote. La respuesta confirma aceptación, no crédito.
3. **Recibe confirmación** — Mono dispara `bank_transfer_approved` cuando el rail acredita el destino, o `bank_transfer_rejected` si es rechazada. Los estados terminales de la transferencia son `approved` y `declined`.
4. **Maneja los fallbacks** — si el rail primario falla, Mono reintenta usando el `fallback_routing` que especificaste y dispara `bank_transfer_fallback_routing` antes del resultado final.
## Pasos [#pasos]
El paso a paso completo (transferencias puntuales y por lote) está pendiente. Hasta que se publique, ver:
* La [página de concepto de transferencias](/docs/guides/banking/concepts/transfers) para opciones de routing, fallbacks y estados.
* La [página de webhooks de transferencias bancarias](/docs/guides/banking/webhooks/bank-transfers) para los payloads de los eventos.
## Siguientes pasos [#siguientes-pasos]
* [Recaudo PSE](/docs/guides/banking/flows/pse-collection) — la contraparte de recaudo de este flujo.
* [Webhooks de Banking](/docs/guides/banking/webhooks) — los eventos que guían la confirmación y el manejo de fallbacks.
* [Claves de idempotencia](/docs/guides/api-standards/idempotency-keys) — requeridas para reintentos seguros en cada transferencia.
# Casos de uso de Recaudo (/es/docs/guides/breb-participant/use-cases/collections)
Un **recaudo** es cómo tu negocio recibe dinero por Bre-B: lo creas, compartes su llave de pago o QR, y Mono te notifica a medida que los pagadores pagan. La misma entidad cubre una venta única, un plan mensual, una donación abierta o un pedido de marketplace — lo que cambia es la configuración. Esta página lista los escenarios comunes y los campos exactos a definir para cada uno.
Lee primero el [concepto de recaudos](/docs/guides/breb-participant/concepts/collections) si estos términos son nuevos. Los montos están en **centavos** (1 COP = 100 centavos), la unidad que espera la API.
Estas recetas nombran solo campos que existen en el request de creación de recaudo. Para el
request y response completos, ve [Crear
recaudo](/docs/api-reference/breb-participant/collections/create_collections).
## Antes de empezar [#antes-de-empezar]
Necesitarás:
* Una cuenta tenant registrada (`tenant_account_id`) para recibir los fondos.
* Un endpoint de webhook para recibir eventos de recaudo — ve [Payloads de webhooks](/docs/guides/breb-participant/webhook-payloads).
* Familiaridad con el [ciclo de vida del recaudo](/docs/guides/breb-participant/architecture/collection-states) (`ready → minimum_paid → paid`).
## Referencia rápida [#referencia-rápida]
| Escenario | `usage_mode` | Control de monto | Compartir |
| -------------------- | -------------- | -------------------------------------- | -------------------------------- |
| Compra de producto | `single_use` | total fijo | QR dinámico |
| Carrito de compra | `single_use` | solo total máximo | sin QR (dinámico) |
| Suscripción | `multiple_use` | por pago = cuota | ninguno (recurrente) |
| Donación libre | `multiple_use` | ninguno (abierto) | QR estático |
| Cuotas flexibles | `multiple_use` | total + mín/máx por pago | ninguno |
| Factura a cliente | `single_use` | total fijo | QR dinámico + `expected_payers` |
| Venta flash | `single_use` | total fijo | QR dinámico, `expires_in` corto |
| Gift card / recarga | `multiple_use` | tope total + mín/máx por pago | QR estático |
| Deuda variable | `multiple_use` | máximo por pago, actualizado por PATCH | QR estático |
| Split de marketplace | `single_use` | total fijo | QR dinámico, split en `metadata` |
## UC1 — Compra de producto (precio fijo) [#uc1--compra-de-producto-precio-fijo]
Un cliente paga un monto fijo una vez. Pon el piso y el techo del total iguales para que el recaudo se cierre con el primer pago exitoso.
```json
{
"usage_mode": "single_use",
"total_minimum_amount": 5000000,
"total_maximum_amount": 5000000,
"expires_in": 3600,
"metadata": { "product_id": "SKU-128", "product_name": "Teclado inalámbrico" }
}
```
`5000000` centavos = $50,000 COP. Compártelo como **QR dinámico** con ese monto y expiración. El recaudo llega a `paid` con el primer pago exitoso.
## UC2 — Carrito de compra (monto dinámico) [#uc2--carrito-de-compra-monto-dinámico]
El total es lo que sume el carrito, calculado en el checkout. Define solo `total_maximum_amount`; omite el mínimo.
```json
{
"usage_mode": "single_use",
"total_maximum_amount": 12000000,
"metadata": { "order_id": "ORD-9931", "item_count": "4" }
}
```
Como el cobro exacto se decide al pagar, comparte la llave de pago en lugar de un QR de monto fijo.
## UC3 — Suscripción / plan de pagos (cuotas fijas) [#uc3--suscripción--plan-de-pagos-cuotas-fijas]
Un plan pagado en cuotas iguales. Acota el total de por vida y fija cada pago al valor de la cuota.
```json
{
"usage_mode": "multiple_use",
"total_maximum_amount": 60000000,
"minimum_attempt_amount": 5000000,
"maximum_attempt_amount": 5000000
}
```
Doce cuotas de $50,000 COP hacia un plan de $600,000. El recaudo progresa `ready → minimum_paid → paid` a medida que llegan las cuotas; sin QR, porque es recurrente.
## UC4 — Donación libre (abierta) [#uc4--donación-libre-abierta]
Acepta cualquier monto, cualquier número de veces, sin fin. Omite ambos montos totales para que el recaudo nunca llegue a `paid` y permanezca en `ready`.
```json
{
"usage_mode": "multiple_use",
"custom_key_value": "REFOREST",
"custom_merchant_name": "Fundación VerdeVida"
}
```
`custom_key_value` hace la llave legible (p. ej. `@MN{random}REFOREST`) y es **inmutable** una vez creada. Compártela como **QR estático** en volantes o redes sociales.
## UC5 — Cuotas flexibles (límites mín/máx) [#uc5--cuotas-flexibles-límites-mínmáx]
El pagador elige cuánto pagar cada vez, dentro de límites, hacia una meta con un depósito mínimo.
```json
{
"usage_mode": "multiple_use",
"total_maximum_amount": 300000000,
"total_minimum_amount": 50000000,
"minimum_attempt_amount": 10000000,
"maximum_attempt_amount": 100000000
}
```
Una meta de $3,000,000 con un depósito mínimo de $500,000; cada pago entre $100,000 y $1,000,000. Los pagos por debajo de `minimum_attempt_amount` o por encima de `maximum_attempt_amount` se rechazan; el recaudo llega a `minimum_paid` cuando se cumple el piso, y a `paid` en el techo.
## UC6 — Factura a cliente (pagador restringido) [#uc6--factura-a-cliente-pagador-restringido]
Una factura pagable solo por el cliente nombrado. Usa `expected_payers` para restringir quién puede pagar y `reference` para llevar el número de factura.
```json
{
"usage_mode": "single_use",
"total_minimum_amount": 18000000,
"total_maximum_amount": 18000000,
"expected_payers": [{ "document_type": "CC", "document_number": "1023711432" }],
"custom_merchant_name": "Distribuciones Andinas S.A.S.",
"reference": "FAC-2024-001",
"expires_in": 86400
}
```
Compártela como **QR dinámico** adjunto a la factura (expiración de 24 horas arriba).
## UC7 — Venta flash (ventana corta) [#uc7--venta-flash-ventana-corta]
Una oferta por tiempo limitado. Usa un `expires_in` muy corto; si nadie paga a tiempo, el recaudo pasa a `discarded`.
```json
{
"usage_mode": "single_use",
"total_minimum_amount": 2500000,
"total_maximum_amount": 2500000,
"expires_in": 30
}
```
Renderiza el **QR dinámico** con una cuenta regresiva que coincida.
## UC8 — Gift card / recarga (con tope, reutilizable) [#uc8--gift-card--recarga-con-tope-reutilizable]
Un saldo cargado en varios pagos hasta un tope, pagable por cualquiera que tenga la tarjeta.
```json
{
"usage_mode": "multiple_use",
"custom_key_value": "GIFT4MARIA",
"total_maximum_amount": 50000000,
"minimum_attempt_amount": 1000000,
"maximum_attempt_amount": 20000000
}
```
Imprime el **QR estático** en la tarjeta. El recaudo llega a `paid` cuando se llena el tope.
## UC9 — Deuda variable (tope dinámico) [#uc9--deuda-variable-tope-dinámico]
Un saldo rotativo — p. ej. una línea de crédito — donde el techo por pago cambia con el tiempo. Mantenlo abierto (sin totales) y actualiza `maximum_attempt_amount` a medida que cambia el saldo.
```json
{
"usage_mode": "multiple_use",
"custom_key_value": "CARD-7781",
"maximum_attempt_amount": 80000000
}
```
Ajusta el techo después con [Actualizar recaudo](/docs/api-reference/breb-participant/collections/update_collection) (PATCH); el cambio dispara un webhook `collection.updated` con los valores previos. La llave en sí es inmutable.
## UC10 — Split de marketplace [#uc10--split-de-marketplace]
Un solo cobro que tu backend luego reparte entre varios destinatarios. Codifica las reglas de split como `metadata` **plana** (los metadata de Bre-B no pueden anidarse), y ejecuta los pagos tras la liquidación.
```json
{
"usage_mode": "single_use",
"total_minimum_amount": 10000000,
"total_maximum_amount": 10000000,
"metadata": {
"split_1_recipient_key": "@MNVENDEDOR1",
"split_1_type": "percentage",
"split_1_value": "80",
"split_2_recipient_key": "@MNPLATFORM",
"split_2_type": "percentage",
"split_2_value": "10",
"split_3_recipient_key": "shipping@logistics.com",
"split_3_type": "fixed",
"split_3_value": "500000"
}
}
```
Cuando llega el webhook `collection.paid`, lee las reglas de split desde `metadata` y ejecuta los pagos — ve la receta de split en [Casos de uso de Dispersiones](/docs/guides/breb-participant/use-cases/outgoing-transfers).
## Próximos pasos [#próximos-pasos]
* [Casos de uso de Dispersiones](/docs/guides/breb-participant/use-cases/outgoing-transfers) — paga a los destinatarios (y completa el split de marketplace).
* [Sandbox: recaudos](/docs/guides/breb-participant/sandbox/collections) — simula pagos contra cualquier receta de arriba.
* [Crear recaudo](/docs/api-reference/breb-participant/collections/create_collections) — el esquema completo de request y response.
# Casos de uso de Dispersiones (/es/docs/guides/breb-participant/use-cases/outgoing-transfers)
Una **transferencia saliente** es cómo tu negocio envía dinero por Bre-B: nombras a un destinatario por su llave de pago (o un target ya resuelto), Mono enruta los fondos y los webhooks siguen el resultado. El mismo endpoint cubre un pago único a un vendedor, un reembolso verificado o un lote de cientos — lo que cambia es la configuración. Esta página lista los escenarios comunes y los campos a definir para cada uno.
Lee primero [Llaves de pago](/docs/guides/breb-participant/concepts/payment-keys) y [Targets](/docs/guides/breb-participant/concepts/targets) si estos términos son nuevos. Los montos están en **centavos** (1 COP = 100 centavos).
Estas recetas nombran solo campos que existen en el request de creación de transferencia. Para
el request y response completos, ve [Crear transferencias
salientes](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers).
## Antes de empezar [#antes-de-empezar]
Necesitarás:
* Una cuenta tenant con fondos (`tenant_account_id`) desde la cual enviar.
* Un endpoint de webhook para eventos de transferencia y de resolución de target — ve [Payloads de webhooks](/docs/guides/breb-participant/webhook-payloads).
* Familiaridad con el [ciclo de vida de la transferencia saliente](/docs/guides/breb-participant/architecture/outgoing-transfer-states) y la [referencia de errores](/docs/guides/breb-participant/outgoing-transfer-errors).
## Cómo una transferencia nombra a su destinatario [#cómo-una-transferencia-nombra-a-su-destinatario]
Cada transferencia en el arreglo `transfers` identifica a su destinatario de una de dos formas:
* **Por llave** — un `query` que Mono resuelve por ti: `{ "query": { "value": "3001234567" } }`. La transferencia pasa por `target_resolved` antes de liquidarse.
* **Por target** — un `target_id` que ya resolviste (ve UC-OT4 abajo): `{ "target_id": "bbtgt_..." }`. Esto omite el paso de resolución.
Opcionalmente agrega `expected_creditor` (`document_type` + `document_number`) para rechazar la transferencia si el dueño resuelto no coincide. Cuando transfieres por target, envíalo en la llamada de resolución (ve UC-OT4): el `target_id` que recibes ya apunta a la persona esperada, y la transferencia lleva el mismo `expected_creditor`.
## UC-OT1 — Pago a vendedor (una transferencia) [#uc-ot1--pago-a-vendedor-una-transferencia]
Paga a un vendedor por su llave. Un lote de un solo ítem sigue siendo un lote.
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"description": "Pago a vendedor — pedido ORD-9931",
"transfers": [
{
"query": { "value": "@MNVENDEDOR1" },
"amount": { "amount": 8000000, "currency": "COP" },
"external_id": "payout-ORD-9931"
}
]
}
```
`8000000` centavos = $80,000 COP. La transferencia recorre la máquina de estados completa `created → processing → target_resolved → ... → successful`, emitiendo webhooks en cada paso. Este es el pago que completa un [split de marketplace](/docs/guides/breb-participant/use-cases/collections).
## UC-OT2 — Reembolso a cliente (verificación de identidad) [#uc-ot2--reembolso-a-cliente-verificación-de-identidad]
Devuelve fondos a una persona específica, rechazando el pago si la llave ahora pertenece a alguien más. Agrega `expected_creditor`.
```json
{
"query": { "value": "3001234567" },
"amount": { "amount": 5000000, "currency": "COP" },
"expected_creditor": { "document_type": "CC", "document_number": "1023711432" },
"external_id": "refund-INV-552"
}
```
Si el dueño resuelto coincide, la transferencia se liquida. Si no, falla con `target_creditor_mismatch` — ve los [errores de validación de destinatario](/docs/guides/breb-participant/outgoing-transfer-errors). En sandbox, fuerza el mismatch con la llave `sandbox@key_target_creditor_mismatch.err`.
## UC-OT3 — Split post-venta (lote) [#uc-ot3--split-post-venta-lote]
Paga a varios destinatarios desde una venta liquidada, mezclando tipos de llave en un solo lote. Combínalo con el [recaudo de split de marketplace](/docs/guides/breb-participant/use-cases/collections): lee las reglas de split del webhook `collection.paid`, y luego envía.
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"description": "Split del pedido ORD-9931",
"transfers": [
{
"query": { "value": "@MNVENDEDOR1" },
"amount": { "amount": 8000000, "currency": "COP" },
"external_id": "split-1-vendor"
},
{
"query": { "value": "@MNPLATFORM" },
"amount": { "amount": 1000000, "currency": "COP" },
"external_id": "split-2-platform"
},
{
"query": { "value": "shipping@logistics.com" },
"amount": { "amount": 500000, "currency": "COP" },
"external_id": "split-3-shipping"
}
]
}
```
Cada transferencia se sigue de forma independiente por su `external_id`; que una falle no revierte las demás.
## UC-OT4 — Pre-resolución (resolver → confirmar → transferir) [#uc-ot4--pre-resolución-resolver--confirmar--transferir]
Cuando quieres que el pagador confirme al destinatario antes de mover dinero, resuelve la llave primero, muestra el nombre resuelto, y luego transfiere por `target_id`.
1. **Resolver** — llama a [Resolver target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target) con la llave. Si ya sabes quién debe ser su dueño, agrega `expected_creditor`: Mono resuelve solo cuando el documento del titular coincide, y de lo contrario responde `400` con `target_creditor_mismatch` y sin `target_id`.
2. **Confirmar** — muestra el nombre del titular, la institución (`participant_name`) y la cuenta enmascarada devueltos a tu usuario.
3. **Transferir** — crea la transferencia con el `target_id` del paso 1 (sin `query`).
Request del paso 1:
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"format": "plain_key",
"value": "3001234567",
"expected_creditor": { "document_type": "CC", "document_number": "1023711432" }
}
```
Request del paso 3:
```json
{
"transfers": [
{
"target_id": "bbtgt_5tgliBmzjZ6mpQPRbQjfKj",
"amount": { "amount": 12000000, "currency": "COP" },
"external_id": "confirmed-transfer-1"
}
]
}
```
Como el destinatario ya está resuelto, la transferencia omite `target_resolved` y pasa directo a la liquidación. Cuando la resolución llevaba un `expected_creditor`, la transferencia lo copia, así que su respuesta de API y sus webhooks muestran el mismo `expected_creditor` que una transferencia por llave.
## UC-OT5 — Pagos a proveedores en lote (resultados mixtos) [#uc-ot5--pagos-a-proveedores-en-lote-resultados-mixtos]
Un lote más grande donde algunas entradas tienen éxito y otras se rechazan. El response de creación devuelve las transferencias aceptadas y una lista `rejected_transfers`; las fallas de resolución (como una llave incorrecta) llegan después como webhooks `outgoing_transfer.failed`.
Maneja ambos: reconcilia el `rejected_transfers` síncrono (p. ej. `amount_exceeds_max_limit`, duplicados) y suscríbete a los webhooks de falla para los resultados por transferencia. Ve [Manejo de transferencias rechazadas](/docs/guides/breb-participant/outgoing-transfer-errors).
## UC-OT6 — Todos los tipos de llave [#uc-ot6--todos-los-tipos-de-llave]
Una transferencia puede apuntar a cualquiera de los cinco [tipos de llave](/docs/guides/breb-participant/concepts/payment-keys). Usa el formato correcto de `query.value`:
| Tipo de llave | Ejemplo de `query.value` |
| ------------- | ------------------------ |
| Cellphone | `3001234567` |
| Document | `21482961` |
| Email | `vendedor@tienda.com` |
| Alphanumeric | `@MNVENDEDOR1` |
| Merchant code | `0012345678` |
Mono resuelve cada una al mismo tipo de target; los datos resueltos que se muestran difieren por tipo.
## UC-OT7 — Manejo de errores (sandbox) [#uc-ot7--manejo-de-errores-sandbox]
Ejercita cada ruta de falla de forma determinista en sandbox antes de salir a producción:
* **Errores de resolución** — pon `query.value` en una llave de prueba como `sandbox@key_not_found.err`, `sandbox@key_suspended.err` o `sandbox@key_target_creditor_mismatch.err`. Las mismas llaves funcionan como `value` de [Resolver target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target): con un `expected_creditor`, la llave de mismatch responde `400 target_creditor_mismatch` y una llave que no sea disparador de sandbox se resuelve al creditor que esperas.
* **Errores de liquidación** — codifica el disparador en el `description` de la transferencia (p. ej. timeout, control de riesgo, cuenta de acreedor no encontrada).
* **Rechazos de creación** — los requests malformados (sin monto, target desconocido, monto sobre el límite) se rechazan de forma síncrona sin webhooks.
La lista completa de códigos `state_reason`, sus significados y el manejo recomendado está en la [referencia de errores de transferencias salientes](/docs/guides/breb-participant/outgoing-transfer-errors). Recorre los disparadores de punta a punta en [Sandbox: transferencias salientes](/docs/guides/breb-participant/sandbox/outgoing-transfers).
## Próximos pasos [#próximos-pasos]
* [Casos de uso de Recaudo](/docs/guides/breb-participant/use-cases/collections) — el lado de recepción (y el split que alimenta a UC-OT3).
* [Flujo de transferencia saliente](/docs/guides/breb-participant/flows/outgoing-transfer) — la secuencia de punta a punta y la máquina de estados.
* [Sandbox: transferencias salientes](/docs/guides/breb-participant/sandbox/outgoing-transfers) — simula cualquier receta de arriba.
# Casos de uso (/es/docs/guides/breb-participant/use-cases)
Bre-B Participant te da un conjunto pequeño de campos que, combinados, cubren la mayoría de formas en que un negocio recibe o envía dinero. Un **caso de uso** es una combinación recomendada de esos campos para un escenario concreto — una compra única, una suscripción, una donación, un pago a proveedores. Esta sección convierte "¿qué configuro para X?" en una receta que puedes copiar.
No existe un endpoint especial de "suscripción" o "donación". Siempre creas un recaudo (para recibir) o una transferencia saliente (para enviar); la *configuración* que eliges es lo que hace que se comporte como una suscripción o una donación.
Cada campo que se usa aquí es parte de la API en vivo. Las recetas nombran los campos y su
efecto — para el request y response completos, sigue el enlace a la referencia de la API en
cada página.
## Las decisiones que definen un recaudo [#las-decisiones-que-definen-un-recaudo]
Cuando recibes dinero (Recaudo), cuatro decisiones moldean el comportamiento:
* **¿Cuántas veces se puede pagar?** `usage_mode` es `single_use` (acepta un solo pago exitoso y se cierra) o `multiple_use` (sigue aceptando pagos hasta alcanzar un tope, o indefinidamente).
* **¿Cuánto, en total?** `total_maximum_amount` y `total_minimum_amount` acotan la vida del recaudo. Ponlos iguales para un precio fijo; omite ambos para dejarlo abierto indefinidamente.
* **¿Cuánto por pago?** `minimum_attempt_amount` y `maximum_attempt_amount` acotan cada pago individual — útil para cuotas y recargas.
* **¿Cómo se comparte?** Un **QR dinámico** lleva un monto fijo y una expiración corta (un checkout). Un **QR estático** no lleva monto y se puede imprimir o publicar para muchos pagadores.
Dos campos opcionales marcan el pago: `custom_key_value` (un sufijo legible en la llave de pago, p. ej. `@MN{random}REFOREST`) y `custom_merchant_name` (el nombre que ve el pagador).
## Las decisiones que definen una transferencia saliente [#las-decisiones-que-definen-una-transferencia-saliente]
Cuando envías dinero (Dispersiones), las decisiones son sobre el destinatario y la confianza:
* **¿Quién la recibe?** Provee un `query.value` (una llave de pago que Mono resuelve por ti) o un `target_id` ya resuelto.
* **¿Necesitas confirmar la identidad primero?** `expected_creditor` hace que Mono rechace la transferencia — o la resolución del target, cuando resuelves primero — si el destinatario resuelto no coincide con quien esperabas. Esencial para reembolsos.
* **¿Una o muchas?** Un solo request puede llevar una transferencia o un lote de muchas, cada una con seguimiento independiente.
## Elige tu rail [#elige-tu-rail]
Compra de producto, carrito, suscripción, donación, cuotas, factura, venta flash, gift card,
deuda variable, split de marketplace.
Pago a vendedor, reembolso con verificación de identidad, lotes, pre-resolución, lotes con
resultados mixtos, todos los tipos de llave, manejo de errores.
## Próximos pasos [#próximos-pasos]
* [Concepto de recaudos](/docs/guides/breb-participant/concepts/collections) — la entidad que estas recetas configuran.
* [Estados del recaudo](/docs/guides/breb-participant/architecture/collection-states) — cómo funciona `ready → minimum_paid → paid`.
* [Sandbox: recaudos](/docs/guides/breb-participant/sandbox/collections) — prueba una receta de punta a punta sin un banco real.
# Recaudos (/es/docs/guides/core/webhooks/payins)
Cada entidad de recaudos reporta su ciclo de vida por webhooks: 7 eventos de la plantilla, 7 de la intención de pago, 5 del pago y 3 del lote de liquidación (22 en total). Cada payload lleva el recurso completo, con la misma forma en que lo devuelve la API. Esta página ubica cada evento en la línea de tiempo; los payloads exactos viven en la [referencia de API de webhooks de recaudos](/docs/api-reference/core/payin-webhooks). Para la verificación de firma, los reintentos y las convenciones de campos de dinero compartidas por todos los webhooks de Mono, ver [Webhook conventions](/docs/guides/api-standards/webhooks).
## El camino feliz, en orden [#el-camino-feliz-en-orden]
Para un recaudo a través de una intención de pago, un solo pago produce esta secuencia:
Los recaudos sin intenciones de pago se saltan los cuatro eventos de la intención de pago; los eventos de la plantilla se disparan una vez por plantilla, no por pago.
## Eventos de la plantilla [#eventos-de-la-plantilla]
| Evento | Llega cuando |
| :----------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`payin_template_created`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_created) | La plantilla es aceptada y persistida. Sus tokens de pago aún se están preparando (`payment_tokens` es null). Todavía no puede recibir pagos. |
| [`payin_template_ready`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_ready) | Terminó el setup del token: la plantilla puede recibir pagos, y `payment_tokens` lleva una entrada por método que emite token (para Bre-B, la llave y el QR). |
| [`payin_template_updated`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_updated) | Cada update exitoso. Lleva `previous_values` con los campos que cambiaron, incluida una transición de estado causada por mover los totales, que **no** dispara los eventos de estado separados. |
| [`payin_template_minimum_paid`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_minimum_paid) | El `paid_amount` acumulado alcanza por primera vez `total_minimum_amount`. La plantilla sigue aceptando pagos. |
| [`payin_template_paid`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_paid) | Se alcanza `total_maximum_amount`, o una plantilla `single_use` recibió su pago. No se aceptan más pagos. |
| [`payin_template_discarded`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_discarded) | La plantilla fue eliminada, expiró o fue barrida por inactividad; sus tokens fueron dados de baja. `prune_reason` dice cuál fue el caso. |
| [`payin_template_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_failed) | El setup del token agotó sus reintentos. `state_reason` lo explica (p. ej. `key_already_registered`). |
## Eventos de la intención de pago [#eventos-de-la-intención-de-pago]
| Evento | Llega cuando |
| :--------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`payin_intent_created`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_created) | La intención de pago es aceptada y persistida. Su token aún se está preparando. El pagador todavía no puede pagar. |
| [`payin_intent_ready_to_pay`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_ready_to_pay) | La intención de pago se vuelve pagable: `payment_tokens` y `expires_at` quedan definidos. También se reenvía cuando la intención de pago se rearma tras un intento fallido. |
| [`payin_intent_awaiting_payment`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_awaiting_payment) | Un pago contra la intención de pago fue autorizado y está en vuelo. |
| [`payin_intent_attempt_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_attempt_failed) | Un intento de pago falló después de la autorización pero la intención de pago sobrevive: está de vuelta en `ready_to_pay` para un reintento. El intento en sí llega como `payin_rejected` o `payin_failed`. |
| [`payin_intent_successful`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_successful) | La intención de pago fue consumida por su pago exitoso. Final: tokens dados de baja. |
| [`payin_intent_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_failed) | La intención de pago falla definitivamente: falló el registro del token, o un pago falló sin rearme. `state_reason` lo explica. |
| [`payin_intent_expired`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_expired) | La ventana se cerró sin que nadie pagara. El `external_id` queda libre para una intención de pago nueva. |
## Eventos del pago [#eventos-del-pago]
| Evento | Llega cuando |
| :--------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`payin_created`](/docs/api-reference/core/payin-webhooks/webhook_payin_created) | Un pago se persiste por primera vez, antes de la confirmación del proveedor. Lleva quién pagó (`instrument.payer`) y a qué llave (`recipient_data`). |
| [`payin_approved_in_provider`](/docs/api-reference/core/payin-webhooks/webhook_payin_approved_in_provider) | El proveedor confirmó el pago. El dinero existe del lado del proveedor; la subcuenta aún no se acredita. |
| [`payin_subaccount_credited`](/docs/api-reference/core/payin-webhooks/webhook_payin_subaccount_credited) | La subcuenta fue acreditada por `subaccount_amount`. Para la mayoría de las integraciones, la señal de "pago completo". |
| [`payin_rejected`](/docs/api-reference/core/payin-webhooks/webhook_payin_rejected) | Una condición de negocio, configuración o del pagador detuvo el pago. `state_reason` lo explica (p. ej. `amount_exceeds_maximum`, `payer_not_allowed`). Los fondos del pagador, si los hubo, se devuelven. |
| [`payin_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_failed) | Una falla del sistema o del proveedor: `lock_timeout` o `unknown`. |
## Eventos del lote de liquidación [#eventos-del-lote-de-liquidación]
| Evento | Llega cuando |
| :----------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- |
| [`payin_settlement_batch_created`](/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_created) | Pagos pendientes fueron agrupados en un lote. `payins` lista lo que aporta cada uno; el dinero aún no se mueve. |
| [`payin_settlement_batch_processing`](/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_processing) | El proveedor de entrega aceptó la transferencia del lote. |
| [`payin_settlement_batch_completed`](/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_completed) | Los fondos llegaron a tu cuenta de fondeo. |
## Qué no se notifica [#qué-no-se-notifica]
* El breve estado `awaiting_payment` del pago no tiene evento propio; el `payin_intent_awaiting_payment` de la intención de pago cubre ese momento.
* Las transiciones de estado de la plantilla causadas por un **update** viajan dentro de `payin_template_updated`, no como eventos de estado separados.
* Los estados `retryable` y `failed` del lote de liquidación se pueden leer por la API pero nunca se notifican: Mono se está encargando de la entrega y no necesitas hacer nada.
## Siguientes pasos [#siguientes-pasos]
* [Webhook conventions](/docs/guides/api-standards/webhooks): firmas, reintentos y reglas de entrega.
* [Referencia de API de webhooks de recaudos](/docs/api-reference/core/payin-webhooks): el payload exacto de cada evento.
* [Flujos de recaudos](/docs/guides/core/flows/payins): los eventos en el contexto de cada caso de uso.
# Transacciones de tarjeta (/es/docs/guides/core/webhooks/card-transactions)
Cuando se realiza una operación que involucra una transacción de tarjeta, se genera una petición de webhook según el tipo de transacción.
## Tipos de evento [#tipos-de-evento]
| Evento | Descripción |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `authorization_approved` | Se dispara cuando una transacción de tarjeta ha sido autorizada. Se reflejará un cargo sobre el saldo de la cuenta. |
| `authorization_declined` | Se dispara cuando una transacción de tarjeta no ha sido autorizada. Incluye un `declination_reason`. No hay cambio sobre el saldo de la cuenta. |
| `withdrawal` | Se dispara cuando un retiro ha sido autorizado (por ejemplo, ATM). Incluye un detalle `withdrawal_fee`. |
| `credit_adjustment` | Se dispara cuando una autorización se actualiza por un reembolso parcial. Se reflejará un reembolso en el saldo de la cuenta. |
| `debit_adjustment` | Se dispara cuando una autorización se actualiza con un nuevo monto. Se hace un nuevo cargo para actualizar la autorización anterior. |
| `force_debit` | Se dispara cuando una transacción se hace sin una autorización previa. Sin `original_transaction_id` ni `previous_values`. |
| `authorization_reversed` | Se dispara cuando una transacción se revierte y el valor del cargo inicial se devuelve completamente a la cuenta. |
| `refund` | Se dispara cuando se hace un reembolso a la cuenta. El valor puede o no estar relacionado con una autorización previa. |
| `reconciliation` | Se dispara después del settlement final cuando el monto autorizado se ajusta al monto efectivamente liquidado por la red de la tarjeta. |
## Campos nullable [#campos-nullable]
Dependiendo del tipo de notificación, los siguientes campos pueden ser null:
| Campo | Cuándo es null |
| ------------------------- | -------------------------------------------------------------------- |
| `withdrawal_fee` | Cuando no hubo comisión (es decir, la transacción no fue en un ATM). |
| `fx_rates` | Cuando el monto se cargó en COP. |
| `declination_reason` | Cuando la operación fue exitosa. |
| `original_transaction_id` | Cuando la operación no involucra una transacción previa. |
| `previous_values` | Cuando no hay valores previos para comparar. |
***
## Ejemplos de payload [#ejemplos-de-payload]
### authorization\_approved [#authorization_approved]
```json
{
"event": {
"type": "card_transaction",
"data": {
"type": "authorization_approved",
"id": "ctx_5tgliBmzj6thQPRbQjfKj",
"inserted_at": "2023-06-15T10:30:00.000Z",
"state": "approved",
"card": {
"id": "car_5tgliBmlpZ6mpQPRbQjfKj",
"last_four": "1234"
},
"cardholder_id": "cah_7MkWDrqvfosB8fzHhb1Eql",
"declination_reason": null,
"original_transaction_id": null,
"merchant": {
"id": "merchant_789012345",
"name": "Nombre del Comercio",
"mcc": "string",
"amount": {
"amount": 1600,
"currency": "USD"
}
},
"amount": {
"amount": 1600,
"currency": "USD"
},
"settlement_amount": {
"amount": 1600,
"currency": "USD"
},
"withdrawal_fee": null,
"fx_rates": {
"USD": "1",
"COP": "4002,24"
},
"previous_values": null
},
"timestamp": "2023-06-15T10:35:00.000Z"
}
}
```
### authorization\_declined [#authorization_declined]
```json
{
"event": {
"type": "card_transaction",
"data": {
"type": "authorization_declined",
"id": "ctx_5tgliBmzjZ6mpQPRbQjfKj",
"inserted_at": "2023-06-15T10:30:00.000Z",
"state": "declined",
"card": {
"id": "car_5tgliBmzjZ6mpQPRbQjfKj",
"last_four": "1234"
},
"cardholder_id": "cah_7MkWaFqvfosB8fzHhb1Eql",
"declination_reason": "insufficient_funds",
"original_transaction_id": null,
"merchant": {
"id": "merchant_789012345",
"name": "Nombre del Comercio",
"mcc": "string",
"amount": {
"amount": 160,
"currency": "USD"
}
},
"amount": {
"amount": 160,
"currency": "USD"
},
"settlement_amount": {
"amount": 160,
"currency": "USD"
},
"withdrawal_fee": null,
"fx_rates": {
"USD": "1",
"COP": "4002,24"
},
"previous_values": null
},
"timestamp": "2023-06-15T10:35:00.000Z"
}
}
```
### withdrawal [#withdrawal]
```json
{
"event": {
"type": "card_transaction",
"data": {
"type": "withdrawal",
"id": "ctx_5tgliBrejZ6mpQPRbQjfKj",
"inserted_at": "2023-06-15T10:30:00.000Z",
"state": "approved",
"card": {
"id": "car_5tgliBmzjZ6mpQPRbQjfKj",
"last_four": "1234"
},
"cardholder_id": "cah_7MkAqFqvfosB8fzHhb1Eql",
"declination_reason": null,
"original_transaction_id": null,
"withdrawal_fee": {
"amount": 240000,
"currency": "COP"
},
"merchant": {
"id": "merchant_789012345",
"name": "Nombre del Comercio",
"mcc": "string",
"amount": {
"amount": 5000000,
"currency": "COP"
}
},
"amount": {
"amount": 5000000,
"currency": "COP"
},
"settlement_amount": {
"amount": 5000000,
"currency": "COP"
},
"fx_rates": null
},
"timestamp": "2023-06-15T10:35:00.000Z"
}
}
```
### credit\_adjustment [#credit_adjustment]
```json
{
"event": {
"type": "card_transaction",
"data": {
"type": "credit_adjustment",
"id": "ctx_5tgliBmzjZ6mpQSDbQjfKj",
"inserted_at": "2023-06-15T10:30:00.000Z",
"state": "approved",
"card": {
"id": "car_5tgliBmzjZ6mpQPRbQjfKj",
"last_four": "1234"
},
"cardholder_id": "cah_7MkWaFqvfusB8fzHhb1Eql",
"declination_reason": null,
"original_transaction_id": "ctx_5tgliBmzjZ9kpQPRbQjfKj",
"merchant": {
"id": "merchant_789012345",
"name": "Nombre del Comercio",
"mcc": "0000",
"amount": {
"amount": 160000,
"currency": "COP"
}
},
"amount": {
"amount": 160000,
"currency": "COP"
},
"settlement_amount": {
"amount": 160000,
"currency": "COP"
},
"withdrawal_fee": null,
"fx_rates": null,
"previous_values": {
"amount": {
"amount": 1000000,
"currency": "COP"
},
"merchant_amount": {
"amount": 1000000,
"currency": "COP"
}
}
},
"timestamp": "2023-06-15T10:35:00.000Z"
}
}
```
### debit\_adjustment [#debit_adjustment]
```json
{
"event": {
"data": {
"amount": {
"amount": 808000,
"currency": "COP"
},
"card": {
"id": "car_02v8ucNWMnt0qvOIG1Cx9Y",
"last_four": "4952"
},
"cardholder_id": "carh_02v8ucNVmEQWOhFmA8KJX4",
"declination_reason": null,
"fx_rates": null,
"id": "ctx_02vSpSMwSwIETjc7di98zU",
"inserted_at": "2024-01-16T20:06:18.397776Z",
"merchant": {
"amount": {
"amount": 200,
"currency": "USD"
},
"id": "111111111111111",
"mcc": "5045",
"name": "Computer Software"
},
"original_transaction_id": null,
"previous_values": {
"amount": {
"amount": 404000,
"currency": "COP"
},
"merchant_amount": {
"amount": 100,
"currency": "USD"
}
},
"settlement_amount": {
"amount": 100,
"currency": "USD"
},
"state": "approved",
"type": "debit_adjustment",
"withdrawal_fee": null
},
"type": "card_transaction"
},
"timestamp": "2024-01-16T20:07:31.979159Z"
}
```
### force\_debit [#force_debit]
```json
{
"event": {
"data": {
"amount": {
"amount": 404000,
"currency": "COP"
},
"card": {
"id": "car_02v8ucNWMnt0qvOIG1Cx9Y",
"last_four": "4952"
},
"cardholder_id": "carh_02v8ucNVmEQWOhFmA8KJX4",
"declination_reason": null,
"fx_rates": null,
"id": "ctx_02vSmf8pSW4HqcViMjaC6a",
"inserted_at": "2024-01-16T18:12:27.393051Z",
"merchant": {
"amount": {
"amount": 100,
"currency": "USD"
},
"id": "111111111111111",
"mcc": "5045",
"name": "Computer Software"
},
"original_transaction_id": null,
"previous_values": null,
"settlement_amount": {
"amount": 100,
"currency": "USD"
},
"state": "approved",
"type": "force_debit",
"withdrawal_fee": null
},
"type": "card_transaction"
},
"timestamp": "2024-01-16T18:12:40.364194Z"
}
```
### authorization\_reversed [#authorization_reversed]
```json
{
"event": {
"data": {
"amount": {
"amount": 400224,
"currency": "COP"
},
"card": {
"id": "car_02v8ucNWMnt0qvOIG1Cx9Y",
"last_four": "4952"
},
"cardholder_id": "carh_02v8ucNVmEQWOhFmA8KJX4",
"declination_reason": null,
"fx_rates": {
"USD": "1",
"COP": "4002,24"
},
"id": "ctx_02vSq0LFdgOjYzv7vtfbr9",
"inserted_at": "2024-01-16T20:28:38.067403Z",
"merchant": {
"amount": {
"amount": 100,
"currency": "USD"
},
"id": "111111111111111",
"mcc": "5045",
"name": "Computer Software"
},
"original_transaction_id": null,
"previous_values": null,
"settlement_amount": {
"amount": 100,
"currency": "USD"
},
"state": "reversed",
"type": "authorization_reversed",
"withdrawal_fee": null
},
"type": "card_transaction"
},
"timestamp": "2024-01-16T20:29:35.474393Z"
}
```
### refund [#refund]
```json
{
"event": {
"data": {
"amount": {
"amount": 3500000,
"currency": "COP"
},
"card": {
"id": "car_02v8ucNWMnt0qvOIG1Cx9Y",
"last_four": "4952"
},
"cardholder_id": "carh_02v8ucNVmEQWOhFmA8KJX4",
"declination_reason": null,
"fx_rates": null,
"id": "ctx_02vSq0LFdgOjYzv7vtfbr9",
"inserted_at": "2024-01-16T20:28:38.067403Z",
"merchant": {
"amount": {
"amount": 3500000,
"currency": "COP"
},
"id": "111111111111111",
"mcc": "5045",
"name": "Computer Software"
},
"original_transaction_id": null,
"previous_values": null,
"settlement_amount": {
"amount": 2715000,
"currency": "COP"
},
"state": "approved",
"type": "refund",
"withdrawal_fee": null
},
"type": "card_transaction"
},
"timestamp": "2024-01-16T20:29:35.474393Z"
}
```
# Transacciones de cuenta de ledger (/es/docs/guides/core/webhooks/ledger-account-transactions)
Cuando se crea una nueva transacción de cuenta de ledger, se envía un evento de webhook a una URL específica con los detalles de la transacción y su contexto de origen. Esto permite a los clientes recibir actualizaciones en tiempo real sobre movimientos de ledger dentro de sus subcuentas.
## Tipos de evento [#tipos-de-evento]
1. `ledger_account_transaction_created`: Este evento se dispara cuando se registra una nueva transacción de cuenta de ledger para una subcuenta. Incluye los detalles de la transacción y la entidad de origen que la causó.
## Cuenta [#cuenta]
`account_id` es el ID de la subcuenta en la que se registró la transacción, en el mismo formato que `id` en los endpoints de cuentas de ledger. Siempre está presente, así que un consumidor puede atribuir el evento a una cuenta sin leer `details`.
## Detalles por origen [#detalles-por-origen]
El campo `details` contiene la entidad de origen que causó la transacción de ledger. Su estructura varía según el valor de `origin`:
| Origin | Descripción |
| -------------------------- | -------------------------------------------------------------------------------- |
| `subaccount_operation` | Una operación de subcuenta (recarga, retiro, transferencia entre cuentas, etc.). |
| `payout` | Una dispersión a una cuenta bancaria externa. |
| `card_transaction` | Una transacción de tarjeta (compra, retiro en ATM, etc.). |
| `card_presentment_payment` | Settlement final de una transacción internacional de tarjeta. |
| `fee` | Un cobro de comisión. |
| `synced_bank_transaction` | Una transacción bancaria sincronizada desde un banco externo. |
Cuando el origen es desconocido, `details` será `null`.
## Campos nullable [#campos-nullable]
Los siguientes campos pueden ser null dependiendo de la transacción:
* `reverts_id`: null cuando la transacción no revierte otra transacción.
* `reverted_by_id`: null cuando la transacción no ha sido revertida.
* `details`: null cuando la entidad de origen es desconocida o no está disponible.
## Ejemplo de payload [#ejemplo-de-payload]
### Operación de subcuenta (crédito): [#operación-de-subcuenta-crédito]
```json
{
"event": {
"data": {
"id": "ltx_5tgliBmzjZ6mpQPRbQjfKj",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "credit",
"origin": "subaccount_operation",
"id_in_origin": "sao_3RJ9CbZLYq7r1MxH8vAm7Y",
"amount": {
"value": "150000000",
"currency": "COP"
},
"transaction_at": "2025-01-15T14:30:00.000000Z",
"origin_transaction_at": "2025-01-15T14:29:55.000000Z",
"description": "Transfer to savings account",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "sao_3RJ9CbZLYq7r1MxH8vAm7Y",
"state": "successful",
"state_reason": null,
"operation_type": "topup",
"origin_account_id": "lac_7MkWDrqvfosB8fzHhb1Eql",
"destination_account_id": null,
"amount": {
"value": "150000000",
"currency": "COP"
},
"source_amount": {
"value": "150000000",
"currency": "COP"
},
"target_amount": null,
"fx_rate": null,
"external_id": "ext-transfer-12345",
"description": "Transfer to savings account",
"reference": "REF-2025-001",
"metadata": {},
"calculated_fees": []
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-15T14:30:05.000000Z"
}
```
### Dispersión (débito): [#dispersión-débito]
```json
{
"event": {
"data": {
"id": "ltx_2PVWOx9dZKJMBZw7opjrrs",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "debit",
"origin": "payout",
"id_in_origin": "pay_5NYRqKnE17nE0MswylZzq1",
"amount": {
"value": "50000000",
"currency": "COP"
},
"transaction_at": "2025-01-15T16:00:00.000000Z",
"origin_transaction_at": "2025-01-15T15:59:50.000000Z",
"description": "Payout to external bank",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "pay_5NYRqKnE17nE0MswylZzq1",
"account_id": "lac_7MkWDrqvfosB8fzHhb1Eql",
"external_id": "ext-payout-789",
"state": "approved",
"state_reason": null,
"method": "co_ach",
"source_amount": {
"value": "50000000",
"currency": "COP"
},
"target_amount": {
"value": "50000000",
"currency": "COP"
},
"description": "Payout to external bank",
"reference": "PAY-2025-001",
"metadata": {},
"calculated_fees": [],
"inserted_at": "2025-01-15T15:59:50.000000Z",
"updated_at": "2025-01-15T16:00:00.000000Z"
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-15T16:00:05.000000Z"
}
```
### Transacción de tarjeta (débito): [#transacción-de-tarjeta-débito]
```json
{
"event": {
"data": {
"id": "ltx_4kRtaBmzjZ6mpQPRbQjfKj",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "debit",
"origin": "card_transaction",
"id_in_origin": "ctx_6WnYqKnE17nE0MswylZzq1",
"amount": {
"value": "25000000",
"currency": "COP"
},
"transaction_at": "2025-01-15T18:00:00.000000Z",
"origin_transaction_at": "2025-01-15T17:59:45.000000Z",
"description": "Card purchase",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "ctx_6WnYqKnE17nE0MswylZzq1",
"type": "authorization_approved",
"state": "approved",
"amount": {
"value": "25000000",
"currency": "COP"
},
"merchant_amount": {
"value": "6250",
"currency": "USD"
},
"declination_reason": null,
"transaction_at": "2025-01-15T17:59:45.000000Z",
"card_id": "car_5tgliBmzjZ6mpQPRbQjfKj",
"merchant_name": "Almacenes Exito",
"merchant": {
"id": "merchant_789012345",
"name": "Almacenes Exito",
"mcc": "5411",
"country": "COL"
},
"card": {
"id": "car_5tgliBmzjZ6mpQPRbQjfKj",
"last_four": "4952"
}
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-15T18:00:05.000000Z"
}
```
### Card presentment payment (débito): [#card-presentment-payment-débito]
```json
{
"event": {
"data": {
"id": "ltx_8rTsaBmzjZ6mpQPRbQjfKj",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "debit",
"origin": "card_presentment_payment",
"id_in_origin": "cpp_3WnYqKnE17nE0MswylZzq1",
"amount": {
"value": "25000000",
"currency": "COP"
},
"transaction_at": "2025-01-16T10:00:00.000000Z",
"origin_transaction_at": "2025-01-15T18:00:00.000000Z",
"description": "Card presentment settlement",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "cpp_3WnYqKnE17nE0MswylZzq1",
"fx_rate": "4000.00",
"paid_at": "2025-01-16T10:00:00.000000Z"
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-16T10:00:05.000000Z"
}
```
### Comisión (débito): [#comisión-débito]
```json
{
"event": {
"data": {
"id": "ltx_7pQWOx9dZKJMBZw7opjrrs",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "debit",
"origin": "fee",
"id_in_origin": "fee_2DpSchlriwoCuyGMOoIuwp",
"amount": {
"value": "1500000",
"currency": "COP"
},
"transaction_at": "2025-01-15T18:00:01.000000Z",
"origin_transaction_at": "2025-01-15T18:00:00.000000Z",
"description": "Transaction fee",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "fee_2DpSchlriwoCuyGMOoIuwp",
"type": "transaction",
"amount": {
"value": "1500000",
"currency": "COP"
},
"processing_type": "card",
"processing_subtype": "authorization",
"tx_description": "Transaction fee",
"state": "successful",
"transaction_at": "2025-01-15T18:00:00.000000Z",
"origin": "card_transaction",
"id_in_origin": "ctx_6WnYqKnE17nE0MswylZzq1"
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-15T18:00:05.000000Z"
}
```
### Transacción bancaria sincronizada (crédito): [#transacción-bancaria-sincronizada-crédito]
```json
{
"event": {
"data": {
"id": "ltx_6kRtaBmzjZ6mpQPRbQjfKj",
"account_id": "lacc_7MkWDrqvfosB8fzHhb1Eql",
"operation_type": "credit",
"origin": "synced_bank_transaction",
"id_in_origin": "sbt_4NYRqKnE17nE0MswylZzq1",
"amount": {
"value": "200000000",
"currency": "COP"
},
"transaction_at": "2025-01-15T20:00:00.000000Z",
"origin_transaction_at": "2025-01-15T19:55:00.000000Z",
"description": "External bank deposit",
"reverts_id": null,
"reverted_by_id": null,
"details": {
"id": "sbt_4NYRqKnE17nE0MswylZzq1"
}
},
"type": "ledger_account_transaction_created"
},
"timestamp": "2025-01-15T20:00:05.000000Z"
}
```
# Webhooks (/es/docs/guides/core/webhooks)
Los webhooks le notifican a tu sistema en el momento en que ocurre un evento de Core — se autoriza una transacción de tarjeta, una entrada de ledger se liquida, se recibe un pago, una dispersión se completa. Para la mecánica compartida que gobierna todos los webhooks de Mono (verificación de firma, política de reintentos, campos de dinero, restricciones de URL), ver [Webhook conventions](/docs/guides/api-standards/webhooks).
## Eventos de webhook [#eventos-de-webhook]
Autorizaciones, retiros y reembolsos
Eventos de transacciones de cuenta de ledger
Los 22 eventos de plantillas, intenciones de pago, pagos y lotes de liquidación
## Siguientes pasos [#siguientes-pasos]
* [Webhook conventions](/docs/guides/api-standards/webhooks) — verificación de firma, política de reintentos y campos de dinero.
* [Dashboard](/docs/guides/core/dashboard) — configura endpoints de webhook e inspecciona entregas recientes.
# Controles de gasto (/es/docs/guides/core/concepts/spending-controls)
Los controles de gasto son una funcionalidad que te ayuda a crear tarjetas que funcionen según tus necesidades. Esta funcionalidad te permite controlar el gasto mensual, semanal o diario de la tarjeta, y te permite controlar los tipos específicos de transacciones permitidas. Puedes crear controles de gasto tanto para tarjetas físicas como virtuales.
Puedes crear controles de gasto con múltiples reglas y luego asociarlos a tus tarjetas. Estas reglas no tienen que aplicarse a todas tus tarjetas a través de un control de gasto, y se pueden cambiar cuando quieras. Por ejemplo, podrías crear un control de gasto que bloquee tus tarjetas para retiros en ATM; esto se podría aplicar a tarjetas designadas para fines de compra.
## Reglas de controles de gasto [#reglas-de-controles-de-gasto]
Los controles de gasto se definen por un conjunto de reglas que ofrecen un enfoque estructurado para administrar los gastos de tu tarjeta.
### Velocidad [#velocidad]
Esta regla está diseñada para limitar el gasto dentro de una ventana de tiempo predefinida, con la capacidad de definir límites máximos de gasto en base diaria, semanal o mensual. Definir una regla de velocidad puede restringir tu gasto a un monto específico dentro del periodo de tiempo definido, ayudándote a tener mejor control sobre las finanzas de tu empresa o tus empleados.
### Uso de tarjeta [#uso-de-tarjeta]
Esta regla restringe las transacciones de tarjeta a tipos específicos, mejorando la seguridad y personalización de tu control de gasto. Por ejemplo, puedes limitar el uso de tu tarjeta para excluir transacciones en línea, agregando una capa adicional de prevención de fraude.
Los tipos de uso de tarjeta que se pueden controlar son:
* Compra en línea
* Compra física
* Retiro en ATM
### Velocidad de retiro [#velocidad-de-retiro]
Similar a la regla de velocidad, esta regla se enfoca en limitar el monto que se puede retirar de ATMs dentro de un periodo definido. Al aplicar un límite de retiro, ganas control sobre las salidas de efectivo, evitando retiros excesivos y administrando el flujo de efectivo de forma eficiente.
### Merchant Category Code [#merchant-category-code]
Esta regla se enfoca en limitar la categoría de comercios donde tus tarjetas pueden usarse. Por ejemplo, puedes restringir las tarjetas para que solo se usen en comercios relacionados con un sector específico, como estaciones de combustible, ganando así más control sobre tus finanzas.
### Merchant ID [#merchant-id]
Esta regla se enfoca en limitar uno o varios comercios específicos donde tus tarjetas pueden usarse. Estos límites son bloqueadores o permisivos, por ejemplo puedes permitir en tus tarjetas únicamente Facebook Ads, o dejar que tus clientes usen todos los comercios excepto Facebook Ads. Para obtener los merchant ids que necesitas para tu nueva regla, puedes contactar a nuestro equipo de integración.
# Dispersiones (/es/docs/guides/core/concepts/payouts)
Una **dispersión** es un movimiento de fondos hacia afuera de una cuenta de ledger de Core. El destino puede ser otra cuenta de ledger dentro de Core (una transferencia interna) o una cuenta bancaria colombiana externa (en cuyo caso Core delega la ejecución a nivel de riel a Banking). En cualquier caso, la dispersión produce un par balanceado de entradas de ledger: un débito en la cuenta origen y un crédito en la cuenta destino — el mismo patrón de partida doble que sigue cada movimiento en Core.
Usa dispersiones cuando tu producto necesite empujar dinero hacia afuera de un saldo — retiros, settlements de vendedores de marketplace, nómina, reembolsos o barridos de tesorería.
## Qué es [#qué-es]
Una dispersión tiene una cuenta de ledger origen, un destino, un monto, una divisa y un estado. La máquina de estados es:
```
pending → processing → settled
└→ failed
```
Las transiciones exactas y los estados terminales se documentarán en la versión completa de esta página.
## Dispersiones internas vs. externas [#dispersiones-internas-vs-externas]
| Tipo | Destino | Riel |
| :------ | :---------------------------- | :-------------------------------------------------- |
| Interna | Otra cuenta de ledger de Core | Entrada de ledger en el mismo tenant — instantáneo. |
| Externa | Cuenta bancaria colombiana | Banking maneja el riel (Turbo, ACH, Transfiya). |
Las dispersiones externas heredan el enrutamiento, fallback y comportamiento de confirmación de Banking.
## Fees [#fees]
Una dispersión a una cuenta bancaria puede llevar dos fees, ambos con precio definido por tus [políticas de fees](/docs/guides/core/concepts/fees/policies) y devueltos en el `calculated_fees` de la dispersión:
| Tipo de fee | Qué es |
| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------- |
| `bank_transfer` | Tu cobro por la transferencia — un monto fijo, un porcentaje, o ambos. |
| `bank_transfer_gmf` | El traslado del GMF — el Gravamen a los Movimientos Financieros de Colombia, 0.4% del monto (el "4 × 1000") — cuando aplica a la dispersión. |
Los fees salen de la subcuenta origen junto con la dispersión: si fijas el monto que debe recibir el destino, el origen paga ese monto más los fees; si fijas el monto origen, el destino recibe ese monto menos los fees. Define un precio general en tus políticas base y sobrescríbelo cuando quieras cobrar de manera diferente por programa, por propietario de cuenta o por una sola cuenta.
Los mismos dos tipos de fee existen del lado de Mono: por cada dispersión, Mono te cobra sus propios `bank_transfer` y `bank_transfer_gmf` en tus cuentas, según lo acordado en tus términos comerciales. El [listado de fees](/docs/api-reference/core/fees/list_fees) muestra ambos lados, diferenciados por `owner`. Ver [Fees](/docs/guides/core/concepts/fees).
## Modelo de datos [#modelo-de-datos]
El schema completo vive en la [referencia de API de dispersiones de Core](/docs/api-reference/core/payouts).
## Eventos de webhook [#eventos-de-webhook]
Las dispersiones emiten eventos en cada transición de estado. Ver [webhooks de Core](/docs/guides/core/webhooks).
## Siguientes pasos [#siguientes-pasos]
* [Ledger](/docs/guides/core/concepts/ledger) — las cuentas entre las que las dispersiones mueven dinero.
* [Transferencias de Banking](/docs/guides/banking/concepts/transfers) — la mecánica a nivel de riel para destinos externos.
* [Flujo de dispersión](/docs/guides/core/flows/payout-disbursement) — caso de uso de extremo a extremo.
* [Políticas de fees](/docs/guides/core/concepts/fees/policies) — cómo ponerle precio a los fees de tus dispersiones.
# Ledger y Tarjetas (/es/docs/guides/core/concepts/ledger)
Ledger es un producto de Mono que te permite crear y administrar saldos virtuales en múltiples divisas, además de asociarlos a distintos medios de pago como tarjetas y transferencias. Además, Mono Ledger te da la flexibilidad de adaptar tus operaciones financieras según las necesidades específicas de tu negocio o productos. Desde lanzar billeteras digitales hasta emitir tarjetas con marca propia, Mono Ledger te entrega las herramientas necesarias para gestionar tus transacciones financieras de forma eficiente y segura.
Mono Ledger solo está disponible vía API.
Para completar tu integración, necesitas administrar los actores de Ledger:
## Cuenta Tenant [#cuenta-tenant]
La cuenta tenant funciona como tu cuenta principal, responsable de administrar todas las transacciones hacia las cuentas de tus clientes (subcuentas). Adicionalmente, emula el saldo que mantienes en tu cuenta de dinero real. Tu cuenta tenant soporta distintas divisas, que se configurarán según tus requerimientos.
Vamos a crear tu cuenta tenant, y usando el endpoint de configuración del tenant, puedes consultar tu configuración, que incluye las divisas soportadas y tus grupos de configuración de tarjetas.
## Account Holders [#account-holders]
Los account holders representan a tus usuarios, los que quieres que se creen en Mono Ledger. Cada individuo debe registrarse como account holder, lo que le da la capacidad de tener uno o varios saldos, tarjetas y realizar transferencias entre cuentas.
Para crear account holders, puedes usar el endpoint de account holder, y necesitas definir los siguientes aspectos:
**Información personal del tarjetahabiente:**
* País
* Tipo y número de documento
* Nombre y apellido
* Tipo de persona
* Número de teléfono
* Dirección
* Email
## Cuentas [#cuentas]
En estas cuentas podrás mantener y administrar el dinero virtual de tus tarjetahabientes. Cada account holder puede tener una o varias cuentas, pero estas cuentas solo pueden tener una divisa. No son cuentas bancarias reales; son cuentas virtuales donde podrás depositar dinero virtual y revisar transacciones.
Para crear una cuenta, debes usar el endpoint de cuenta y definir los siguientes aspectos:
* ID del Account Holder
* Código de divisa
* Nombre que le quieres dar a la cuenta
## Tarjetas [#tarjetas]
Puedes crear y vincular tarjetas virtuales a cada una de las cuentas, y se pueden usar para servicios como Facebook Ads, Google Workplace, entre otros. Estas tarjetas pueden personalizarse con tu propio diseño, o pueden llevar el diseño clásico de Mono.
Las tarjetas en Mono Ledger son tarjetas débito, eso significa que funcionarán con el dinero de las cuentas a las que están vinculadas, siempre estarán vinculadas a esa cuenta y usarán el dinero disponible allí.
Puedes usar el endpoint de tarjeta para crear tarjetas virtuales de Ledger, y necesitas definir los siguientes aspectos:
* ID de la cuenta a la que quieres vincular la tarjeta.
* Información personal del tarjetahabiente.
* Grupo de configuración de tarjetas
* Nickname de la tarjeta.
## Transferencias bancarias [#transferencias-bancarias]
Con Mono Ledger Colombia, podrás hacer transferencias bancarias desde tus cuentas de ledger hacia cualquier cuenta del sistema financiero colombiano. Pueden ser vía transferencia Turbo o transferencias ACH normales.

## ¿Cómo funciona tu Ledger? [#cómo-funciona-tu-ledger]
Vamos a crear tu cuenta tenant y darte acceso a tus API Keys. Para crear la cuenta necesitas definir los siguientes aspectos:
* Las divisas que quieres que tenga tu cuenta.
* Tenant Fees para compras nacionales e internacionales, es decir el porcentaje que vas a cobrar por cada compra hecha con las tarjetas vinculadas a tus cuentas.
Al mismo tiempo, te crearemos una cuenta para tu dinero real, tu cuenta de dinero virtual actuará como un espejo de esa cuenta y tendrá disponible la misma cantidad de dinero.
## Dinero virtual vs dinero real [#dinero-virtual-vs-dinero-real]
Para usar tu ledger, necesitas entender la diferencia entre dinero real y virtual:
### Dinero real [#dinero-real]
Dinero que tendrás en tu cuenta de dinero real, la cuenta que vas a fondear. El dinero saldrá de esta cuenta solo cuando se haga una transacción real, por ejemplo una compra con tarjeta.
### Dinero virtual [#dinero-virtual]
Dinero que usas para hacer recargas y retiros sobre tus cuentas de ledger, no hay movimiento de dinero real cuando haces una de estas operaciones.
El dinero en tu Ledger es dinero virtual, eso significa que todas las recargas que hagas a las cuentas de ledger, o las transferencias entre cuentas de ledger no generarán transacciones reales desde tu cuenta de dinero real. Entonces, ¿cuándo se descontará este dinero? En el momento en que uno de tus account holders haga una transacción fuera de tu ledger, por ejemplo: una compra con tarjeta o una transferencia bancaria fuera de Mono. Esto es bueno para ti, porque podrás estudiar la forma en que tus usuarios están gastando su dinero y, con base en eso, cobrar a tu cuenta real, no tiene que ser 1:1 con el dinero que pongas en las cuentas de ledger.
# Tarjetas (/es/docs/guides/core/concepts/cards)
Las tarjetas son un medio de pago que te permite pagar productos y servicios. Las tarjetas de Mono están en alianza con Visa y pueden ser débito o prepago.
## Tipos de tarjetas [#tipos-de-tarjetas]
### Débito [#débito]
Estas tarjetas funcionan con el saldo asociado a tu cuenta Mono. No necesitas recargar la tarjeta previamente; solo necesitas tener dinero en tu cuenta.
### Prepago [#prepago]
Estas tarjetas funcionan con el saldo que decides cargar. Si no le pones dinero a la tarjeta, no se activará. Para cargar o retirar dinero de la tarjeta, debes usar el endpoint de topup/withdrawal de tarjetas.
## Presentación de la tarjeta [#presentación-de-la-tarjeta]
### Tarjetas físicas [#tarjetas-físicas]
Se pueden usar para pagar en tiendas físicas, retirar dinero en ATMs e incluso hacer compras en línea. Nuestras tarjetas físicas pueden incluir tu logo o llevar el diseño clásico de Mono.
#### ¿Cómo obtenerlas? [#cómo-obtenerlas]
Puedes solicitar tarjetas físicas por lotes o individualmente. Solicita tus tarjetas físicas desde la Mono Web App entrando a la sección Tarjetas > Tarjeta física. Contacta a nuestro equipo de ventas para solicitar tarjetas por lotes.
#### ¿Cómo activo una tarjeta física? [#cómo-activo-una-tarjeta-física]
Una vez recibas las tarjetas, ingresa a la Mono Web App, ve a la sección Tarjetas y verás el mensaje: `Tienes 1 tarjeta en camino. ¿Ya llegó? Actívala ahora`. Allí debes ingresar el alias de la tarjeta, el cupo deseado y un PIN.
### Tarjetas virtuales [#tarjetas-virtuales]
Están disponibles de forma inmediata y se pueden usar para compras y pagos en plataformas digitales. Con un CVV, son perfectas para pagar servicios como Facebook, Google, Slack, publicidad, entre otros. Nuestras tarjetas virtuales pueden personalizarse con el diseño que solicites o llevar el diseño clásico de Mono. Al igual que con las físicas, te guiamos en el proceso para asegurar que todo cumpla con tus preferencias.
#### ¿Cómo obtenerlas? [#cómo-obtenerlas-1]
Puedes solicitar tarjetas virtuales desde la aplicación web de Mono o vía API.
#### Crear una tarjeta vía API [#crear-una-tarjeta-vía-api]
Después del proceso de autenticación, puedes usar el endpoint de creación de tarjeta para generar las tarjetas virtuales que necesites. Para generar una (o varias) tarjetas virtuales en Mono vía API, debes definir los siguientes aspectos:
* ID de la cuenta a la que se va a asociar la tarjeta (puedes encontrar tu ID de cuenta en el endpoint de accounts).
* Información del tarjetahabiente.
* Monto inicial si la tarjeta es prepago.
* Alias y cupo de la tarjeta.
* Las tarjetas creadas en Mono están en estado activo al momento de la creación.
## Estados de la tarjeta [#estados-de-la-tarjeta]
Tus tarjetas Mono pueden estar en uno de los siguientes estados:
| Estado | Descripción |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `created` | Este estado solo aplica a tarjetas físicas. Es el estado en el que está tu tarjeta antes de ser activada en la app de Mono. Si tu tarjeta está en este estado, no funcionará en ningún comercio. |
| `active` | Este es el único estado en el que tu tarjeta funcionará para pagos. |
| `frozen` | En este estado, tu tarjeta está "suspendida" para operación normal. Puedes reactivarla vía API usando el endpoint o desde tu app de Mono. |
| `canceled` | Una vez canceles una tarjeta, no podrás usarla ni reactivarla. Úsalo cuando ya no quieras usar esta tarjeta. |
# Programas (/es/docs/guides/core/concepts/program)
Un **programa** agrupa cuentas que operan bajo la misma configuración: qué monedas pueden tener, qué funcionalidades o servicios tienen habilitados (recaudos, dispersiones, transferencias entre subcuentas, tarjetas), qué métodos de pago puede usar cada uno y con qué cuenta funding liquidan. Toda cuenta pertenece a un programa, así que el programa es el nivel en el que defines cómo se comporta un conjunto completo de cuentas.
Piensa en un programa como una línea de producto dentro de tu organización. Una billetera en COP para usuarios retail, una tesorería en USD para clientes corporativos y un plan premium con precios distintos pueden ser cada uno su propio programa — con sus propias reglas y fees — bajo una sola integración con Mono.
Los programas se configuran con Mono al hacer el onboarding, o cuando necesitas uno nuevo — no
hay un endpoint de autoservicio para crearlos o editarlos. Lo que sí configuras tú, por
programa, son los precios: ver [Políticas de fees](/docs/guides/core/concepts/fees/policies).
## Qué define un programa [#qué-define-un-programa]
| Configuración | Qué decide |
| :------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Monedas** | Las monedas que pueden tener las cuentas del programa. Cada cuenta tiene exactamente una moneda, elegida de esta lista. |
| **Funcionalidades o servicios habilitados** | Qué funcionalidades o servicios de Core pueden usar las cuentas: recaudos, dispersiones, transferencias entre subcuentas (con o sin conversión de moneda) y tarjetas. |
| **Métodos** | Dentro de cada funcionalidad o servicio, los métodos de pago disponibles — qué rieles de dispersión, qué métodos de recaudo — y su configuración. |
| **Cuenta funding** | La cuenta bancaria real con la que liquida el programa: donde se entrega el dinero de los pagos recibidos y de donde sale el dinero de las dispersiones. |
| **Por defecto** | Uno de tus programas es el programa por defecto: las cuentas creadas sin programa van ahí. |
## Programas y cuentas [#programas-y-cuentas]
Al crear una cuenta de ledger puedes indicar su `program_id` (un identificador `pro_`). Si lo dejas vacío, la cuenta entra a tu programa por defecto. La cuenta hereda todo lo que define el programa: no puede tener una moneda que el programa no soporte, ni usar una funcionalidad o servicio que el programa no tenga habilitado.
Los propietarios de cuentas no están atados a un solo programa — un mismo propietario puede tener cuentas en programas distintos.
## Por qué dividir tu operación en programas [#por-qué-dividir-tu-operación-en-programas]
Usa varios programas siempre que dos grupos de cuentas deban comportarse distinto:
* **Por moneda** — un programa en COP y otro en USD, cada uno liquidando con su propia cuenta funding.
* **Por producto o plan** — un plan gratuito y un plan premium con fees distintos.
* **Por cliente o segmento** — un programa por cliente empresarial, o por región, para mantener separadas su configuración y sus precios.
Los programas también son uno de los niveles a los que aplican las políticas de fees: una política que apunta a un programa le pone precio a todas sus cuentas, salvo que una política más específica — para un propietario de cuenta o para una sola cuenta — la sobrescriba. Ver [a quién aplica una política](/docs/guides/core/concepts/fees/policies#a-quién-aplica-una-política).
## Dónde aparecen los programas en el API [#dónde-aparecen-los-programas-en-el-api]
* `program_id` al [crear una cuenta de ledger](/docs/api-reference/core/ledger/create_ledger_account) — elige el programa, u omítelo para usar el de por defecto.
* `target_type: program` y `target_id` en una [política de fees](/docs/api-reference/core/fees), para ponerle precio a todas las cuentas de un programa de una vez.
* Tu [configuración de tenant](/docs/api-reference/core/ledger/get_tenant_config), que lista las monedas que soportan tus programas.
## Siguientes pasos [#siguientes-pasos]
* [Fees](/docs/guides/core/concepts/fees) — lo que le cobras a tus usuarios y lo que Mono te cobra a ti.
* [Políticas de fees](/docs/guides/core/concepts/fees/policies) — precios a nivel global, de programa, de propietario de cuenta o de una sola cuenta.
* [Ledger](/docs/guides/core/concepts/ledger) — las cuentas que agrupa un programa.
# Conceptos (/es/docs/guides/core/concepts)
La API de Core está construida sobre un puñado de entidades. Cada una tiene su propio modelo de datos y ciclo de vida, y la mayoría sus propios webhooks. Entenderlas en aislamiento es el prerrequisito para combinarlas en un [flujo](/docs/guides/core/flows).
Recibir dinero en subcuentas es una sub-área completa por derecho propio — [Recaudos](/docs/guides/core/concepts/payins) — anidada en esta sección con sus propias páginas de conceptos y métodos; sus flujos y webhooks viven en las secciones de [Flujos](/docs/guides/core/flows/payins) y [Webhooks](/docs/guides/core/webhooks/payins) de Core.
Contabilidad de partida doble para cuentas, transacciones, holds y settlements.
Agrupa cuentas bajo una misma configuración — monedas, funcionalidades o servicios, métodos,
cuenta funding.
Lo que le cobras a tus usuarios y lo que Mono te cobra a ti — y las políticas que fijan el
precio.
Dinero hacia adentro — plantillas, intenciones de pago, pagos y lotes de liquidación.
Dinero hacia afuera — a cuentas internas o destinos externos.
Tarjetas virtuales y físicas que gastan contra cuentas de ledger.
Reglas en tiempo real que limitan lo que puede hacer una tarjeta.
Tasas de referencia y mecánica de conversión para ledgers multi-divisa.
# Tasas FX (/es/docs/guides/core/concepts/fx-rates)
Una **tasa FX** es la tasa de cambio que Core usa para convertir entre divisas dentro del ledger. Si tu producto mantiene saldos en más de una divisa — por ejemplo, COP y USD — cada operación cross-currency necesita una tasa para calcular el monto equivalente en la divisa de destino. Core publica tasas de referencia con una cadencia fija y las aplica a las conversiones internas en tu ledger.
Te van a importar las tasas FX si construyes una billetera multi-divisa, un producto de tesorería o cualquier caso donde el dinero se mueve entre cuentas que usan distintas divisas ISO 4217.
## Qué es [#qué-es]
Una tasa FX es una tupla `(base_currency, quote_currency, rate, valid_from, valid_until)`. Core guarda las tasas activas y las aplica automáticamente cuando realizas una operación de ledger cross-currency.
## Cuándo se aplica [#cuándo-se-aplica]
* Una transacción de ledger cross-currency (por ejemplo, debitar cuenta USD, acreditar cuenta COP).
* Una dispersión cross-currency.
* Una autorización cross-currency en una tarjeta que gasta contra una cuenta en una divisa distinta a la del comercio.
## Redondeo y comisiones [#redondeo-y-comisiones]
Los montos convertidos se redondean a los decimales de cada moneda. En las transferencias entre subcuentas, tu spread sobre la tasa de referencia y un cobro fijo por la conversión son fees a los que les pones precio con tus políticas de fees — ver [Transferencias entre subcuentas](/docs/guides/core/flows/account-to-account-transfers) y [Políticas de fees](/docs/guides/core/concepts/fees/policies).
## Modelo de datos [#modelo-de-datos]
El schema completo vive en la [referencia de API de tasas FX de Core](/docs/api-reference/core).
## Siguientes pasos [#siguientes-pasos]
* [Ledger](/docs/guides/core/concepts/ledger)
* [Dispersiones](/docs/guides/core/concepts/payouts)
# Transferencia saliente (/es/docs/guides/breb-participant/flows/outgoing-transfer)
Una **transferencia saliente** es un pago Bre-B: los fondos dejan tu cuenta tenant en Mono y llegan a un destino identificado por una llave de pago — un alias como un número de teléfono, documento, email, un identificador alfanumérico Bre-B o un código de comercio. A diferencia de una transferencia por tarjeta o ACH, las transferencias Bre-B settlean en tiempo real, 24/7, y el destino se resuelve en runtime contra el directorio central.
Usa este flujo cuando tu negocio empuja dinero a un usuario final — reembolsos, nómina por Bre-B, pagos a vendedores de marketplace, barridos de tesorería a otro participante o retiros de clientes.
Esta página es un esqueleto de flujo. La versión completa incluirá ejemplos de código para
cada paso y patrones de conciliación. La secuencia, el ciclo de vida y el catálogo de errores
ya están documentados en detalle — ver abajo y las páginas enlazadas.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una cuenta de Bre-B participant con Mono y credenciales OAuth de cliente. Ver [Autenticación](/docs/guides/api-standards/authentication).
* Una cuenta tenant con saldo suficiente para cubrir la transferencia.
* Los scopes apropiados: `target_resolutions` para una llave conocida,
`target_discoveries` cuando la llave es desconocida y `outgoing_transfers`
para enviar el pago.
* Un endpoint de webhook suscrito a [eventos de transferencia saliente](/docs/api-reference/breb-participant/outgoing-transfer-webhooks) y [eventos de target-resolution](/docs/api-reference/breb-participant/target-resolution-webhooks), con [verificación de firma](/docs/guides/breb-participant/webhook-signatures) en su lugar.
* Una [clave de idempotencia](/docs/guides/api-standards/idempotency-keys) por transferencia.
## Visión general de la secuencia [#visión-general-de-la-secuencia]
1. **Obtén la llave** — si conoces la llave exacta, [resuélvela](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target).
Si conoces al beneficiario y su cuenta, pero no la llave, usa el
[descubrimiento de llaves](/docs/guides/breb-participant/flows/target-discovery). Ambos caminos
devuelven los datos del titular y destino para confirmación. Si ya sabes quién debe ser
el dueño de la llave, envía `expected_creditor` al resolver: el request falla con
`target_creditor_mismatch` en lugar de devolver un target que pertenece a otra persona.
2. **Confirma con el pagador** — muestra el nombre resuelto al usuario y obtén una confirmación explícita antes de despachar.
3. **Despacha la transferencia** — llama al endpoint de transferencia saliente con la llave resuelta, monto, moneda y clave de idempotencia.
4. **Rastrea el ciclo de vida** — la transferencia se mueve por estados pending, sent, accepted, settled o rejected. Los webhooks disparan en cada transición.
5. **Concilia** — en settlement, acredita la operación en tus libros; en rechazo, surface el motivo de la falla al usuario usando el [catálogo de errores](/docs/guides/breb-participant/outgoing-transfer-errors).
## Pasos [#pasos]
El paso a paso completo (con muestras de curl, Node.js y Python) está pendiente. Hasta que se publique, consulta:
* [Estados de la transferencia saliente](/docs/guides/breb-participant/architecture/outgoing-transfer-states) — la máquina de estados completa, transiciones y estados terminales.
* [Descubrimiento de llaves](/docs/guides/breb-participant/flows/target-discovery) — encuentra una llave desde los datos del beneficiario y su cuenta antes de enviar.
* [Errores de transferencia saliente](/docs/guides/breb-participant/outgoing-transfer-errors) — motivos de rechazo y manejo recomendado.
* [Payloads de webhook](/docs/guides/breb-participant/webhook-payloads) — el envelope y los schemas de eventos.
* [Firmas de webhook](/docs/guides/breb-participant/webhook-signatures) — verificación HMAC-SHA256.
* [Sandbox: transferencias salientes](/docs/guides/breb-participant/sandbox/outgoing-transfers) — simula el ciclo de vida completo sin dinero real.
* [Sandbox: descubrimiento de llaves](/docs/guides/breb-participant/sandbox/target-discovery) — obtén llaves determinísticas de un solo uso antes de enviar.
## Siguientes pasos [#siguientes-pasos]
* [Recaudo por QR](/docs/guides/breb-participant/flows/qr-collection) — la contraparte de payin de este flujo.
* [Estados de la transferencia saliente](/docs/guides/breb-participant/architecture/outgoing-transfer-states) — el ciclo de vida que sigue cada transferencia saliente.
* [Claves de idempotencia](/docs/guides/api-standards/idempotency-keys) — requeridas para reintentos seguros en cada despacho.
# Descubrimiento de llaves (/es/docs/guides/breb-participant/flows/target-discovery)
Usa el descubrimiento de llaves cuando conoces la identidad del beneficiario y
su cuenta destino, pero no su llave Bre-B exacta. Mono prueba un conjunto
acotado de llaves derivadas de los datos enviados y solo devuelve una llave
utilizable cuando confirma la misma persona, banco y cuenta.
Si ya conoces la llave exacta, llama a [Resolver
llave](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target). Discovery
sirve para encontrar una llave desde los datos del beneficiario, no para listar todas las
llaves que posee una persona.
Usa los sufijos determinísticos de documento `101`–`104` para probar los resultados exacto,
alternativo, vacío cacheado y no disponible. Consulta [descubrimiento de llaves en
sandbox](/docs/guides/breb-participant/sandbox/target-discovery).
## Antes de empezar [#antes-de-empezar]
* Autentícate con OAuth 2.0 client credentials.
* Confirma que Mono haya habilitado el descubrimiento de llaves para tu tenant.
* Usa `target_discoveries` para `POST /api/v1/targets/discover`.
* Usa `target_discoveries` o `target_discoveries:readonly` para los GET del
catálogo bancario.
* Ten el `tenant_account_id` que originará la transferencia saliente. Debe
pertenecer al tenant autenticado.
Consulta [Autenticación de Bre-B Participant](/docs/guides/breb-participant/authentication)
para el flujo de tokens.
## 1. Selecciona el código bancario canónico [#1-selecciona-el-código-bancario-canónico]
Discovery recibe un código ACH colombiano de tres dígitos en
`account.bank_code`. No derives ese código por tu cuenta desde el nombre del
banco. Primero busca en el catálogo:
```http
GET /api/v1/banks?q=bancolombia&limit=5
```
Un código ACH, nombre canónico o alias exacto produce un `match` confiable y
ninguna sugerencia:
```json
{
"catalog_version": 1,
"match": {
"ach_code": "007",
"name": "BANCOLOMBIA"
},
"data": []
}
```
Una búsqueda parcial o aproximada produce sugerencias:
```http
GET /api/v1/banks?q=davi&limit=5
```
```json
{
"catalog_version": 1,
"match": null,
"data": [
{ "ach_code": "051", "name": "DAVIVIENDA" },
{ "ach_code": "551", "name": "DAVIPLATA" }
]
}
```
Solo un `match` no nulo se puede seleccionar automáticamente. Cada elemento de
`data` es una sugerencia —incluso si solo hay uno—, así que muéstrale las
opciones al pagador y usa el `ach_code` que seleccione. Una búsqueda sin
resultados también devuelve `200`, con `match: null` y `data: []`.
También puedes listar el catálogo completo o consultar un código exacto. Ver
[Listar bancos](/docs/api-reference/breb-participant/target-discovery/list_banks)
y [Consultar banco](/docs/api-reference/breb-participant/target-discovery/get_bank).
## 2. Descubre la llave [#2-descubre-la-llave]
Envía los datos que ya conoces del beneficiario. Los nombres, celular y correo
son pistas opcionales; la identidad, banco y cuenta son obligatorios.
```http
POST /api/v1/targets/discover
Content-Type: application/json
Authorization: Bearer
```
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"person": {
"document_type": "CC",
"document_number": "1192793827",
"first_name": "Diego",
"middle_name": null,
"first_surname": "Sánchez",
"second_surname": null,
"phone": "+573104838791",
"email": "diego@example.com"
},
"account": {
"bank_code": "013",
"number": "0013123456789",
"type": "savings_account"
}
}
```
Consulta [Descubrir llave](/docs/api-reference/breb-participant/target-discovery/discover_target)
para ver todos los campos y enums.
## 3. Maneja el resultado [#3-maneja-el-resultado]
Los dos resultados normales devuelven `200`. Revisa `state`; no trates todo
`200` como una llave descubierta.
| `state` | Significado |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `discovered` | `target` y `resolution_id` están presentes. La llave coincide con la persona, banco y cuenta solicitados. |
| `not_discovered` | `target` y `resolution_id` son `null`. `state_reason` es `no_exact_target`; esto no significa que la persona no tenga otra llave. |
### Llave exacta encontrada [#llave-exacta-encontrada]
```json
{
"state": "discovered",
"state_reason": null,
"attempts_count": 2,
"resolution_id": "bbtgr_5tgliBmzjZ6mpQPRbQjfKj",
"target": {
"id": "bbtgt_5tgliBmzjZ6mpQPRbQjfKj",
"key_type": "identification",
"key_value": "1192793827",
"resolution_type": "plain_key",
"spbvi": "CRB",
"participant_nit": "860003020",
"participant_name": "BBVA Colombia",
"creditor": {
"type": "natural",
"document_type": "CC",
"document_number": "1192793827",
"full_name": "Diego Sánchez"
},
"creditor_account": {
"type": "savings_account",
"number": "0013123456789",
"currency_code": "COP"
},
"transaction_amount": null,
"payment_id": null
},
"same_person_keys": [
{
"key_type": "alphanumeric",
"key_value": "@BBVA3104838791",
"bank_code": "007",
"participant_nit": "890903938",
"creditor_account": {
"type": "savings_account",
"number_last_four": "4321",
"currency_code": "COP"
},
"resolution_required": true
}
]
}
```
La llave resuelta es fresca y de un solo uso: pasa su `target.id` a una sola
transferencia saliente. Llamar discovery otra vez crea otra resolución de la
llave en lugar de devolver una guardada en cache.
### No se encontró una llave exacta [#no-se-encontró-una-llave-exacta]
```json
{
"state": "not_discovered",
"state_reason": "no_exact_target",
"attempts_count": 4,
"resolution_id": null,
"target": null,
"same_person_keys": [
{
"key_type": "email",
"key_value": "diego@example.com",
"bank_code": "007",
"participant_nit": "890903938",
"creditor_account": {
"type": "savings_account",
"number_last_four": "4321",
"currency_code": "COP"
},
"resolution_required": true
}
]
}
```
`same_person_keys` siempre está presente y puede ser `[]`. Contiene máximo
cinco llaves confirmadas para la misma identidad y nunca datos de otra persona.
No es exhaustivo: las candidatas solo provienen de los datos del request y
discovery se detiene en la primera llave exacta. Por eso una respuesta
`discovered` solo incluye llaves de la misma persona observadas antes de esa
primera llave exacta.
Un elemento de `same_person_keys` es una llave alternativa, pero todavía no
puede usarse para transferir. Antes de usarlo:
1. Deja que el pagador elija la alternativa.
2. Llama a [Resolver llave](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target)
con su `key_value` para obtener una llave fresca y utilizable.
3. Confirma que el `participant_nit` y la terminación de
`creditor_account.number` de la llave recién resuelta todavía coincidan con el
`participant_nit` y `number_last_four` de la alternativa.
4. Pide confirmación antes de enviar dinero.
La resolución adicional es obligatoria porque una alternativa no queda
reservada y después podría apuntar a otro destino.
## Comportamiento del cache [#comportamiento-del-cache]
Mono puede cachear por máximo una hora únicamente resultados `not_discovered`
con `same_person_keys` vacío. En ese cache hit, `attempts_count` es `0`. Los
resultados que contienen una llave exacta o una alternativa no se sirven desde cache,
por lo que sus datos del directorio e IDs provienen del request actual.
## Envía la transferencia saliente [#envía-la-transferencia-saliente]
Cuando el pagador confirme al beneficiario resuelto, crea la transferencia
saliente con el `target.id` de la llave:
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"description": "Pago a beneficiario confirmado",
"transfers": [
{
"target_id": "bbtgt_5tgliBmzjZ6mpQPRbQjfKj",
"amount": { "amount": 12000000, "currency": "COP" },
"external_id": "transferencia-confirmada-1"
}
]
}
```
Consulta [Crear transferencias
salientes](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers)
para el request completo. Cada `target.id` solo puede financiar una transferencia;
haz otro discovery o resolución para otro pago.
Cuando discovery crea una llave exacta, aplican los webhooks existentes
[`target_resolution_created`](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_created)
y
[`target_resolution_resolved`](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_resolved).
Los resultados `not_discovered` y las alternativas no emiten webhooks de
resolución. Los consumidores deben deduplicar esos webhooks por `resolution_id`;
la entrega es asíncrona, por lo que un evento `target_resolution_created` puede
traer `state=resolved` y el orden no está garantizado. Trata el `state` y
`updated_at` más recientes como autoridad.
## Errores y reintentos [#errores-y-reintentos]
| Estado | Cuándo ocurre | Qué hacer |
| -----: | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `400` | El documento o número de cuenta no se puede comparar de forma segura. | Corrige los datos; no reintentes sin cambios. |
| `401` | Falta el token, es inválido o expiró. | Obtén un token válido y reintenta. |
| `403` | La credencial no tiene el scope requerido o discovery no está habilitado para el tenant. | Solicita el scope o contacta a Mono para habilitar el descubrimiento de llaves. |
| `404` | La cuenta tenant no existe o no pertenece al tenant. | Verifica `tenant_account_id`. Un lookup de banco exacto desconocido también devuelve `bank_not_found`. |
| `422` | El payload es inválido o `bank_code` no es aceptado (`unknown_bank`). | Corrige el request o selecciona un banco del catálogo público. |
| `429` | Se superó el límite de discovery por cliente o tenant (`rate_limit_exceeded`). | Espera el intervalo `Retry-After` antes de reintentar. |
| `503` | El directorio Bre-B no está disponible temporalmente. | Reintenta después de `Retry-After`, cuando esté presente, con backoff acotado. |
| `500` | Ocurrió un error inesperado del servidor. | Reintenta de forma segura; contacta a soporte con el `id` de la respuesta si persiste. |
Una búsqueda válida sin llave exacta no es un error HTTP: devuelve `200` con
`state=not_discovered`.
## Referencia relacionada [#referencia-relacionada]
* [Listar bancos](/docs/api-reference/breb-participant/target-discovery/list_banks)
* [Consultar banco](/docs/api-reference/breb-participant/target-discovery/get_bank)
* [Descubrir llave](/docs/api-reference/breb-participant/target-discovery/discover_target)
* [Resolver llave](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target)
## Siguientes pasos [#siguientes-pasos]
* [Descubrimiento de llaves en sandbox](/docs/guides/breb-participant/sandbox/target-discovery) — prueba los escenarios determinísticos `101`–`104` antes de salir a producción.
* [Flujo de transferencia saliente](/docs/guides/breb-participant/flows/outgoing-transfer) — paga la llave que acabas de descubrir.
* [Autenticación de Bre-B Participant](/docs/guides/breb-participant/authentication) — los scopes y el flujo de tokens que requiere discovery.
# Flujos (/es/docs/guides/breb-participant/flows)
Un **flujo** es un caso de uso completo, de extremo a extremo, que compone varios conceptos Bre-B juntos. Mientras que la sección de [Conceptos](/docs/api-reference/breb-participant) responde "¿qué es una llave de pago, una llave resuelta o una cuenta tenant?", los flujos responden "¿cómo recibo un pago Bre-B, o cómo envío uno?".
Los flujos se agrupan por capacidad comercial:
* **Payin** — recibir un pago Bre-B de otro participante.
* **Payout** — enviar una transferencia Bre-B a otro participante.
## Flujos disponibles [#flujos-disponibles]
Genera un QR Bre-B, recibe el pago y concilia el evento de confirmación.
Encuentra una llave desde la identidad y cuenta bancaria del beneficiario y confirma el
destino.
Resuelve la llave destino, despacha la transferencia y maneja los estados del ciclo de vida.
## Siguientes pasos [#siguientes-pasos]
* [Estados de la transferencia saliente](/docs/guides/breb-participant/architecture/outgoing-transfer-states) — el ciclo de vida que sigue cada transferencia saliente.
* [Errores de transferencia saliente](/docs/guides/breb-participant/outgoing-transfer-errors) — motivos de rechazo y cómo manejarlos.
* [Firmas de webhook](/docs/guides/breb-participant/webhook-signatures) — cómo verificar la autenticidad de los eventos.
# Recaudo por QR (/es/docs/guides/breb-participant/flows/qr-collection)
**Bre-B** es el esquema interbancario de pagos instantáneos de Colombia — un rail en tiempo real similar a Pix de Brasil o UPI de India que permite a cualquier banco participante o entidad licenciada enviar y recibir fondos 24/7 en segundos. Un **recaudo por QR** es el flujo que tu negocio corre cuando quiere que un cliente te pague por Bre-B: generas un código QR que codifica una instrucción de pago, el cliente lo escanea desde su app bancaria, su banco envía la transferencia y Mono te notifica cuando llegan los fondos.
Este flujo es la mitad de payin de Bre-B Participant. Úsalo para checkout presencial, recargas en kiosco, páginas de donación o cualquier momento en que el pagador inicia un pago banco a banco usando su teléfono.
Esta página es un esqueleto de flujo y se expandirá con el request de generación de QR, el
formato del payload EMVCo, el webhook de confirmación, errores comunes y un recorrido de
sandbox.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una cuenta de Bre-B participant con Mono y credenciales OAuth de cliente. Ver [Autenticación](/docs/guides/api-standards/authentication).
* Una cuenta tenant registrada que recibirá los fondos.
* Un endpoint de webhook que pueda recibir [eventos de recaudo](/docs/api-reference/breb-participant/collection-webhooks) y verificar su firma — ver [Firmas de webhook](/docs/guides/breb-participant/webhook-signatures).
* Una [clave de idempotencia](/docs/guides/api-standards/idempotency-keys) por request de generación de QR.
## Visión general de la secuencia [#visión-general-de-la-secuencia]
1. **Genera un recaudo** — tu backend le pide a Mono un QR que codifique el monto, la moneda y una referencia que tú controlas.
2. **Muestra el QR** — tu frontend o terminal renderiza el payload EMVCo como un código escaneable.
3. **El cliente paga** — el pagador escanea el QR con su app bancaria y autoriza la transferencia.
4. **Recibe la confirmación** — Mono dispara un webhook a tu endpoint con el resultado final, firmado con HMAC-SHA256.
5. **Concilia** — tu sistema acredita la orden o libera la mercancía.
## Pasos [#pasos]
El paso a paso completo (con ejemplos de curl, Node.js y Python para generación de QR, verificación y conciliación) está pendiente. Hasta que se publique, consulta:
* La [referencia de API de Bre-B participant](/docs/api-reference/breb-participant) para los endpoints de recaudo.
* [Firmas de webhook](/docs/guides/breb-participant/webhook-signatures) para verificar el evento de confirmación.
* [Payloads de webhook](/docs/guides/breb-participant/webhook-payloads) para el envelope del evento y los schemas de payload.
## Siguientes pasos [#siguientes-pasos]
* [Transferencia saliente](/docs/guides/breb-participant/flows/outgoing-transfer) — la contraparte de payout de este flujo.
* [Sandbox: recaudos](/docs/guides/breb-participant/sandbox/collections) — simula un pago de cliente sin involucrar un banco real.
* [Payloads de webhook](/docs/guides/breb-participant/webhook-payloads) — el evento que recibirás en un recaudo exitoso.
# Recaudos (/es/docs/guides/core/sandbox/payins)
En producción el dinero llega cuando una persona real decide pagarte. En el sandbox esa persona
eres tú: una sola petición hace que un pago aterrice en uno de tus recaudos, y todo lo que sigue se
comporta como si el dinero fuera real. Así construyes y pruebas un recaudo de punta a punta antes
de tocar producción.
El pago simulado corre sobre el riel real: Bre-B, la red de transferencias inmediatas de
Colombia. Los mismos estados, los mismos webhooks, los mismos tiempos. Sólo el dinero no es
real.
## Antes de empezar [#antes-de-empezar]
Necesitas:
* Una API key de sandbox, y las peticiones apuntando a `https://api.sandbox.cuentamono.com`.
* El scope `payins` en tus credenciales.
* Algo que se pueda pagar: una [plantilla](/docs/guides/core/concepts/payins/templates) en estado
`ready`, o una [intención de pago](/docs/guides/core/concepts/payins/intents) en estado
`ready_to_pay`.
Ese último punto es el que más tropieza. Una plantilla o una intención de pago no es pagable en el
momento en que la creas. Mono todavía tiene que registrar su llave Bre-B ante la red. Espera
primero el webhook
[`payin_template_ready`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_ready) o
[`payin_intent_ready_to_pay`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_ready_to_pay).
Simular contra un objetivo que aún no tiene llave responde `400` con `payment_token_not_found`.
Sólo sandbox. En cualquier otro ambiente este endpoint responde `403` con
`feature_not_enabled`, así que una llamada que se filtre al código de producción no puede
acuñar pagos.
## Qué hace realmente una simulación [#qué-hace-realmente-una-simulación]
Mono opera directamente sobre Bre-B, así que el sandbox no finge el pago: recorre el mismo flujo de
aceptación que una transferencia real. Nombras un objetivo y un monto, y Mono construye un pago
entrante contra la llave Bre-B de ese objetivo, exactamente como si un pagador hubiera escaneado su
QR.
La respuesta es un recibo, no un veredicto. Te dice que existe un pago; su resultado te llega unos
segundos después por los [webhooks de recaudos](/docs/guides/core/webhooks/payins), igual que uno
real.
## Las dos formas de un pago [#las-dos-formas-de-un-pago]
Cuál envías depende de qué estás pagando. Una plantilla acepta muchos pagos y no sabe nada de este
en particular, así que la petición tiene que traer su propio método y monto. Una intención de pago
fue declarada para un pago específico y ya fija ambos.
| Si pagas una... | Envías | Porque |
| :------------------------------------------------------------- | :---------------------------------- | :---------------------------------------------------- |
| [Plantilla](/docs/guides/core/concepts/payins/templates) | `template_id` + `method` + `amount` | La plantilla acepta muchos pagos; este trae los suyos |
| [Intención de pago](/docs/guides/core/concepts/payins/intents) | `intent_id` | La intención de pago ya fijó el método y el monto |
Las dos formas son excluyentes. Nombrar ambos objetivos, o enviar un `method` junto a un
`intent_id`, responde `422`.
### Pagar una plantilla [#pagar-una-plantilla]
```json title="POST /v1/core/payins/sandbox/payments"
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" }
}
```
`method` tiene que ser uno que la plantilla acepte, o la llamada responde `400` con
`method_not_allowed`. Hoy `co_breb` es el único método de pago que ofrece Recaudos de Core. Los
montos son enteros en centavos, así que el ejemplo de arriba son 50.000 COP.
### Pagar una intención de pago [#pagar-una-intención-de-pago]
```json title="POST /v1/core/payins/sandbox/payments"
{
"intent_id": "cpint_5tgliBmzjZ6mpQPRbQjfKj"
}
```
Esa es toda la petición. La intención de pago aporta el método y el monto, y eso es justo lo que la
hace útil para probar: si envías esto, estás verificando el monto que la intención de pago exige,
no uno que retecleaste.
## Leer la respuesta [#leer-la-respuesta]
El código de estado te dice si nació un pago:
| Estado | Cuerpo | Qué pasó |
| :----- | :------------------------------------------------------------- | :-------------------------------------------------------- |
| `201` | `{ "payin_id": "cpin_...", "state_reason": null }` | El pago entró. Síguelo por los webhooks. |
| `201` | `{ "payin_id": "cpin_...", "state_reason": "bank_rejected" }` | El pago existe y ya fue rechazado por sus propias reglas. |
| `200` | `{ "payin_id": null, "state_reason": "amount_below_minimum" }` | El pago se rehusó antes de quedar registrado. |
Ese `200` merece atención. Cuando la autorización de pago de Mono rehúsa el pago (un monto fuera
de los límites de la plantilla, un pagador que no espera), el pago no queda registrado y **no se
emite ningún webhook**. En producción la app del banco del pagador es la que reporta el rechazo,
así que esta respuesta es el único lugar donde tu sistema puede enterarse. Provocar esos rechazos
es buena parte de para qué existe este endpoint.
## Elegir quién paga [#elegir-quién-paga]
Si omites `payer`, Mono se inventa uno: un nombre colombiano al azar y un documento `CC` de 10
dígitos al azar. Suficiente para la mayoría de pruebas. Envíalo cuando el pagador sea el punto de la
prueba:
```json
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" },
"payer": {
"document": { "type": "CC", "number": "1234567890" },
"name": "Juan Garcia"
}
}
```
Así ejercitas la lista
[`expected_payers`](/docs/guides/core/concepts/payins/templates#restringir-quién-puede-pagar) de una
plantilla. Envía un documento de la lista para confirmar que el pago pasa; envía uno que no esté
para confirmar que obtienes `payer_not_allowed`. El `type` del documento acepta `CC`, `CE`, `NIT`,
`PA` y `TI`.
## Provocar fallas [#provocar-fallas]
Hay dos tipos de falla, y se provocan de dos maneras distintas. Tener clara la diferencia ahorra
mucha confusión.
### Fallas del riel: el campo `error` [#fallas-del-riel-el-campo-error]
Son condiciones de infraestructura que el riel reporta **después** de que el pago ya fue aceptado.
Nombra una en `error` y el sandbox le pide al riel que la produzca:
```json
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" },
"error": "risk_control"
}
```
La llamada sigue respondiendo `201` con un `payin_id` y un `state_reason` nulo: el pago fue
aceptado, así que Mono ya lo registró. La falla llega 2 a 5 segundos después, por el webhook:
| `error` | El pago termina | Significado |
| :--------------------- | :-------------------------------- | :------------------------------------------------ |
| `risk_control` | `failed` / `risk_control` | Los controles de riesgo del proveedor lo frenaron |
| `provider_unavailable` | `failed` / `provider_unavailable` | El proveedor de pagos estaba fuera de servicio |
| `breb_timeout` | `failed` / `breb_timeout` | El riel Bre-B no respondió a tiempo |
| `unknown` | `failed` / `unknown` | Una falla sin clasificar |
Las cuatro dejan el pago en `failed` y te llegan como
[`payin_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_failed). Si el pago iba
dirigido a una intención de pago, esta reporta aparte: se rearma para un reintento con
[`payin_intent_attempt_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_attempt_failed),
o se da por vencida con
[`payin_intent_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_failed).
### Rechazos de negocio: por construcción [#rechazos-de-negocio-por-construcción]
Todo lo que atrapa la validación de Mono no tiene palanca, y no la necesita: lo provocas enviando un
pago que el objetivo genuinamente rehúsa. Se rehúsan de forma sincrónica, así que vuelven en la
respuesta misma como un `200` sin pago registrado.
| Para obtener | Envía |
| :------------------------------------ | :------------------------------------------------------------- |
| `amount_below_minimum` | Un monto por debajo del `minimum_payin_amount` de la plantilla |
| `amount_exceeds_maximum` | Un monto por encima de su `maximum_payin_amount` |
| `amount_exceeds_total_maximum` | Un monto que la pase de su `total_maximum_amount` |
| `payer_not_allowed` | Un documento en `payer` que no esté en `expected_payers` |
| `template_expired` / `intent_expired` | Un pago después del `expires_at` del objetivo |
| `template_paid` | Un segundo pago a una plantilla `single_use` |
| `template_disabled` | Un pago a una plantilla que actualizaste a `enabled: false` |
| `template_deleted` | Un pago a una plantilla que eliminaste |
Cada código, con su causa y qué hacer al respecto, está en
[Errores de pago](/docs/guides/core/concepts/payins/payins/errors).
`intent_amount_mismatch` queda fuera de alcance en el sandbox. Un pago a una intención de pago
siempre toma el monto de la intención de pago misma, así que no hay manera de enviar uno
distinto. Para ese caso, confía en la validación propia de la intención de pago.
## Simular el QR que escaneó el pagador [#simular-el-qr-que-escaneó-el-pagador]
Un pago Bre-B puede llevar el identificador del QR del que salió. Envíalo cuando tu conciliación lea
ese campo:
```json
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" },
"method_params": { "payment_id": "CO.COM.CRB.TRXIDP1234567891234" }
}
```
Si lo omites, el riel asigna uno. `method_params` es específico del método: para `co_breb` el único
campo es `payment_id`.
## Errores del endpoint mismo [#errores-del-endpoint-mismo]
Son fallas en la petición, no resultados de pago simulados.
| Estado | Código | Causa |
| :----- | :---------------------------------------------- | :----------------------------------------------------------------------- |
| `400` | `method_not_allowed` | La plantilla no acepta el `method` que enviaste. |
| `400` | `payin_invalid_method_params` | Los `method_params` no corresponden al método. |
| `400` | `payment_token_not_found` | El objetivo aún no tiene llave Bre-B: no está `ready` / `ready_to_pay`. |
| `401` | `missing_authorization_header`, `invalid_token` | Problema de credenciales. |
| `403` | `feature_not_enabled` | No es un ambiente sandbox, o Recaudos no está habilitado para tu cuenta. |
| `404` | `template_not_found`, `payin_intent_not_found` | No existe el objetivo. El de otro tenant se lee como uno que no existe. |
| `422` | No aplica | La petición viola el esquema: ambos objetivos, o un campo faltante. |
## Una prueba completa, de punta a punta [#una-prueba-completa-de-punta-a-punta]
Todo junto, un recaudo que puedes correr de una sentada:
1. Crea una plantilla. Espera el `payin_template_ready`.
2. Simula un pago por debajo del mínimo. Espera `200`, `amount_below_minimum`, ningún webhook.
3. Simula un pago válido. Espera `201`, y luego `payin_created`.
4. Unos segundos después, espera `payin_approved_in_provider` y `payin_subaccount_credited`.
5. Simula otro con `"error": "breb_timeout"`. Espera `201`, y luego `payin_failed`.
6. Lee la plantilla. Su `paid_amount` cuenta el paso 3 y no los pasos 2 ni 5.
Si tu integración maneja los seis, maneja un recaudo real.
## Siguientes pasos [#siguientes-pasos]
* [Recaudar con una plantilla](/docs/guides/core/flows/payins/collect-with-template): el flujo que prueba este endpoint.
* [Recaudar con intenciones de pago](/docs/guides/core/flows/payins/collect-with-intents): lo mismo, un pago declarado a la vez.
* [Errores de pago](/docs/guides/core/concepts/payins/payins/errors): todas las razones por las que un pago termina mal.
* [Simula un pago](/docs/api-reference/core/payins/simulate_payin_payment): la referencia del endpoint.
# Dispersiones (/es/docs/guides/core/sandbox/payouts)
Una dispersión por Bre-B desde Core son dos llamados: resuelves el destino y luego le pagas. En el
sandbox ambos llamados corren contra el directorio Bre-B de sandbox, y un conjunto de valores
reservados te permite disparar cada error cuando lo necesites, en vez de esperar a que ocurra en
producción.
## Antes de empezar [#antes-de-empezar]
Necesitas:
* Una API key de sandbox y peticiones contra `https://api.sandbox.cuentamono.com`.
* El scope `payouts` en tus credenciales.
* El método de dispersión `co_breb` habilitado en tu [programa](/docs/guides/core/concepts/program).
Pídele a Mono que lo habilite si tu tenant de sandbox aún no lo tiene.
* Una **subcuenta** con saldo desde la cual pagar. Las dispersiones y la resolución de llaves solo
funcionan sobre cuentas de tipo `subaccount`; fondéala con un topup antes de empezar.
## Una dispersión exitosa [#una-dispersión-exitosa]
Cualquier llave bien formada que no sea uno de los valores reservados de abajo se resuelve, y todas
se resuelven al mismo beneficiario de sandbox. Sirve un correo (`payee@example.com`) o una llave
alfanumérica (`@payee123`).
1. **Resuelve la llave** con
[`POST /v1/core/payouts/co_breb/targets/resolve`](/docs/api-reference/core/payouts/resolve_breb_target):
```json
{
"account_id": "lacc_...",
"format": "plain_key",
"value": "@payee123"
}
```
La respuesta trae el `id` del target y el `creditor` enmascarado: del documento quedan los
últimos tres dígitos y de cada palabra del nombre las tres primeras letras. En sandbox siempre es
la misma persona:
```json
{
"type": "natural",
"document_type": "CC",
"document_number": "*****455",
"full_name": "Jua*** Car*** Per*** Gom***"
}
```
Compara el beneficiario con la persona a la que quieres pagar antes de mover dinero. Para recorrer
el camino feliz de punta a punta, registra tu usuario de prueba con el documento `CC 123143455`.
2. **Paga el target resuelto** con
[`POST /v1/core/payouts`](/docs/api-reference/core/payouts/create_payout), usando
`target.kind: co_breb_target_id`:
```json
{
"method": "co_breb",
"account_id": "lacc_...",
"external_id": "retiro-0001",
"target_amount": { "amount": 5000000, "currency": "COP" },
"target": {
"country": "CO",
"currency": "COP",
"kind": "co_breb_target_id",
"target_id": ""
}
}
```
El llamado responde `201` con la dispersión en estado `processed`. El resto del ciclo te llega por
webhooks: Mono envía uno en cada cambio de estado, y cada uno trae la dispersión completa. Lee su
`state` para saber en qué va: `created`, `processed`, `charged`, `sending_to_provider`,
`sent_to_provider` y finalmente `successful`. Una dispersión fallida termina en `declined`,
`rejected` o `returned`, con el motivo en `state_reason`.
3. **Concilia** con
[`GET /v1/core/payouts/find_successful_by`](/docs/api-reference/core/payouts/find_successful_payout)
usando tu `external_id`.
Los montos van en centavos:
`5000000`
es COP 50.000.
## Errores al resolver una llave [#errores-al-resolver-una-llave]
Envía estos valores en `value` con `format: plain_key`. Cada uno responde de forma síncrona, así
que puedes probar el mensaje que ven tus usuarios sin crear una dispersión.
| `value` | Respuesta | Qué debe hacer tu integración |
| :----------------------------------------- | :-------------------------------- | :------------------------------------------------------------------- |
| `sandbox@key_not_found.err` | `404` · `breb_target_not_found` | Decirle al usuario que la llave no existe y pedirle otra |
| `sandbox@key_suspended.err` | `403` · `key_blocked_or_inactive` | Informar que la llave está suspendida; no reintentar con la misma |
| Una llave mal formada | `400` · `invalid_breb_key` | Validar el formato de la llave antes de llamar |
| `sandbox@key_target_creditor_mismatch.err` | `200`, con documento `*****500` | Tu validación del beneficiario debe detectarlo y detenerse sin pagar |
La última fila no es un error de Mono: la llave se resuelve normalmente, pero a un documento distinto
del beneficiario habitual de sandbox (`12314345500` en vez de `123143455`). Úsala para comprobar que tu integración compara el beneficiario resuelto
con tus propios registros y se niega a pagar cuando no coinciden.
## Errores al crear la dispersión [#errores-al-crear-la-dispersión]
Se validan antes de mover dinero, así que la dispersión no se crea y el llamado responde `400`.
| Cómo provocarlo | Respuesta |
| :------------------------------------------------------------------------------- | :--------------------------------- |
| Pagar más que el saldo de la subcuenta | `400` · `insufficient_funds` |
| Pagar un monto por encima del límite de Bre-B, p. ej. `1000000000000` (centavos) | `400` · `amount_exceeds_max_limit` |
## Próximamente [#próximamente]
| Escenario | Caso de prueba |
| :------------------------------------------------- | :------------- |
| Dispersión rechazada por la red después de cobrada | Por documentar |
| Dispersión devuelta después de ser exitosa | Por documentar |
## Siguientes pasos [#siguientes-pasos]
* [Dispersiones](/docs/guides/core/concepts/payouts) — cómo encajan dispersiones, targets y comisiones.
* [Ledger](/docs/guides/core/sandbox/ledger) — revisa las entradas de ledger que deja cada dispersión.
* [Lista de verificación de producción](/docs/guides/core/production) — pasos antes de procesar dispersiones reales.
# Ledger (/es/docs/guides/core/sandbox/ledger)
Usa estos casos de prueba para validar cómo tu integración maneja transacciones de cuenta de ledger — recargas, retiros, transferencias entre cuentas, comisiones, orígenes de dispersión y reversos.
Esta página es un esqueleto de sandbox. La versión completa incluirá casos de prueba
deterministas para cada valor de `origin` del catálogo de eventos
`ledger_account_transaction_created` (subaccount\_operation, payout, card\_presentment\_payment y
otros).
## Escenarios de prueba [#escenarios-de-prueba]
| Escenario | Descripción | Caso de prueba |
| :-------------------------- | :--------------------------------------------------------------------------- | :------------- |
| Recarga de subcuenta | Se acreditan fondos a una subcuenta. | Por documentar |
| Retiro de subcuenta | Se debitan fondos desde una subcuenta. | Por documentar |
| Transferencia entre cuentas | Los fondos se mueven entre dos subcuentas. | Por documentar |
| Débito de comisión | Una comisión de plataforma se deduce de la cuenta. | Por documentar |
| Origen dispersión | Se crea una entrada de ledger como resultado de una dispersión. | Por documentar |
| Presentación de tarjeta | Se crea una entrada de ledger cuando una autorización de tarjeta se liquida. | Por documentar |
| Reverso | Se revierte una entrada de ledger previa. | Por documentar |
## Siguientes pasos [#siguientes-pasos]
* [Webhooks de transacciones de cuenta de ledger](/docs/guides/core/webhooks/ledger-account-transactions) — los payloads de evento disparados para cada escenario anterior.
* [Dispersiones](/docs/guides/core/sandbox/payouts) — simula entradas de ledger con origen de dispersión.
# Tarjetas (/es/docs/guides/core/sandbox/cards)
Usa estos casos de prueba para validar los flujos de emisión de tarjetas — estados de creación, activación, congelamiento, descongelamiento y reposición — sin aprovisionar credenciales reales de tarjeta.
Esta página es un esqueleto de sandbox. La versión completa incluirá casos de prueba
deterministas (valores de campo específicos que producen cada estado de tarjeta) para
escenarios de creación, activación, congelamiento/descongelamiento y reposición.
## Escenarios de prueba [#escenarios-de-prueba]
| Escenario | Descripción | Caso de prueba |
| :-------------------------------------------- | :--------------------------------------------------------------------------------------- | :------------- |
| Tarjeta virtual creada activa | Una tarjeta virtual se aprovisiona y es usable de inmediato. | Por documentar |
| Tarjeta física creada pendiente de activación | Una tarjeta física se aprovisiona en estado `created` y requiere activación del titular. | Por documentar |
| Congelamiento de tarjeta | Una tarjeta se congela y todos los intentos de autorización siguientes son rechazados. | Por documentar |
| Descongelamiento de tarjeta | Una tarjeta congelada se descongela y las autorizaciones se reanudan. | Por documentar |
| Reposición de tarjeta | Una tarjeta existente se repone con un nuevo id de tarjeta y nuevas credenciales. | Por documentar |
## Siguientes pasos [#siguientes-pasos]
* [Transacciones de tarjeta](/docs/guides/core/sandbox/card-transactions) — simula autorizaciones y rechazos sobre una tarjeta de sandbox.
* [Flujo de emisión de tarjetas](/docs/guides/core/flows/issuing-cards) — la secuencia completa de emisión en producción.
# Transacciones de tarjeta (/es/docs/guides/core/sandbox/card-transactions)
Usa estos casos de prueba para validar cómo tu integración maneja eventos de transacciones de tarjeta — aprobaciones, rechazos por motivo, retiros, settlements parciales, reversos, reembolsos y conciliación.
Esta página es un esqueleto de sandbox. La versión completa incluirá casos de prueba
deterministas (números de tarjeta o montos específicos que disparan cada escenario) para cada
tipo de evento del catálogo de webhooks de transacciones de tarjeta.
## Escenarios de prueba [#escenarios-de-prueba]
| Escenario | Descripción | Caso de prueba |
| :-------------------------------------------- | :-------------------------------------------------------------------------------------- | :------------- |
| Autorización aprobada | La transacción de tarjeta se autoriza y el saldo es cargado. | Por documentar |
| Autorización rechazada — fondos insuficientes | La transacción se rechaza porque el saldo de la cuenta es muy bajo. | Por documentar |
| Autorización rechazada — fraude | La transacción se rechaza por una marca de fraude. | Por documentar |
| Autorización rechazada — límite excedido | La transacción se rechaza porque se alcanzó un límite de gasto. | Por documentar |
| Retiro | Un retiro en ATM se autoriza, incluyendo el `withdrawal_fee`. | Por documentar |
| Settlement parcial (ajuste de crédito) | Un monto autorizado se reembolsa parcialmente en el settlement. | Por documentar |
| Ajuste de débito | El monto autorizado se incrementa en el settlement. | Por documentar |
| Reverso | Una autorización se revierte completamente y el cargo se devuelve. | Por documentar |
| Reembolso | Se emite un reembolso a la cuenta. | Por documentar |
| Conciliación | El monto liquidado difiere del monto autorizado después del settlement final de la red. | Por documentar |
## Siguientes pasos [#siguientes-pasos]
* [Webhooks de transacciones de tarjeta](/docs/guides/core/webhooks/card-transactions) — los payloads de evento disparados para cada escenario anterior.
* [Tarjetas](/docs/guides/core/sandbox/cards) — configura una tarjeta de sandbox antes de correr escenarios de transacciones.
# Sandbox (/es/docs/guides/core/sandbox)
El sandbox de Core es una copia completa y aislada del ambiente de producción. Puedes crear cuentas de ledger, emitir tarjetas, simular autorizaciones y disparar dispersiones — todo sin mover dinero real ni afectar a ningún cliente real. Es el lugar correcto para desarrollar tu integración de extremo a extremo antes de solicitar credenciales de producción.
Las credenciales de sandbox son distintas a las de producción y no son intercambiables. Confirma siempre a qué ambiente apunta tu código antes de correr cualquier cosa.
## Antes de empezar [#antes-de-empezar]
Necesitas una cuenta de Mono con acceso a sandbox habilitado, y credenciales de sandbox: una API key o una credencial OAuth 2.0, obtenidas como se describe en [Autenticación](/docs/guides/core/authentication). Las credenciales de sandbox solo funcionan contra la URL base de sandbox de abajo.
## Base URL [#base-url]
```
https://api.sandbox.cuentamono.com
```
## Escenarios de prueba [#escenarios-de-prueba]
Estados de creación, activación y controles de tarjetas
Autorizaciones, rechazos, settlements y reversos
Movimientos de ledger, operaciones de subcuenta y comisiones
Escenarios de éxito, falla y reintento de dispersiones
Simula que un pagador paga tus plantillas e intenciones de pago
## Siguientes pasos [#siguientes-pasos]
* [Arquitectura](/docs/guides/core/architecture)
* [Autenticación](/docs/guides/core/authentication)
* [Lista de verificación de producción](/docs/guides/core/production)
# Validación de pagos en recaudos (/es/docs/guides/breb-participant/architecture/payment-validation)
Cuando un cliente paga uno de tus recaudos, Mono valida el pago antes de
aceptarlo. Las reglas son las que definiste al crear el recaudo: cuánto se puede
pagar, hasta cuándo y quién puede pagar. Un pago que rompe una regla se rechaza
automáticamente, y la razón de rechazo te dice exactamente qué regla rompió.
Esta página explica cada regla y cada razón que puedes recibir.
Un **recaudo** (collection) es tu punto de cobro Bre-B (un código QR o una llave de pago).
Cada pago individual contra él es un **intento de pago** (attempt), con su propio estado —
separado del [ciclo de vida del
recaudo](/docs/guides/breb-participant/architecture/collection-states). Un intento rechazado
**no** cambia el estado del recaudo; solo incrementa su contador `failed_attempts`.
## Cómo se valida un intento de pago [#cómo-se-valida-un-intento-de-pago]
Cada pago entrante se convierte en un intento que termina en uno de tres
desenlaces:
| Estado del intento | Significado | Final |
| ------------------ | -------------------------------------------------------------- | ----- |
| `created` | Intento recibido, validación en curso | No |
| `successful` | El pago fue exitoso | Sí |
| `rejected` | Mono rechazó el pago con base en las reglas del recaudo | Sí |
| `failed` | El pago falló por causas externas (banco, proveedor, timeouts) | Sí |
Para intentos `rejected` y `failed`, el campo `state_reason` del intento nombra
la causa exacta — la lista completa está en la
[tabla de referencia](#referencia-de-razones-de-rechazo).
## Las reglas que tú controlas [#las-reglas-que-tú-controlas]
### Quién puede pagar — `expected_payers` [#quién-puede-pagar--expected_payers]
Si envías `expected_payers` al crear el recaudo, **solo esos pagadores quedan
permitidos** — cualquier otro se rechaza con `payer_not_allowed`. Cada pagador
esperado se identifica por su documento de identidad colombiano:
* `document_type` — uno de `CC`, `CE`, `NIT`, `NUIP`, `PPT`, `PEP`, `PASS`, `TI`
* `document_number` — el número de documento del pagador esperado
Deja `expected_payers` vacío para aceptar pagos de cualquier persona. Mira
[UC6 — Factura a cliente](/docs/guides/breb-participant/use-cases/collections)
para un ejemplo armado.
### Cuánto se puede pagar — límites de monto [#cuánto-se-puede-pagar--límites-de-monto]
Todos los montos son enteros en **centavos** (p. ej. `10000000` =
COP 100.000,00). Aplican dos niveles de límites:
| Campo | Aplica a | Regla | Razón de rechazo |
| ------------------------ | ------------------ | ----------------------------------------------------------------------------------- | ------------------------------ |
| `minimum_attempt_amount` | cada intento | Monto mínimo permitido por intento de pago (solo recaudos `multiple_use`) | `amount_below_minimum` |
| `maximum_attempt_amount` | cada intento | Monto máximo permitido por intento de pago (solo recaudos `multiple_use`) | `amount_exceeds_maximum` |
| `total_maximum_amount` | el total acumulado | Máximo total que el recaudo puede recibir — al alcanzarlo, el recaudo pasa a `paid` | `amount_exceeds_total_maximum` |
| `total_minimum_amount` | el total acumulado | Mínimo total requerido para que el recaudo se considere cumplido | `amount_below_total_minimum` |
`usage_mode` decide cómo se acumulan los intentos:
* `single_use` — el recaudo acepta **solo un** pago exitoso.
* `multiple_use` — el recaudo acepta múltiples pagos hasta alcanzar
`total_maximum_amount`.
### Hasta cuándo — `expires_at` / `expires_in` [#hasta-cuándo--expires_at--expires_in]
Define el plazo del recaudo como fecha absoluta ISO-8601 (`expires_at`) o en
segundos desde ahora (`expires_in`) — son mutuamente excluyentes en el request.
Un pago después del plazo se rechaza con `collection_expired`. Mira
[UC7 — Venta flash](/docs/guides/breb-participant/use-cases/collections)
para un ejemplo armado.
### Encendido o apagado — `enabled` [#encendido-o-apagado--enabled]
`enabled` controla si el recaudo puede recibir pagos. Los pagos contra un
recaudo deshabilitado se rechazan con `collection_disabled` — útil para pausar
un recaudo sin eliminarlo.
## Referencia de razones de rechazo [#referencia-de-razones-de-rechazo]
El `state_reason` del intento (presente solo en intentos `rejected` / `failed`)
nombra la causa:
**Ligadas a las reglas y el estado de tu recaudo:**
| `state_reason` | El pago se rechazó porque… |
| ------------------------------ | -------------------------------------------------------------------- |
| `payer_not_allowed` | el pagador no está en la lista `expected_payers` del recaudo |
| `amount_below_minimum` | el intento está por debajo de `minimum_attempt_amount` |
| `amount_exceeds_maximum` | el intento está por encima de `maximum_attempt_amount` |
| `amount_exceeds_total_maximum` | empujaría el total recibido más allá de `total_maximum_amount` |
| `amount_below_total_minimum` | está por debajo del requisito `total_minimum_amount` del recaudo |
| `collection_expired` | ya pasó el plazo `expires_at` / `expires_in` del recaudo |
| `collection_disabled` | el recaudo tiene `enabled: false` |
| `collection_paid` | el recaudo ya llegó a `paid` (su máximo total, o era de un solo uso) |
| `collection_discarded` | el recaudo fue descartado |
| `collection_invalid_state` | el recaudo no está en un estado que acepte pagos |
| `collection_not_found` | ninguna llave de recaudo coincide con el pago |
| `collection_pruning` | la llave del recaudo está en proceso de poda (pruning) |
**Causas externas / operativas** (fuera de la configuración de tu recaudo):
`rejected_by_bank`, `provider_unavailable`, `lock_timeout`, `risk_control`,
`internal_error`, `mono_timeout`, `breb_timeout`, `unknown`.
## Observar rechazos en tu integración [#observar-rechazos-en-tu-integración]
* **Webhooks** — cada intento no exitoso dispara
[`collection.attempt_unsuccessful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_unsuccessful)
con el intento y su `state_reason`; los exitosos disparan
[`collection.attempt_successful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_successful).
* **Consulta directa** — lista los intentos con
[List collection attempts](/docs/api-reference/breb-participant/collections/list_collection_attempts_by_collection).
* Un intento rechazado nunca saca al recaudo de su estado actual — solo
incrementa el contador `failed_attempts` del recaudo.
## Siguientes pasos [#siguientes-pasos]
* [Ciclo de vida del recaudo](/docs/guides/breb-participant/architecture/collection-states) — cómo el recaudo mismo se mueve entre estados.
* [Casos de uso de recaudos](/docs/guides/breb-participant/use-cases/collections) — combinaciones de reglas listas para usar (precio fijo, pagador restringido, venta flash…).
* [Create collections](/docs/api-reference/breb-participant/collections/create_collections) — la referencia completa del request con todos los campos de esta página.
# Visión general de arquitectura (/es/docs/guides/breb-participant/architecture)
**Bre-B** es el esquema interbancario de pagos instantáneos de Colombia — un rail en tiempo real similar a Pix de Brasil o UPI de India que permite a cualquier banco participante o entidad licenciada enviar y recibir fondos 24/7 en segundos. La API de Bre-B Participant te da una rampa programable a esa red: te autenticas con OAuth, registras cuentas tenant, generas códigos QR para pagos entrantes y despachas transferencias salientes contra llaves de pago (alias como teléfono, documento, email, alfanumérico o código de comercio).
Esta página es el punto de partida correcto si quieres entender el camino que toma un request desde tu código hasta el directorio central de Bre-B y de vuelta.
## Componentes [#componentes]
| Componente | Rol |
| :------------------------ | :------------------------------------------------------------------ |
| Servicio de autenticación | Emite access tokens OAuth para la API de Participant. |
| Cuentas tenant | Cuentas lógicas en Mono que mantienen saldos y reciben pagos Bre-B. |
| Target resolution | Busca una llave de pago contra el directorio central de Bre-B. |
| Transferencias salientes | Despacha un pago a un target resuelto. |
| Recaudos | Genera códigos QR y concilia pagos entrantes. |
| Webhooks | Eventos asíncronos de resoluciones, transferencias y recaudos. |
## Ciclo de vida del request (transferencia saliente) [#ciclo-de-vida-del-request-transferencia-saliente]
1. **Autenticación** — intercambia las credenciales de cliente por un access token OAuth.
2. **Resuelve el target** — envía la llave de pago; recibes el nombre y banco del titular.
3. **Despacha** — envía el target resuelto con monto y clave de idempotencia.
4. **Rastrea el ciclo de vida** — los webhooks se disparan en cada transición de estado.
5. **Concilia** — acredita o libera la operación en tus libros.
## Modelo de tenants [#modelo-de-tenants]
Cada cliente es un tenant participante de Bre-B en Mono. Las cuentas tenant están aisladas; las resoluciones y transferencias siempre operan contra un tenant específico.
## Referencias de ciclo de vida [#referencias-de-ciclo-de-vida]
Una vez que un request entra a la plataforma se mueve a través de una máquina de estados determinística, y cada transición emite un webhook. Los dos flujos tienen sus propias páginas de ciclo de vida — léelas junto con la arquitectura de arriba para entender qué debería estar escuchando tu integración.
* [Ciclo de vida de la transferencia saliente](/docs/guides/breb-participant/architecture/outgoing-transfer-states) — estados, eventos de webhook y resultados terminales para los pagos que despachas.
* [Ciclo de vida del recaudo](/docs/guides/breb-participant/architecture/collection-states) — estados, eventos de webhook y resultados terminales para códigos QR y llaves de pago que reciben fondos.
## Siguientes pasos [#siguientes-pasos]
* [Autenticación](/docs/guides/breb-participant/authentication)
* [Conceptos](/docs/guides/breb-participant/concepts)
* [Ejemplo de integración](/docs/guides/breb-participant/integration-example)
# Ciclo de vida del recaudo (/es/docs/guides/breb-participant/architecture/collection-states)
Un **recaudo** es un endpoint receptor de Bre-B — típicamente un código QR o
una llave de pago compartible — que permite a uno o más pagadores enviar fondos
a tu cuenta tenant. Desde el momento en que lo creas, el recaudo se mueve por una
secuencia determinística de estados. Cada transición mapea a un evento de webhook,
así que tu integración puede rastrear el progreso sin polling. Esta guía explica qué
significa cada estado, qué hace que un recaudo se mueva entre ellos y cómo distinguir
el éxito terminal de la falla terminal.
## Diagrama de estados [#diagrama-de-estados]
## Estados de un vistazo [#estados-de-un-vistazo]
| Estado | ¿Terminal? | Evento de webhook |
| -------------- | ---------- | --------------------------------------------------------------------------------------------------------------------- |
| `created` | No | [`collection.created`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_created) |
| `ready` | No | [`collection.ready`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_ready) |
| `minimum_paid` | No | [`collection.minimum_paid`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_minimum_paid) |
| `paid` | **Sí** | [`collection.paid`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_paid) |
| `discarded` | **Sí** | [`collection.discarded`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_discarded) |
| `failed` | **Sí** | [`collection.failed`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_failed) |
Los intentos de pago individuales contra un recaudo `ready` o `minimum_paid` tienen su propia
máquina de estados (`successful`, `rejected`, `failed`) y emiten sus propios eventos —
[`collection.attempt_successful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_successful)
y
[`collection.attempt_unsuccessful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_unsuccessful).
Un intento fallido **no** saca al recaudo de su estado actual — solo incrementa el contador
`failed_attempts` del recaudo.
## Recorrido detallado [#recorrido-detallado]
### 1. `created` [#1-created]
Estado inicial. Un recaudo entra a este estado tan pronto como
[create collection](/docs/api-reference/breb-participant/collections/create_collection)
acepta tu request. La fila del recaudo existe en nuestro sistema y tiene un `id`
que puedes usar para búsquedas, pero la llave Bre-B subyacente todavía no fue
registrada con el directorio central (DICE), así que aún no puede aceptar pagos.
Si el request falla la validación síncrona — por ejemplo, los límites de monto son inválidos o
el formato de la llave está mal formado — la API devuelve una respuesta 4xx y el recaudo
**no** se persiste. Nunca pasará por esta máquina de estados y no se disparará ningún webhook.
**Qué pasa después:** el sistema registra la llave Bre-B con DICE. Esto
típicamente se completa en segundos.
### 2. `ready` [#2-ready]
La llave Bre-B fue registrada exitosamente con DICE y el arreglo `keys` del
recaudo está poblado con la llave activa. El recaudo ya está vivo y puede
aceptar pagos entrantes — los pagadores pueden escanear el QR o enviar a la llave.
**Siguientes estados:**
* → `minimum_paid` (solo recaudos multi-uso) una vez que el `paid_amount` acumulado
alcanza o supera el `total_minimum_amount`.
* → `paid` una vez que el `paid_amount` acumulado alcanza el `total_maximum_amount`. Para
recaudos de un solo uso esto pasa en el primer pago exitoso.
* → `discarded` si el recaudo se borra, expira o se auto-descarta
por inactividad antes de ser pagado.
### 3. `minimum_paid` [#3-minimum_paid]
Aplica solo a recaudos **multi-uso** que definen tanto un mínimo como un
máximo. El `paid_amount` acumulado cruzó
`total_minimum_amount`, lo que significa que el recaudo cumplió su objetivo soft pero
sigue abierto a pagos adicionales hasta `total_maximum_amount`.
Este estado es útil para escenarios de pago parcial — por ejemplo, una
recolección por objetivo que cumplió su meta pero sigue aceptando contribuciones hasta
un tope duro.
**Siguientes estados:**
* → `paid` una vez que `paid_amount` alcanza `total_maximum_amount`.
* → `discarded` por expiración o inactividad.
### 4. `paid` (terminal) [#4-paid-terminal]
El recaudo fue pagado por completo. `paid_amount` es igual a
`total_maximum_amount`. El recaudo ya no acepta pagos y no
ocurren más transiciones de estado.
### 5. `discarded` (terminal) [#5-discarded-terminal]
El recaudo fue removido del conjunto activo. El campo `state_reason`
te dice por qué:
* `deleted` — removido explícitamente vía
[delete collection](/docs/api-reference/breb-participant/collections/delete_collection).
* `expired` — el recaudo pasó su `expires_at` sin alcanzar un estado terminal
de pagado.
* `inactivity` — el recaudo estuvo inactivo lo suficiente como para ser auto-descartado por
el sistema.
No ocurren más transiciones de estado.
### 6. `failed` (terminal) [#6-failed-terminal]
El recaudo no pudo ser usable. Esto pasa cuando el registro de la llave
Bre-B falla entre `created` y `ready`. El campo `state_reason`
lleva la causa — valores comunes incluyen `key_already_registered`,
`key_registration_failed` y `key_canceled`.
Los recaudos fallidos no aceptan pagos y nunca vuelven a transicionar. Para
recuperar, crea un nuevo recaudo (típicamente con otra llave).
## Trabajando con estados en tu integración [#trabajando-con-estados-en-tu-integración]
1. **Trata cada estado no terminal como transitorio.** Un recaudo en `created`
aún no es usable — espera `ready` antes de mostrar el QR o la llave a los
pagadores.
2. **Trabaja con webhooks, no con polling.** Cada transición de estado emite un webhook
con el nuevo estado y, donde aplique, un `state_reason`. Verifica cada
webhook (ver [Verificación de firma de webhook](/docs/guides/breb-participant/webhook-signatures))
y actualiza tu registro local de forma idempotente — el mismo estado puede ser
reintentado por nuestros workers.
3. **Maneja entrega fuera de orden.** Las entregas de webhook son at-least-once. Si
ves `paid` antes que `minimum_paid`, conserva el estado más reciente e ignora el
anterior. Una regla simple: si el recaudo ya está en un estado terminal
localmente, ignora los webhooks no terminales.
4. **Distingue el estado del recaudo del estado del intento.** Un recaudo `ready`
puede tener muchos eventos `collection.attempt_unsuccessful` sin dejar el
estado `ready`. Surface los fallos de intento por separado del estado del recaudo en
tu UI y lógica de conciliación.
5. **Tres estados son finales.** `paid`, `discarded` y `failed` son los únicos
estados terminales. Una vez que un recaudo alcanza cualquiera de ellos, nunca volverá a
moverse.
# Ciclo de vida de la transferencia saliente (/es/docs/guides/breb-participant/architecture/outgoing-transfer-states)
Una transferencia saliente (un pago saliendo de tu cuenta por Bre-B) se mueve por una
secuencia determinística de estados. Cada transición mapea a un evento de webhook,
así que tu integración puede rastrear el progreso sin polling. Esta guía explica
qué significa cada estado, qué hace que una transferencia se mueva entre ellos y cómo
distinguir el éxito terminal de la falla terminal.
## Diagrama de estados [#diagrama-de-estados]
## Estados de un vistazo [#estados-de-un-vistazo]
| Estado | ¿Terminal? | Evento de webhook |
| ----------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `created` | No | [`outgoing_transfer.created`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_created) |
| `processing` | No | [`outgoing_transfer.processing`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_processing) |
| `target_resolved` | No | [`outgoing_transfer.target_resolved`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_target_resolved) |
| `held` | No | [`outgoing_transfer.held`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_held) |
| `sent_to_breb_provider` | No | [`outgoing_transfer.sent_to_breb_provider`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_send_to_breb_provider) |
| `successful` | **Sí** | [`outgoing_transfer.successful`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_successful) |
| `failed` | **Sí** | [`outgoing_transfer.failed`](/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_failed) |
## Recorrido detallado [#recorrido-detallado]
### 1. `created` [#1-created]
Estado inicial. Una transferencia entra a este estado tan pronto como
[create outgoing transfers](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers)
la acepta en un batch. La fila de la transferencia existe en nuestro sistema y tiene un `id`
que puedes usar para búsquedas.
Si el request falla la validación previa al insert — por ejemplo, el monto supera el límite
máximo, o el target es inválido — la transferencia se devuelve en el arreglo
`rejected_transfers` de la respuesta del batch y **no** se persiste. Nunca pasará por esta
máquina de estados y no se disparará ningún webhook. Ver [Códigos de error de transferencia
saliente](/docs/guides/breb-participant/outgoing-transfer-errors) para la lista de motivos de
rechazo.
**Qué pasa después:** un worker en background toma la transferencia y la mueve
a `processing`. Esto típicamente ocurre en segundos.
### 2. `processing` [#2-processing]
El sistema está procesando activamente la transferencia. El camino que toma depende
de cómo se proveyó el target:
* **Transferencias por plain-key** (proveíste una llave Bre-B en `query`): el sistema
primero debe resolver la llave a un destinatario antes de poder retener fondos.
* **Target pre-resuelto** (proveíste un `target_id` existente): la resolución se
salta y la transferencia va directo al hold de fondos. Si ese target se resolvió
con un `expected_creditor`, la coincidencia ya la validó
[Resolver target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target)
y la transferencia lleva el mismo `expected_creditor`.
**Siguientes estados:**
* → `target_resolved` si la resolución tiene éxito (camino plain-key) **o** el target
ya estaba resuelto.
* → `failed` si la resolución da error — por ejemplo, `key_not_found`,
`key_suspended` o `invalid_key_format`.
### 3. `target_resolved` [#3-target_resolved]
La llave Bre-B del destinatario fue resuelta a un creditor concreto y una cuenta
bancaria. Si proveíste un `expected_creditor` en una transferencia por `query`, ahí
es donde validamos que el creditor resuelto coincida. Esta es la **última oportunidad
de rechazar la transferencia antes de que los fondos se muevan**.
**Siguientes estados:**
* → `held` una vez que los fondos están retenidos en la cuenta origen.
* → `failed` con `state_reason = target_creditor_mismatch` si el documento del creditor
resuelto no coincide con el `expected_creditor` que enviaste. Este es
el resguardo que evita pagar a la persona equivocada cuando una llave se reasigna.
### 4. `held` [#4-held]
Fondos equivalentes al monto de la transferencia están reservados en tu cuenta origen. El
dinero salió de tu saldo disponible pero todavía no fue enviado al banco
del destinatario. En este punto la transferencia está totalmente comprometida desde tu lado.
**Siguientes estados:**
* → `sent_to_breb_provider` una vez que despachamos la instrucción de pago al
proveedor Bre-B (Credibanco).
* → `failed` si el hold mismo falla (raro en este punto — la mayoría de errores de fondos
emergen antes). `state_reason` llevará la causa (e.g.,
`insufficient_funds`).
### 5. `sent_to_breb_provider` [#5-sent_to_breb_provider]
El pago fue entregado al proveedor Bre-B para settlement. Ahora está
en vuelo a través de la red Bre-B. La transferencia está esperando el callback
final de settlement del proveedor.
La mayoría de transferencias settlea en segundos, pero timeouts del proveedor y la
disponibilidad de los bancos aguas abajo pueden estirar esto a minutos. No asumas que una
transferencia falló solo porque no salió rápido de este estado — espera el webhook terminal.
**Siguientes estados:**
* → `successful` con un callback de settlement positivo.
* → `failed` con un callback de settlement negativo (e.g., `breb_timeout`,
`provider_unavailable`, `risk_control` o un fallback `unknown`).
### 6. `successful` (terminal) [#6-successful-terminal]
Los fondos fueron acreditados al destinatario. No hay más transiciones; no se
disparan más webhooks para esta transferencia.
### 7. `failed` (terminal) [#7-failed-terminal]
La transferencia no se completó. El campo `state_reason` en la transferencia (y
en el webhook `outgoing_transfer.failed`) te dice por qué. Dependiendo del
motivo, el error puede ser reintentable enviando una nueva transferencia — ver la
guía de reintentos en
[Códigos de error de transferencia saliente](/docs/guides/breb-participant/outgoing-transfer-errors#4-guía-de-reintentos).
* Las transferencias **rechazadas** se devuelven de forma síncrona en `rejected_transfers`
cuando creas un batch. Nunca se convierten en transferencias en nuestro sistema. - Las
transferencias **fallidas** fueron aceptadas, persistidas, pasaron por al menos `created` y
luego golpearon un error. Tienen un `state_reason` y emiten un webhook.
## Trabajando con estados en tu integración [#trabajando-con-estados-en-tu-integración]
1. **Trata cada estado no terminal como transitorio.** Una transferencia en `held` o
`sent_to_breb_provider` sigue en vuelo — muéstrala como "en proceso" en
tu UI en vez de "completa".
2. **Trabaja con webhooks, no con polling.** Cada transición de estado emite un webhook
con el nuevo estado y, en caso de falla, un `state_reason`. Verifica cada webhook
(ver [Verificación de firma de webhook](/docs/guides/breb-participant/webhook-signatures))
y actualiza tu registro local de forma idempotente — el mismo estado puede ser
reintentado por nuestros workers.
3. **Maneja entrega fuera de orden.** Las entregas de webhook son at-least-once. Si
ves `successful` antes que `sent_to_breb_provider`, conserva el estado más reciente
e ignora el anterior. Una regla simple: si la transferencia ya está en un estado
terminal localmente, ignora los webhooks no terminales.
4. **Solo dos estados son finales.** `successful` y `failed` son los únicos
estados terminales. Una vez que una transferencia alcanza cualquiera, nunca volverá a moverse.
# Contabilidad de ledger (/es/docs/guides/core/flows/ledger-accounting)
Una **transacción de ledger** es la unidad de contabilidad en Core: un par balanceado de entradas de débito y crédito que juntas describen un movimiento de dinero. Cada cambio a los saldos pasa por una transacción de ledger — no hay forma de subir un saldo en silencio, y no hay forma de perder rastro de dónde vino el dinero o hacia dónde fue.
Este flujo cubre los patrones que vas a usar con más frecuencia: registrar una transacción simple de dos entradas, retener fondos antes del settlement, liquidar un hold y revertir una entrada cuando algo sale mal.
Esta página es un esqueleto de flujo. La versión completa incluirá snippets de código para
cada patrón, un diagrama de secuencia en Mermaid y un ejemplo trabajado de conciliación.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar una API key de Core, dos o más cuentas de ledger entre las cuales mover fondos y una [clave de idempotencia](/docs/guides/api-standards/idempotency-keys) por cada intento de registro.
## Patrones [#patrones]
### Registrar una transacción [#registrar-una-transacción]
El patrón más simple: debitar una cuenta, acreditar otra, ambas en la misma divisa, ambas por el mismo monto. La transacción se registra atómicamente — ambas entradas aterrizan o ninguna lo hace.
### Hold y settle [#hold-y-settle]
Para autorizaciones de tarjeta y cualquier caso donde te comprometes a un débito futuro, primero registra un **hold** y luego ejecuta un **settle**. El hold reserva fondos; el settle convierte la reserva en un débito permanente. Si el hold no se liquida dentro de su ventana, expira y los fondos se liberan.
### Revertir una transacción [#revertir-una-transacción]
Los reversos deshacen una transacción registrada registrando su imagen espejo. El original se preserva — el ledger de Core es append-only — para que la traza de auditoría se mantenga intacta.
### Cross-currency [#cross-currency]
Cuando las cuentas de débito y crédito están en distintas divisas, la transacción debe referenciar una [tasa FX](/docs/guides/core/concepts/fx-rates).
## Pasos [#pasos]
El paso a paso completo (con código) está pendiente. Hasta que se publique, ver la [página de concepto de Ledger](/docs/guides/core/concepts/ledger) y la [referencia de API de ledger de Core](/docs/api-reference/core/ledger).
## Siguientes pasos [#siguientes-pasos]
* [Emisión de tarjetas](/docs/guides/core/flows/issuing-cards) — las tarjetas gastan a través de holds y settlements del ledger.
* [Dispersión](/docs/guides/core/flows/payout-disbursement) — las dispersiones son transacciones de ledger más un riel.
* [Buenas prácticas](/docs/guides/core/best-practices) — patrones de conciliación.
# Dispersión (/es/docs/guides/core/flows/payout-disbursement)
Una **dispersión** es el flujo de extremo a extremo de mover dinero hacia afuera de una cuenta de ledger de Core: disparar la dispersión, observar su transición por el ciclo de vida, manejar éxito o falla y conciliar el ledger. Las dispersiones internas (de cuenta a cuenta dentro de Core) se liquidan instantáneamente; las externas (a cuentas bancarias colombianas) viajan por los rieles de Banking y heredan los tiempos de confirmación de Banking.
Usa este flujo cuando un saldo le sea exigible a su dueño — un usuario de billetera solicita un retiro, se cierra la ventana de settlement de un vendedor de marketplace, corre la nómina o un barrido de tesorería mueve fondos entre cuentas internas.
Esta página es un esqueleto de flujo. La versión completa incluirá ejemplos de código para
dispersiones internas y externas, el diagrama de estados, la relación con las transferencias
de Banking y patrones de conciliación.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una API key de Core con el scope `payouts`.
* Una cuenta de ledger origen con saldo suficiente.
* Un destino — otra cuenta de ledger (interna) o una cuenta bancaria colombiana (externa).
* Un endpoint de webhook suscrito a eventos de dispersión.
* Una [clave de idempotencia](/docs/guides/api-standards/idempotency-keys) por dispersión.
* Tus políticas de fees para `bank_transfer` y `bank_transfer_gmf`, si le cobras a tus usuarios por las dispersiones. Mono crea las base — ver [Políticas de fees](/docs/guides/core/concepts/fees/policies).
## Resumen de la secuencia [#resumen-de-la-secuencia]
### Dispersión interna [#dispersión-interna]
1. Envía la dispersión con origen, destino, monto y clave de idempotencia.
2. Core registra una transacción de ledger balanceada.
3. El webhook se dispara con el estado final.
### Dispersión externa [#dispersión-externa]
1. Envía la dispersión con origen, cuenta bancaria destino, monto y clave de idempotencia.
2. Core le pone precio a los fees con tus políticas de fees, registra un hold sobre la cuenta origen por el monto más los fees y despacha la transferencia a Banking. Los fees vuelven en `calculated_fees`.
3. Banking enruta la transferencia (Turbo, ACH, Transfiya) y devuelve una confirmación.
4. Core convierte el hold en un settlement en caso de éxito, o lo libera en caso de rechazo.
5. El webhook se dispara con el estado final.
## Pasos [#pasos]
El paso a paso completo está pendiente. Hasta que se publique, ver:
* [Página de concepto de Dispersiones](/docs/guides/core/concepts/payouts).
* [Página de concepto de transferencias de Banking](/docs/guides/banking/concepts/transfers) — para destinos externos.
* [Referencia de API de dispersiones de Core](/docs/api-reference/core/payouts).
## Siguientes pasos [#siguientes-pasos]
* [Contabilidad de ledger](/docs/guides/core/flows/ledger-accounting)
* [Flujo de transferencias de Banking](/docs/guides/banking/flows/sending-transfers)
* [Idempotency Keys](/docs/guides/api-standards/idempotency-keys)
# Emisión de tarjetas (/es/docs/guides/core/flows/issuing-cards)
La **emisión** es el proceso de crear una tarjeta que gasta contra una cuenta de ledger de Core, ponerla en manos del usuario y gestionar su ciclo de vida. Una tarjeta puede ser virtual (un número de 16 dígitos instantáneo para uso en línea o en la app) o física (una tarjeta plástica que se envía al tarjetahabiente), y puede aprovisionarse a una billetera digital como Apple Pay o Google Wallet para habilitar pagos contactless.
Usa este flujo cuando tu producto necesite entregar una tarjeta a tus usuarios finales — billeteras de consumo, programas corporativos de gastos, dispersiones a vendedores de marketplace sobre una tarjeta, o cualquier caso donde quieras que el dinero sea gastable directamente desde un saldo de ledger.
Esta página es un esqueleto de flujo. La versión completa incluirá código por paso, el
diagrama de estados del ciclo de vida, el handshake de aprovisionamiento a billetera digital y
el flujo de autorización.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una API key de Core con el scope `cards`.
* Una cuenta de ledger contra la cual la tarjeta va a gastar.
* Una configuración de control de gasto apropiada para el tarjetahabiente.
* Un endpoint de webhook suscrito a [eventos de tarjeta y de transacción de tarjeta](/docs/guides/core/webhooks).
## Resumen de la secuencia [#resumen-de-la-secuencia]
1. **Crear la tarjeta** — llama al endpoint de creación de tarjeta con la cuenta de ledger vinculada, los datos del tarjetahabiente y una referencia al control de gasto.
2. **Activar la tarjeta** — las tarjetas virtuales son usables de inmediato; las físicas requieren activación por parte del tarjetahabiente luego de la entrega.
3. **Aprovisionar a una billetera digital** (opcional) — genera un token de aprovisionamiento para que el tarjetahabiente pueda agregar la tarjeta a Apple Pay o Google Wallet.
4. **Autorizar transacciones** — Core decide cada autorización en tiempo real usando el saldo del tarjetahabiente y los controles de gasto activos. Los webhooks se disparan en cada autorización, settlement y reverso.
5. **Gestionar el ciclo de vida** — congelar, descongelar, reponer o terminar la tarjeta según se necesite.
## Pasos [#pasos]
El paso a paso completo está pendiente. Hasta que se publique, ver:
* [Página de concepto de Tarjetas](/docs/guides/core/concepts/cards) — modelo de datos y ciclo de vida.
* [Página de concepto de Controles de gasto](/docs/guides/core/concepts/spending-controls) — cómo configurar reglas.
* [Referencia de API de tarjetas de Core](/docs/api-reference/core/cards).
## Siguientes pasos [#siguientes-pasos]
* [Contabilidad de ledger](/docs/guides/core/flows/ledger-accounting) — cómo las autorizaciones se convierten en entradas de ledger.
* [Buenas prácticas](/docs/guides/core/best-practices)
# Transferencias entre subcuentas (/es/docs/guides/core/flows/account-to-account-transfers)
Una **transferencia entre subcuentas** mueve dinero del saldo de uno de tus usuarios al de otro — un pago entre personas dentro de tu billetera, un movimiento entre la cuenta en COP y la cuenta en USD de un mismo usuario, una corrección entre dos saldos. Las transferencias en la misma moneda son un movimiento de ledger simple. Cuando las dos subcuentas tienen monedas distintas, Core convierte el monto a una tasa de cambio y la transferencia se vuelve una **transferencia FX**, con dos fees a los que puedes ponerles precio: un cobro fijo por la conversión y tu margen sobre la tasa.
Este flujo hace parte del servicio de **ledger** de Core. Las transferencias FX deben estar habilitadas para el programa de las cuentas; Mono las habilita al configurar el programa.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una API key de Core con el scope `ledger`.
* Dos subcuentas activas, cuyos propietarios también estén activos, que te pertenezcan.
* Para transferencias FX: la funcionalidad habilitada para el programa de las subcuentas, y — si quieres ponerle precio a la conversión — tus políticas de fees para `account_to_account_fx` y `account_to_account_fx_spread`. Mono crea las base; ver [Políticas de fees](/docs/guides/core/concepts/fees/policies).
* Una [clave de idempotencia](/docs/guides/api-standards/idempotency-keys) por transferencia.
## Transferencias en la misma moneda [#transferencias-en-la-misma-moneda]
Envía la subcuenta origen, la subcuenta destino y un solo monto — `source_amount` o `target_amount`, el otro en `null`. Ambos son iguales, y la moneda debe ser la de ambas subcuentas. Core registra una transacción de ledger balanceada: un débito en el origen y un crédito en el destino, al instante. No aplican fees.
## Transferencias FX [#transferencias-fx]
Cuando las subcuentas tienen monedas distintas, el mismo request hace una conversión:
1. Envía `source_amount` (en la moneda de origen) o `target_amount` (en la moneda de destino); Core calcula el otro.
2. Opcionalmente envía `fx_rate`. Si lo omites, Core toma la tasa de mercado vigente — la que publica el [endpoint de tasas FX](/docs/guides/core/concepts/fx-rates) — y le suma tu spread. Si lo envías, Core lo usa tal cual y asume que ya incluye tu spread.
3. Core le pone precio a los dos fees de abajo, registra la transacción y devuelve los montos, la tasa aplicada y `calculated_fees`.
### Los dos fees [#los-dos-fees]
| Tipo de fee | Qué es | Cómo se cobra |
| :----------------------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- |
| `account_to_account_fx` | Un cobro fijo por la conversión, en la moneda de origen. | Como un movimiento propio dentro de la transacción de la transferencia (`add_posting`). |
| `account_to_account_fx_spread` | Tu margen sobre la tasa de cambio, como porcentaje sobre la tasa de mercado. | Incluido en la tasa aplicada (`upcharge`): el usuario simplemente recibe una tasa un poco peor. |
Ambos reciben su precio de tus [políticas de fees](/docs/guides/core/concepts/fees/policies) de esos tipos — por programa, propietario de cuenta o cuenta, como cualquier otro fee — y ambos se pueden reemplazar para una sola transferencia a través de `override_fees`: `fixed` y `tx_description` para el fee fijo, `percentage` para el spread.
### Cómo se calculan los montos [#cómo-se-calculan-los-montos]
`fx_rate` es la tasa de la moneda destino a la moneda origen — de COP a USD se ve como `4000`; de USD a COP, como `0.00025`. Con el fee fijo en la moneda de origen:
* **Dado el monto destino**: `source_amount = target_amount × fx_rate + fixed_fee`.
* **Dado el monto origen**: `target_amount = (source_amount − fixed_fee) ÷ fx_rate`.
Los resultados se redondean a los decimales de cada moneda. Sin un `fx_rate` explícito, y con un spread del 1% sobre una tasa de mercado de 4000, la tasa aplicada es `4000 × 1.01 = 4040`.
### Qué devuelve [#qué-devuelve]
La respuesta incluye `calculated_fees`, con una entrada por tipo de fee y el monto final de cada uno en la moneda de origen. El fee de spread es la diferencia entre convertir a la tasa aplicada y convertir a la tasa de mercado — lo que tu usuario pagó por la conversión por encima del precio de mercado. Esos son los montos contra los que conciliar; también aparecen, con su estado y la regla que les puso precio, en el [listado de fees](/docs/api-reference/core/fees/list_fees).
## Pasos [#pasos]
1. Consulta las dos subcuentas y confirma que ambas están activas, y que sus propietarios también lo están.
2. Decide qué monto fijas — lo que paga quien envía o lo que recibe quien recibe — y si pasas tu propio `fx_rate`.
3. Llama a [Realizar una transferencia entre cuentas de ledger](/docs/api-reference/core/ledger/create_ledger_account_transfer) con una clave de idempotencia. Un `201` devuelve la transferencia; repetir la misma clave devuelve la misma transferencia con un `200`.
4. Lee `calculated_fees` y los montos de la respuesta y muéstrale a tu usuario qué se cobró.
5. Para volver a encontrar una transferencia por tu propia referencia, usa [Buscar una operación de subcuenta exitosa](/docs/api-reference/core/ledger/find_successful_subaccount_operation).
## Siguientes pasos [#siguientes-pasos]
* [Políticas de fees](/docs/guides/core/concepts/fees/policies) — define el cobro por conversión y el spread por programa, propietario de cuenta o cuenta.
* [Tasas FX](/docs/guides/core/concepts/fx-rates) — de dónde sale la tasa de mercado.
* [Contabilidad de ledger](/docs/guides/core/flows/ledger-accounting) — la contabilidad que produce cada transferencia.
# Flujos (/es/docs/guides/core/flows)
Un **flujo** es un caso de uso completo de extremo a extremo que compone varios conceptos de Core. Mientras la sección de [Conceptos](/docs/guides/core/concepts) responde "qué es una cuenta de ledger, tarjeta o control de gasto?", los flujos responden "cómo registro un settlement, emito una tarjeta o disperso un pago?".
Los flujos de Core se agrupan por el tipo de operación que describen:
* **Operaciones de ledger** — mover dinero dentro de los libros de Core, incluidas las transferencias entre subcuentas con o sin conversión de moneda.
* **Emisión** — crear tarjetas y aprovisionarlas a billeteras digitales.
* **Recaudos** — recibir dinero en subcuentas: recargas, recaudo con llave estática, intenciones de pago individuales y liquidación.
* **Dispersión** — sacar dinero de una cuenta de ledger.
## Flujos disponibles [#flujos-disponibles]
Contabilidad de partida doble con holds, settlements y reversos.
Mueve dinero entre dos subcuentas, en la misma moneda o entre monedas distintas, con fees.
Crear, activar, congelar y aprovisionar tarjetas a billeteras digitales.
Recargas, recaudo con llave estática, intenciones de pago individuales y liquidación.
Disparar una dispersión, gestionar el estado y recibir confirmación.
## Siguientes pasos [#siguientes-pasos]
* [Conceptos de Core](/docs/guides/core/concepts)
* [Ejemplo de integración](/docs/guides/core/integration-example)
* [Webhooks](/docs/guides/core/webhooks)
# Recaudos (/es/docs/guides/breb-participant/sandbox/collections)
Este entorno sandbox te permite probar tu integración de recaudos de extremo a extremo
sin procesar pagos reales. Puedes simular pagos entrantes a tus
recaudos, incluyendo escenarios exitosos y fallidos.
## Prerrequisitos [#prerrequisitos]
* Al menos un recaudo con una llave Bre-B **activa**.
* El recaudo asociado a la llave Bre-B debe estar en estado `ready` o `minimum_paid`.
## Simulador de Sandbox Collections [#simulador-de-sandbox-collections]
Si quieres simular un pago sin escribir código, abre el
[simulador sandbox de Bre-B](https://breb.app/uc/sandbox-simulator). Es una
herramienta web hospedada por Mono que paga cualquiera de tus llaves de
recaudo del sandbox de extremo a extremo — disparando exactamente los mismos
webhooks que un pago real.
### Tres modos de entrada [#tres-modos-de-entrada]
* **Ingresar una llave manualmente** (`Ingresar llave`) — pega un valor de llave Bre-B.
* **Escanear un QR** (`Escanear QR`) — usa la cámara de tu dispositivo para leer un QR Bre-B.
* **Subir una imagen de QR** (`Subir imagen`) — arrastra y suelta un QR guardado.
### Campos del formulario [#campos-del-formulario]
| Campo | Propósito |
| :------------------ | :---------------------------------------------------------------------- |
| `Llave de pago` | Llave Bre-B destino (se autocompleta al escanear o subir un QR). |
| `Monto (COP)` | Monto en pesos colombianos. **Requerido.** |
| `Tipo de error` | Opcional. Elige un modo de falla para ejercitar los caminos de rechazo. |
| `Datos del pagador` | Opcional. Sobrescribe la información del pagador autogenerada. |
Haz clic en `Simular Pago` y el simulador ejecuta el mismo flujo de extremo a
extremo que el endpoint `POST /sandbox/collection-attempts` documentado abajo —
es decir, los webhooks que recibes en tu endpoint son idénticos.
### Cuándo usar cada uno [#cuándo-usar-cada-uno]
| Usa el simulador web si... | Usa el endpoint de la API si... |
| :------------------------------------------------------------- | :--------------------------------------------------------- |
| Quieres una verificación rápida sin escribir un script | Estás agregando llamadas de sandbox a una suite de pruebas |
| Estás verificando un escenario específico a mano | Necesitas scriptar simulaciones en bulk |
| Aún no has montado tooling de sandbox (Postman, scripts, etc.) | Ya estás dentro de tu test runner |
## Simulando un intento de recaudo [#simulando-un-intento-de-recaudo]
Envía un request `POST` para simular a un pagador haciendo un pago a una de tus
llaves de recaudo. Este [endpoint](/docs/api-reference/breb-participant/sandbox/create_sandbox_collection_attempt) solo está disponible en el entorno sandbox.
### Ejemplo mínimo [#ejemplo-mínimo]
```json
{
"creditor_key_value": "@YOURBREBKEY",
"amount": {
"amount": 50000,
"currency": "COP"
}
}
```
El sandbox generará información aleatoria del pagador y un payment\_id si no se proveen, y procesará el pago
a través del flujo completo.
Recibirás una respuesta `202 Accepted` inmediatamente:
```json
{
"collection_id": "bbcol_5g8k2mNpQrStUvWx",
"attempt_id": "bbcolat_7hJkLmNpQrStUvWx",
"transfer_id": "bbit_1a2b3c4d5e6f7g8h9i0j",
"attempt_state_reason": null
}
```
Unos segundos después, los eventos de webhook se entregarán a tu URL de webhook configurada con el resultado final del intento.
Podrías recibir los siguientes webhooks de recaudo:
* [`collection.attempt_successful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_successful): El intento de pago fue settlleado exitosamente por el workflow de simulación.
* [`collection.attempt_unsuccessful`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_unsuccessful): El intento de pago fue rechazado (ya sea por un error simulado o una falla de validación).
* [`collection.paid`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_paid): El recaudo alcanzó su monto total.
* [`collection.minimum_paid`](/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_minimum_paid): El recaudo alcanzó su monto mínimo requerido.
## Simulación de errores [#simulación-de-errores]
Por defecto, los pagos simulados se completan con éxito. Para probar cómo tu
integración maneja las fallas, pasa un campo `error`:
```json
{
"creditor_key_value": "@MN1234567890",
"amount": {
"amount": 50000,
"currency": "COP"
},
"error": "tx_risk_control"
}
```
El pago igual se crea y recibirás la respuesta `202 Accepted`,
pero el evento de webhook posterior llevará un estado de rechazo.
### Códigos de error disponibles [#códigos-de-error-disponibles]
| Código de error | Descripción |
| ------------------------- | ------------------------------------------------ |
| `tx_unknown` | Ocurrió un error inesperado |
| `tx_provider_unavailable` | El sistema Bre-B no está disponible |
| `tx_breb_timeout` | Timeout del sistema Bre-B |
| `tx_risk_control` | Transacción bloqueada por reglas de risk control |
## Flujo esperado [#flujo-esperado]
1. Llamas al endpoint de sandbox para simular un pago.
2. La API responde con `202 Accepted`.
3. Después de un breve delay (2–5 segundos), se envía un webhook a tu URL configurada.
1. Si no se especificó `error`, el pago se settlea exitosamente.
2. Si se especificó un `error`, el pago se rechaza con el motivo correspondiente.
# Descubrimiento de llaves en sandbox (/es/docs/guides/breb-participant/sandbox/target-discovery)
Usa estos escenarios determinísticos para probar el [descubrimiento de
llaves](/docs/guides/breb-participant/flows/target-discovery) sin depender de
datos reales del directorio de Bre-B. Solo aplican en:
```text
https://sandbox.api.cuentamono.com
```
Usa las identidades y cuentas de ejemplo únicamente para pruebas. Las reglas de sufijos de
esta página no están disponibles en producción y no describen personas ni cuentas reales.
## Prerrequisitos y scopes [#prerrequisitos-y-scopes]
Usa una cuenta tenant del sandbox y un token de acceso con el scope requerido
en cada paso. Antes de probar, pídele a Mono que habilite el descubrimiento de
llaves para ese tenant de sandbox:
| Paso | Scope |
| ------------------------------------------------------ | ---------------------------------------------------- |
| `POST /api/v1/targets/discover` | `target_discoveries` |
| `GET /api/v1/banks` y `GET /api/v1/banks/{ach_code}` | `target_discoveries` o `target_discoveries:readonly` |
| `POST /api/v1/targets/resolve` para la alternativa 102 | `target_resolutions` |
| `POST /api/v1/outgoing_transfers` | `outgoing_transfers` |
Consulta [autenticación y
scopes](/docs/guides/breb-participant/authentication) para configurar el token.
## Selecciona el escenario con `document_number` [#selecciona-el-escenario-con-document_number]
Los últimos tres caracteres de `person.document_number` son el **único** selector.
Cambiar el correo, teléfono, banco o cuenta no selecciona otro escenario.
| Sufijo | Resultado |
| --------------------- | -------------------------------------------------------------------------------------- |
| `101` | Llave de identificación exacta después de una alternativa previa de la misma persona |
| `102` | Sin llave exacta; una alternativa de identificación que se puede resolver |
| `103` | Resultado vacío; una petición idéntica repetida puede mostrar un hit de caché negativo |
| `104` | Directorio no disponible con HTTP `503` |
| Cualquier otro sufijo | El mismo resultado vacío y cacheable de `103` |
El sufijo del documento es el único selector del escenario. El banco y la
cuenta solicitados siguen siendo entradas válidas aunque la cuenta comience por
`99`; cambiar cualquiera de los dos no cambia el escenario del sandbox. Sin
embargo, las respuestas determinísticas documentadas para `101` y `102` solo
están garantizadas cuando el `account.number` solicitado no empieza por `99`.
Ese prefijo está reservado para destinos alternativos sintéticos, que Mono
deriva de forma independiente.
Una llave en texto plano con forma de documento y terminada en `102` está reservada en sandbox
para `POST /api/v1/targets/resolve` y para transferencias salientes pagadas por llave. Así, la
alternativa devuelta por discovery se resuelve al mismo destino determinístico.
## Coincidencia exacta: sufijo 101 [#coincidencia-exacta-sufijo-101]
Envía esta petición:
```http
POST /api/v1/targets/discover
Content-Type: application/json
Authorization: Bearer
```
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"person": {
"document_type": "CC",
"document_number": "1192793101",
"first_name": "Diego",
"first_surname": "Sánchez",
"phone": "+573104838791",
"email": "diego@example.com"
},
"account": {
"bank_code": "013",
"number": "0013123456789",
"type": "savings_account"
}
}
```
La respuesta es:
```json
{
"state": "discovered",
"state_reason": null,
"attempts_count": 2,
"resolution_id": "bbtgr_5tgliBmzjZ6mpQPRbQjfKj",
"target": {
"id": "bbtgt_5tgliBmzjZ6mpQPRbQjfKj",
"key_type": "identification",
"key_value": "1192793101",
"resolution_type": "plain_key",
"spbvi": "CRB",
"participant_nit": "860003020",
"participant_name": null,
"creditor": {
"type": "natural",
"document_type": "CC",
"document_number": "1192793101",
"full_name": "Diego Sánchez"
},
"creditor_account": {
"type": "savings_account",
"number": "0013123456789",
"currency_code": "COP"
},
"transaction_amount": null,
"payment_id": null
},
"same_person_keys": [
{
"key_type": "alphanumeric",
"key_value": "@BBVA3104838791",
"bank_code": "001",
"participant_nit": "860002964",
"creditor_account": {
"type": "savings_account",
"number_last_four": "2852",
"currency_code": "COP"
},
"resolution_required": true
}
]
}
```
`participant_name` no hace parte del escenario: es el nombre de la institución que
Mono tiene registrado, así que puede ser `null` en sandbox.
Para BBVA (`bank_code=013`), discovery prueba la plantilla alfanumérica del
banco antes de la candidata de identificación. Observa la plantilla como una
alternativa de la misma persona, encuentra la coincidencia exacta en el segundo
intento y termina el recorrido. No prueba las candidatas posteriores de
teléfono y correo.
Por eso, `attempts_count` depende del orden de candidatas y las plantillas del
banco seleccionado; no es el número de campos de la petición.
`same_person_keys` no es exhaustivo y solo contiene candidatas de la misma
persona observadas antes de la primera coincidencia exacta.
Solo la llave exacta ganadora crea una resolución y emite
[`target_resolution_created`](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_created)
y
[`target_resolution_resolved`](/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_resolved).
La alternativa no emite webhooks de resolución. La entrega es asíncrona, por
lo que `target_resolution_created` puede contener desde el inicio
`state=resolved`.
## Solo alternativa: sufijo 102 [#solo-alternativa-sufijo-102]
Usa el mismo payload con el documento terminado en `102`:
```http
POST /api/v1/targets/discover
Content-Type: application/json
Authorization: Bearer
```
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"person": {
"document_type": "CC",
"document_number": "1192793102",
"first_name": "Diego",
"first_surname": "Sánchez",
"phone": "+573104838791",
"email": "diego@example.com"
},
"account": {
"bank_code": "013",
"number": "0013123456789",
"type": "savings_account"
}
}
```
El recorrido completo de candidatas devuelve:
```json
{
"state": "not_discovered",
"state_reason": "no_exact_target",
"attempts_count": 4,
"resolution_id": null,
"target": null,
"same_person_keys": [
{
"key_type": "identification",
"key_value": "1192793102",
"bank_code": "007",
"participant_nit": "890903938",
"creditor_account": {
"type": "savings_account",
"number_last_four": "6809",
"currency_code": "COP"
},
"resolution_required": true
}
]
}
```
Este resultado no crea una llave, resolución ni webhook de resolución. Entre
las alternativas sintéticas del sandbox, únicamente esta alternativa
`identification` terminada en `102` tiene garantizada una resolución posterior.
### Resuelve y verifica la alternativa 102 [#resuelve-y-verifica-la-alternativa-102]
Cuando el pagador seleccione la alternativa, resuelve su `key_value` exacto:
```http
POST /api/v1/targets/resolve
Content-Type: application/json
Authorization: Bearer
```
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"format": "plain_key",
"value": "1192793102"
}
```
La respuesta resuelta incluye este destino determinístico:
```json
{
"id": "bbtgr_6tgliBmzjZ6mpQPRbQjfKj",
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"format": "plain_key",
"value": "1192793102",
"state": "resolved",
"state_reason": null,
"target": {
"id": "bbtgt_6tgliBmzjZ6mpQPRbQjfKj",
"key_type": "identification",
"key_value": "1192793102",
"resolution_type": "plain_key",
"spbvi": "CRB",
"participant_nit": "890903938",
"participant_name": null,
"creditor": {
"type": "natural",
"document_type": "CC",
"document_number": "1192793102",
"full_name": "Persona Sandbox"
},
"creditor_account": {
"type": "savings_account",
"number": "99023886809",
"currency_code": "COP"
},
"transaction_amount": null,
"payment_id": null
}
}
```
La identidad sintética siempre es `CC` / `Persona Sandbox`, y el resultado
siempre es una llave `identification` para valores con forma de documento
terminados en `102`. Antes de pedir confirmación al pagador, verifica estas dos
condiciones:
1. El `participant_nit` de la llave resuelta es `890903938`, correspondiente al
código ACH `007` de la alternativa.
2. `creditor_account.number` termina en el `number_last_four` anunciado,
`6809`.
No pagues la alternativa si cambió el destino ACH o la terminación de cuatro
dígitos.
## Resultado vacío y caché negativo: sufijo 103 o caso por defecto [#resultado-vacío-y-caché-negativo-sufijo-103-o-caso-por-defecto]
Usa el sufijo `103` —o cualquier sufijo distinto de `101`–`104`— para obtener
un resultado vacío:
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"person": {
"document_type": "CC",
"document_number": "1192793103",
"first_name": "Diego",
"first_surname": "Sánchez",
"phone": "+573104838791",
"email": "diego@example.com"
},
"account": {
"bank_code": "013",
"number": "0013123456789",
"type": "savings_account"
}
}
```
La primera respuesta sin cachear es:
```json
{
"state": "not_discovered",
"state_reason": "no_exact_target",
"attempts_count": 4,
"resolution_id": null,
"target": null,
"same_person_keys": []
}
```
Repite la **misma petición** dentro de una hora para probar el caché negativo:
```json
{
"state": "not_discovered",
"state_reason": "no_exact_target",
"attempts_count": 0,
"resolution_id": null,
"target": null,
"same_person_keys": []
}
```
Solo se cachean los resultados `not_discovered` vacíos, por máximo una hora.
Un sandbox compartido puede contener ya la misma huella, por lo que la primera
petición que observes puede tener legítimamente `attempts_count=0`. Si el
sandbox no dispone de HMAC para discovery, los resultados negativos no se
cachean y cada petición hace el recorrido completo de candidatas.
## Directorio no disponible: sufijo 104 [#directorio-no-disponible-sufijo-104]
Envía el payload normal con el documento `1192793104`:
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"person": {
"document_type": "CC",
"document_number": "1192793104",
"first_name": "Diego",
"first_surname": "Sánchez",
"phone": "+573104838791",
"email": "diego@example.com"
},
"account": {
"bank_code": "013",
"number": "0013123456789",
"type": "savings_account"
}
}
```
Discovery aborta en el primer intento no disponible contra el directorio y
devuelve HTTP `503`:
```json
{
"code": "503 Service Unavailable",
"errors": [
{
"error_code": "service_is_unavailable",
"message": "The service is unavailable, Please try again later",
"path": null,
"url": null
}
],
"id": "log_7MkWaFqvfosB8fzHhb1Eql",
"message": "Service unavailable"
}
```
El error no se cachea ni crea llaves, resoluciones o webhooks. Reintenta con
backoff limitado, como lo harías ante una falla transitoria en producción.
## Usa la llave resultante en una transferencia [#usa-la-llave-resultante-en-una-transferencia]
La llave exacta de `101` y la llave recién resuelta de `102` son de un solo uso.
Envía el `target.id` seleccionado en una transferencia saliente:
```json
{
"tenant_account_id": "bbtacc_5tgliBmzjZ6mpQPRbQjfKj",
"transfers": [
{
"external_id": "sandbox-discovery-001",
"target_id": "bbtgt_6tgliBmzjZ6mpQPRbQjfKj",
"amount": {
"amount": 10000,
"currency": "COP"
}
}
]
}
```
También puedes crear una transferencia saliente por `plain_key` con un valor
en forma de documento terminado en `102`; se resolverá al mismo destino
determinístico. Consulta [transferencias salientes en
sandbox](/docs/guides/breb-participant/sandbox/outgoing-transfers) y [crear
transferencias
salientes](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers).
## Catálogo bancario [#catálogo-bancario]
`GET /api/v1/banks` y `GET /api/v1/banks/{ach_code}` usan el catálogo normal
del sandbox. No necesitan sufijos ni otros valores de simulación. Usa sus
valores `ach_code` canónicos en las peticiones de discovery.
## Referencia relacionada [#referencia-relacionada]
* [Descubrir llave](/docs/api-reference/breb-participant/target-discovery/discover_target)
* [Listar bancos](/docs/api-reference/breb-participant/target-discovery/list_banks)
* [Consultar banco](/docs/api-reference/breb-participant/target-discovery/get_bank)
* [Resolver llave](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target)
* [Flujo de descubrimiento de llaves](/docs/guides/breb-participant/flows/target-discovery)
* [Flujo de transferencia saliente](/docs/guides/breb-participant/flows/outgoing-transfer)
# Transferencias salientes (/es/docs/guides/breb-participant/sandbox/outgoing-transfers)
Los escenarios determinísticos `101` y `102` de discovery producen llaves de un solo uso que
puedes enviar en una transferencia saliente. Una `plain_key` con forma de documento y
terminada en `102` también está reservada para pagos por llave. Consulta [descubrimiento de
llaves en sandbox](/docs/guides/breb-participant/sandbox/target-discovery).
## Simulación exitosa [#simulación-exitosa]
Para simular una [transferencia saliente](/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers) exitosa en el entorno sandbox, simplemente envía un request con una estructura de payload válida que pase todas las validaciones de negocio. No se requieren valores especiales de campo.
```json
{
"tenant_account_id": "your_tenant_account_id",
"transfers": [
{
"external_id": "transfer_001",
"payment_id": "123856456",
"amount": {
"amount": 50000,
"currency": "COP"
},
"description": "Payment for services",
"query": {
"format": "plain_key",
"value": "user@email.com"
}
}
]
}
```
Si recuperas la transferencia saliente usando este [endpoint](/docs/api-reference/breb-participant/outgoing-transfers/get_an_outgoing_transfer_by_id), verás la transferencia completada exitosamente:
```json
{
"id": "bbot_5tgliBmzjZ6mpQPRbQjfKj",
"resolution_request_id": "bbtgr_5tgliBmzjZ6mpQPRbQjfKj",
"state": "successful",
"state_reason": null,
...
}
```
## Simulación de errores [#simulación-de-errores]
Puedes simular errores en el entorno sandbox a través de reglas especiales que aplican a ciertos valores de campo del payload. Las siguientes son las reglas para simular errores en la creación de transferencias salientes:
| Condición uno | Condición dos | Error esperado en state reason |
| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| transfers\[x].description contiene "tx\_unknown" | | unknown |
| transfers\[x].description contiene "tx\_amount\_exceeds\_balance\_limit" | | amount\_exceeds\_balance\_limit |
| transfers\[x].description contiene "tx\_creditor\_account\_not\_found" | | creditor\_account\_not\_found |
| transfers\[x].description contiene "tx\_invalid\_creditor\_account" | | invalid\_creditor\_account |
| transfers\[x].description contiene "tx\_risk\_control" | | risk\_control |
| transfers\[x].description contiene "tx\_breb\_timeout" | | breb\_timeout |
| transfers\[x].query.format = "plain\_key" | transfers\[x].query.value = "[sandbox@tx\_provider\_unavailable.err](mailto:sandbox@tx_provider_unavailable.err)" | provider\_unavailable |
| transfers\[x].query.format = "plain\_key" | transfers\[x].query.value = "[sandbox@key\_unknown.err](mailto:sandbox@key_unknown.err)" | unknown |
| transfers\[x].query.format = "plain\_key" | transfers\[x].query.value = "[sandbox@key\_not\_found.err](mailto:sandbox@key_not_found.err)" | key\_not\_found |
| transfers\[x].query.format = "plain\_key" | transfers\[x].query.value = "[sandbox@key\_suspended.err](mailto:sandbox@key_suspended.err)" | key\_suspended |
| transfers\[x].query.format = "plain\_key" | transfers\[x].query.value = "[sandbox@key\_provider\_unavailable.err](mailto:sandbox@key_provider_unavailable.err)" | provider\_unavailable |
| transfers\[x].query.format = "plain\_key" y transfers\[x].expected\_creditor está definido | transfers\[x].query.value = "[sandbox@key\_target\_creditor\_mismatch.err](mailto:sandbox@key_target_creditor_mismatch.err)" | target\_creditor\_mismatch |
> **Nota:** Estos errores están relacionados con la comunicación interna con el sistema Bre-B y son distintos de los errores relacionados con validación de datos. Estos errores se reflejan en el `state_reason` de la transferencia saliente.
## Ejemplos [#ejemplos]
### Simulando un error de Key Not Found [#simulando-un-error-de-key-not-found]
Si quieres simular un error cuando la llave Bre-B no existe para la creación de transferencias salientes, puedes enviar algo como:
```json
{
"tenant_account_id": "your_tenant_account_id",
"transfers": [
{
"external_id": "transfer_001",
"payment_id": "123856456",
"query": {
"format": "plain_key",
"value": "sandbox@key_not_found.err"
}
}
]
}
```
La respuesta será exitosa. Después, si recuperas la transacción saliente usando este [endpoint](/docs/api-reference/breb-participant/outgoing-transfers/get_an_outgoing_transfer_by_id), verás el error reflejado en el estado de la transferencia saliente:
```json
{
"id": "bbot_5tgliBmzjZ6mpQPRbQjfKj",
"resolution_request_id": "bbtgr_5tgliBmzjZ6mpQPRbQjfKj",
"state": "failed",
"state_reason": "key_not_found",
...
}
```
### Simular un mismatch de creditor al resolver un target [#simular-un-mismatch-de-creditor-al-resolver-un-target]
[Resolver target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target) acepta el mismo `expected_creditor` que una transferencia por llave. En sandbox, una llave plana que no sea disparador de sandbox se resuelve al creditor que esperas, y `sandbox@key_target_creditor_mismatch.err` se resuelve a otra persona:
```json
{
"tenant_account_id": "your_tenant_account_id",
"format": "plain_key",
"value": "sandbox@key_target_creditor_mismatch.err",
"expected_creditor": {
"document_type": "CC",
"document_number": "1234567"
}
}
```
La llamada responde `400` y no persiste ningún target:
```json
{
"code": "400 Bad Request",
"errors": [
{
"error_code": "target_creditor_mismatch",
"message": "The resolved creditor doesn't match the expected creditor",
"path": "",
"url": ""
}
],
"id": "log_7MkWaFqvfosB8fzHhb1Eql",
"message": "Malformed request"
}
```
El webhook `target_resolution.failed` del request lleva `"state_reason": "target_creditor_mismatch"` y tu `expected_creditor`. Envía una llave normal con el mismo `expected_creditor` para obtener una respuesta `resolved` cuyo `target.creditor` coincida.
# Reversos y reaplicaciones (/es/docs/guides/breb-participant/sandbox/reverse-and-reapply)
Después de que una transferencia se settlea, la conciliación de cierre del Banco
de la República (BanRep) todavía puede ajustarla. Hay dos resultados:
* **Reverso** — una operación que parecía exitosa en realidad no se completó en
el destino (o fue devuelta), por lo que se reversa. Un pago **entrante**
reversado que pertenece a un recaudo se revierte: su intento pasa de
`successful` a `reversed`, se recalculan el `paid_amount` y los contadores del
recaudo, y el recaudo puede reabrirse.
* **Reaplicación** — una operación marcada como `failed` en realidad se settleó
tarde, por lo que se reaplica. Un pago **entrante** reaplicado settlea el
intento que se había finalizado como fallido: pasa de `failed` a `successful`
y se acredita al recaudo.
En producción estos resultados vienen del archivo de conciliación diario. El
sandbox te permite **replicar cualquiera de los dos resultados sobre una de tus
propias transferencias** para verificar que tu integración maneja los webhooks
resultantes. Estos endpoints solo están disponibles en el entorno sandbox.
## Prerrequisitos [#prerrequisitos]
Necesitas una transferencia existente y su `end_to_end_id`:
* Para **reversar**, la transferencia debe estar en un estado reversable
(`successful` o `reapplied`). Para un recaudo, primero
[simula un pago de recaudo exitoso](/docs/guides/breb-participant/sandbox/collections).
* Para **reaplicar**, la transferencia debe estar en estado `failed`. Para un
recaudo, primero simula un pago con un
[`error`](/docs/guides/breb-participant/sandbox/collections#simulación-de-errores)
para que el intento quede finalizado como fallido.
El `end_to_end_id` se devuelve en la respuesta de la
[simulación de intento de recaudo](/docs/guides/breb-participant/sandbox/collections#simulando-un-intento-de-recaudo)
y está presente en todos los payloads de webhook `collection.attempt_*` e
`incoming_transfer.*`.
## Simulando un reverso [#simulando-un-reverso]
Envía un `POST` para reversar la transferencia que coincide con el
`end_to_end_id`:
```bash
curl -X POST \
https://breb-participant.sandbox.mono.la/api/v1/sandbox/transfers/{end_to_end_id}/reverse \
-H "Authorization: Bearer "
```
Recibes una respuesta `202 Accepted` describiendo el resultado aplicado:
```json
{
"end_to_end_id": "20260527345544001ENT177994036665555",
"direction": "incoming",
"state": "reversed",
"state_reason": "reversed_by_banrep",
"result": "applied"
}
```
Unos segundos después, los webhooks se entregan a tu URL configurada. Para un
pago entrante reversado que pertenece a un recaudo recibes:
* `collection.attempt_reversed` — el intento que estaba `successful` ahora está
`reversed`.
* `collection.updated` — se recalcularon el `paid_amount`, los contadores y el
estado del recaudo.
## Simulando una reaplicación [#simulando-una-reaplicación]
Envía un `POST` para reaplicar la transferencia fallida que coincide con el
`end_to_end_id`:
```bash
curl -X POST \
https://breb-participant.sandbox.mono.la/api/v1/sandbox/transfers/{end_to_end_id}/reapply \
-H "Authorization: Bearer "
```
Recibes una respuesta `202 Accepted`:
```json
{
"end_to_end_id": "20260527345544001ENT177994036665555",
"direction": "incoming",
"state": "reapplied",
"state_reason": "reapplied_by_banrep",
"result": "applied"
}
```
Para un pago entrante reaplicado que pertenece a un recaudo recibes:
* `collection.attempt_successful` — el intento que estaba `failed` ahora está
`successful`.
* `collection.updated` — se recalcularon el `paid_amount`, los contadores y el
estado del recaudo.
Ver la [referencia de webhooks de recaudo](/docs/api-reference/breb-participant/collection-webhooks)
para los payloads completos.
## Campos de la respuesta [#campos-de-la-respuesta]
| Campo | Descripción |
| --------------- | ------------------------------------------------------------------------------------------------------------ |
| `end_to_end_id` | La transferencia a la que se aplicó el resultado. |
| `direction` | `incoming` (settlea un recaudo) o `outgoing`. |
| `state` | El estado resultante de la transferencia: `reversed` o `reapplied`. |
| `state_reason` | El motivo registrado para el cambio. |
| `result` | `applied` cuando la transferencia transicionó ahora, o `already_applied` si ya estaba en el estado objetivo. |
Llamar reverse sobre una transferencia ya reversada (o reapply sobre una ya reaplicada)
devuelve éxito con `"result": "already_applied"` y **no** emite un nuevo webhook.
## Errores [#errores]
| Estado | Código | Cuándo |
| ------ | ------------------------ | ------------------------------------------------------------------------ |
| `404` | `transfer_not_found` | Ninguna transferencia coincide con el `end_to_end_id`. |
| `409` | `transfer_invalid_state` | La transferencia no puede reversarse/reaplicarse desde su estado actual. |
## Flujo esperado [#flujo-esperado]
1. Crea una transferencia sobre la cual actuar (p. ej. simula un pago de recaudo).
2. Toma su `end_to_end_id` de la respuesta de simulación o de un webhook.
3. Llama al endpoint sandbox de reverse o reapply con ese `end_to_end_id`.
4. La API responde con `202 Accepted` y el resultado aplicado.
5. Tras un breve delay, los webhooks `collection.*` se entregan a tu URL
configurada.
# Sandbox (/es/docs/guides/breb-participant/sandbox)
Usa el entorno sandbox para probar tu integración Bre-B de extremo a extremo sin procesar pagos ni transferencias reales.
## URL base [#url-base]
```
https://sandbox.api.cuentamono.com
```
Prueba coincidencias exactas, alternativas, resultados vacíos, caché y fallas
Simula transferencias salientes en el entorno sandbox
Simula pagos de recaudo en el entorno sandbox
Simula los resultados de conciliación reverse y reapply sobre tus transferencias
¿Quieres simular un pago de recaudo sin escribir código? Abre el [simulador sandbox de
Bre-B](https://breb.app/uc/sandbox-simulator) — una herramienta web hospedada por Mono que
paga cualquiera de tus llaves de recaudo del sandbox con un solo clic. Ver [Sandbox →
Recaudos](/docs/guides/breb-participant/sandbox/collections#simulador-de-sandbox-collections)
para más detalles.
# Recaudos (/es/docs/guides/breb-participant/concepts/collections)
Un **recaudo** es un pago Bre-B entrante a una de tus cuentas tenant. Recibes recaudos mostrando un código QR de Bre-B que codifica una instrucción de pago, o compartiendo una llave de pago registrada contra la cuenta tenant. Una vez que el pagador autoriza el pago en su app bancaria, Mono lo recibe de la red, acredita tu cuenta tenant y te notifica con un webhook.
Los recaudos son la mitad de payin de Bre-B Participant. Úsalos cuando tu negocio necesita recibir dinero por Bre-B — checkout, recargas en kiosco, páginas de donación o cualquier momento en que el pagador inicia un pago banco a banco.
## Dos formas de recibir [#dos-formas-de-recibir]
| Método | Caso de uso |
| :--------------------- | :--------------------------------------------------------------------------------------------------------------------- |
| QR generado | Cada recaudo tiene su propio QR con monto y referencia fijos. Ideal para checkout y pagos únicos. |
| Llave de pago estática | La misma llave recibe cualquier cantidad de pagos. Ideal para relaciones recurrentes (cobro de servicios, donaciones). |
## Ciclo de vida [#ciclo-de-vida]
Cada recaudo se mueve por una máquina de estados determinística — `created` → `ready` → opcionalmente `minimum_paid` → `paid`, con `discarded` y `failed` como resultados terminales alternos. Ver la página de [Ciclo de vida del recaudo](/docs/guides/breb-participant/architecture/collection-states) para el diagrama de estados completo, el recorrido por estado y el evento de webhook que mapea a cada transición.
## Modelo de datos [#modelo-de-datos]
El schema completo vive en la [referencia de API de recaudos de Bre-B Participant](/docs/api-reference/breb-participant/collections).
## Eventos de webhook [#eventos-de-webhook]
Los recaudos emiten eventos en cada transición de estado. Ver [Payloads de webhook](/docs/guides/breb-participant/webhook-payloads).
## Siguientes pasos [#siguientes-pasos]
* [Flujo de recaudo por QR](/docs/guides/breb-participant/flows/qr-collection)
* [Cuentas tenant](/docs/guides/breb-participant/concepts/tenant-accounts)
* [Firmas de webhook](/docs/guides/breb-participant/webhook-signatures)
# Cuentas tenant (/es/docs/guides/breb-participant/concepts/tenant-accounts)
Una **cuenta tenant** es una cuenta lógica en Mono que pertenece a tu tenant participante. Mantiene un saldo y actúa como origen de las transferencias salientes de Bre-B y destino de los recaudos Bre-B entrantes. Si tu producto maneja saldos para muchos usuarios finales, típicamente tienes una cuenta tenant por usuario; si operas con un único saldo agrupado, tienes una sola cuenta tenant en total.
Cada operación Bre-B referencia una cuenta tenant. Las transferencias salientes la debitan; los recaudos la acreditan. Las llaves de pago se registran contra cuentas tenant.
## Siguientes pasos [#siguientes-pasos]
* [Llaves de pago](/docs/guides/breb-participant/concepts/payment-keys)
* [Recaudos](/docs/guides/breb-participant/concepts/collections)
* [Arquitectura](/docs/guides/breb-participant/architecture)
# Conceptos (/es/docs/guides/breb-participant/concepts)
La API de Bre-B Participant está construida alrededor de cuatro entidades. Cada una tiene su propio modelo de datos, ciclo de vida y webhooks. Entenderlas por separado es el prerrequisito para combinarlas en un [flujo](/docs/guides/breb-participant/flows).
Las cuentas lógicas que mantienen saldos y son origen/destino de operaciones Bre-B.
Los alias que identifican destinos en Bre-B — teléfono, documento, email o alfanumérico.
Pagos Bre-B entrantes — códigos QR, eventos de settlement.
Los destinos resueltos devueltos por el directorio central.
# Targets (/es/docs/guides/breb-participant/concepts/targets)
Un **target** es lo que obtienes cuando resuelves una llave de pago contra el directorio central de Bre-B: un objeto estructurado que describe quién posee la llave, qué banco tiene la cuenta destino y qué nombre se mostrará al pagador para confirmación. Los targets son el artefacto que pasas al endpoint de transferencia saliente cuando quieres despachar un pago.
Vas a encontrar targets cada vez que tu tenant envíe un pago Bre-B. El flujo siempre es: tomas una llave de pago, la resuelves a un target, le muestras el nombre del titular al pagador para confirmación, y después despachas la transferencia.
## Qué contiene un target [#qué-contiene-un-target]
| Campo | Significado |
| :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Nombre del titular | El nombre registrado en el banco destino. Muéstrale esto al pagador. |
| Documento del titular | Documento de identificación del titular (cuando está autorizado). |
| Participante destino | La institución que tiene la cuenta destino, devuelta como `participant_nit` y `participant_name` (`null` cuando Mono aún no conoce el nombre). Un participante Bre-B no es necesariamente un banco. |
| Timestamp de resolución | Cuándo ocurrió la resolución. |
## Fijar quién debe ser el dueño de la llave [#fijar-quién-debe-ser-el-dueño-de-la-llave]
Cuando ya sabes quién debe recibir el dinero — un reembolso, por ejemplo — dile a Mono a quién esperas detrás de la llave al resolverla. Envía `expected_creditor` (`document_type` + `document_number`) a [Resolver target](/docs/api-reference/breb-participant/outgoing-transfers/resolve_target), con cualquier formato de llave. Mono resuelve la llave solo cuando el titular registrado tiene ese documento. Si la llave pertenece a otra persona, el request falla con `target_creditor_mismatch` y no se crea ningún target, así que un `target_id` obtenido de esta forma siempre apunta a la persona que esperabas.
Una transferencia creada con ese `target_id` lleva el mismo `expected_creditor` en su respuesta de API y en sus webhooks, igual que una transferencia que envía `expected_creditor` junto con la llave. Ve [Casos de uso de Dispersiones](/docs/guides/breb-participant/use-cases/outgoing-transfers) para ambos escenarios.
## Siguientes pasos [#siguientes-pasos]
* [Llaves de pago](/docs/guides/breb-participant/concepts/payment-keys)
* [Flujo de transferencia saliente](/docs/guides/breb-participant/flows/outgoing-transfer)
* [Buenas prácticas](/docs/guides/breb-participant/best-practices)
# Llaves de pago (/es/docs/guides/breb-participant/concepts/payment-keys)
Una **llave de pago** es un alias que identifica un destino en la red Bre-B. En lugar de usar números de cuenta (que son largos, propensos a error y específicos por banco), los participantes de Bre-B registran llaves contra sus cuentas en el directorio central: un número de celular (solo los 10 dígitos del número nacional, sin código de país), un número de documento, una dirección de email, un identificador alfanumérico emitido por Bre-B o un código de comercio. Cuando alguien quiere enviarte dinero, ingresa tu llave en su app bancaria, y Bre-B la busca y enruta el pago a tu cuenta.
Vas a usar llaves de pago de dos formas: registrando llaves para tus cuentas tenant para que puedan recibir pagos, y enviando llaves para resolución cuando tu tenant quiera enviar un pago.
## Tipos de llave [#tipos-de-llave]
| Tipo | Ejemplo | Notas |
| :------------ | :------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Cellphone | 3001234567 | Solo número celular colombiano. Exactamente 10 dígitos, empieza con `3`. Sin código de país, sin `+`, sin espacios. Regex `^3\d{9}$`. |
| Document | 21482961 | Solo el número de identificación — sin prefijo `CC`/`CE`/`NIT`, sin separadores. 1–18 caracteres alfanuméricos (pasaportes aceptados). Regex `^[A-Za-z0-9]{1,18}$`. |
| Email | [user@example.com](mailto:user@example.com) | Parte local 1–30 caracteres (sin espacios, sin `@`); dominio 1–61 caracteres de `[A-Za-z0-9.-]`. Regex `^[^\s@]{1,30}@[A-Za-z0-9.-]{1,61}$`. |
| Alphanumeric | @user123 | Identificador emitido por Bre-B. Empieza con `@`, seguido de 5–20 caracteres alfanuméricos. Útil cuando el usuario no quiere revelar datos personales. Regex `^@[A-Za-z0-9]{5,20}$`. |
| Merchant code | 0012345678 | 10 dígitos, empieza con `00`. Identifica comercios registrados en Bre-B. Regex `^00\d{8}$`. |
## Siguientes pasos [#siguientes-pasos]
* [Targets](/docs/guides/breb-participant/concepts/targets)
* [Cuentas tenant](/docs/guides/breb-participant/concepts/tenant-accounts)
* [Flujo de transferencia saliente](/docs/guides/breb-participant/flows/outgoing-transfer)
# Card Webhooks (/es/docs/api-reference/banking/card-webhooks)
Receive notifications when card lifecycle events occur, including plastic card activation, card tokenization OTP requests, and 3DS transaction authentication.
## Events [#events]
Plastic card activation
Card tokenization OTP
3DS transaction OTP
# Card tokenization OTP (/es/docs/api-reference/banking/card-webhooks/webhook_card_token_otp)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a tokenization OTP is requested for a card.
This occurs when a card is being added to a digital wallet (e.g., Apple Pay, Google Pay)
and an OTP verification is required to complete the tokenization process.
# 3DS transaction OTP (/es/docs/api-reference/banking/card-webhooks/webhook_threeds_otp)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a 3-D Secure (3DS) OTP is requested for an online card transaction.
This occurs when a cardholder makes an online purchase that requires 3DS authentication
and a one-time password must be delivered to the cardholder to confirm the transaction.
# Plastic card activation (/es/docs/api-reference/banking/card-webhooks/webhook_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a plastic card activation changes state.
This occurs during the card activation process when the physical card is linked to the system.
The `state` field indicates the result of the activation:
* `activated`: Card was successfully activated
* `failed`: Card activation failed (e.g., error retrieving card details from provider)
* `group_mismatch`: The card's affinity group does not match the expected configuration group
# Cards (/es/docs/api-reference/banking/cards)
Endpoints for managing cards, cardholders, and digital wallet provisioning. To issue a new card, see [Card Issuing and Prepaid Balance](/docs/api-reference/banking/card-issuing-and-prepaid-balance).
## Cards [#cards]
List cards
Gets card
Update a card
Gets card transactions
Gets information about the card look
## Cardholders [#cardholders]
Create a cardholder
Show a cardholder
Update a cardholder
## PIN Management [#pin-management]
Change plastic card pin
## Plastic Card Activation [#plastic-card-activation]
Generate a URL to activate a plastic card
List plastic card activations
Get plastic card activation
## Digital Wallet Provisioning [#digital-wallet-provisioning]
Apple Pay Tokenization
Google Pay Tokenization
# Gets card (/es/docs/api-reference/banking/cards/get_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card with sensitive information
# Update a cardholder (/es/docs/api-reference/banking/cards/update_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update a cardholder
# List plastic card activations (/es/docs/api-reference/banking/cards/list_plastic_card_activations)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List plastic card activations
# Gets card transactions (/es/docs/api-reference/banking/cards/get_card_transactions)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card transactions
# Get plastic card activation (/es/docs/api-reference/banking/cards/get_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get plastic card activation
# Update a card (/es/docs/api-reference/banking/cards/update_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to update cards
# Get plastic card activation (/es/docs/api-reference/banking/cards/fetch_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get plastic card activation
# Gets the sensitive details of a card (/es/docs/api-reference/banking/cards/get_card_look)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the URL of an iframe that displays the card's sensitive details — number,
security code and expiration — so an app or site can show them without ever
handling them. The URL carries a short-lived credential of the cardholder, so
treat it as sensitive: anyone holding it sees the card.
Which fields the iframe renders comes from the card's configuration group; by
default it renders all three.
# Generate a URL to activate a plastic card (/es/docs/api-reference/banking/cards/build_plastic_card_activation_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a URL to activate a plastic card
# List cards (/es/docs/api-reference/banking/cards/list_cards)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List cards. the result will include the cards with the paging information
# Change plastic card pin (/es/docs/api-reference/banking/cards/change_card_pin)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to change plastic card PIN
# Apple Pay Tokenization (/es/docs/api-reference/banking/cards/provision_apple_pay_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to handle Apple Pay token provisioning. If you want to use this endpoint, communicate with us.
# Show a cardholder (/es/docs/api-reference/banking/cards/get_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Show a cardholder
# Create a cardholder (/es/docs/api-reference/banking/cards/create_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a cardholder
# List plastic card activations (/es/docs/api-reference/banking/cards/get_plastic_card_activations)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List plastic card activations
# Generate an URL to activate a plastic card (/es/docs/api-reference/banking/cards/activate_plastic_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create an URL to activate a plastic card
# Google Pay Tokenization (/es/docs/api-reference/banking/cards/provision_google_pay_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to handle Google Pay token provisioning. If you want to use this endpoint, communicate with us.
# Authentication (/es/docs/api-reference/banking/authentication)
Endpoints for the OAuth 2.0 token lifecycle of Banking partners: exchange your client credentials for an access token, check whether a token is still active, and revoke it.
The scopes a Banking token can carry, and what each one grants, are listed in the [Banking authentication guide](/docs/guides/banking/api/authentication#scopes).
## Endpoints [#endpoints]
Obtain access token
Introspect token
Revoke token
# Obtain access token (/es/docs/api-reference/banking/authentication/get_auth_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token endpoint supporting client credentials and refresh token grant types.
Use this endpoint to obtain an access token that can be used to authenticate
requests to other API endpoints. The token should be included in the
Authorization header as a Bearer token.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Grant Types:**
* **client\_credentials**: Use client ID and secret to obtain an access token
* **refresh\_token**: Use a refresh token to obtain a new access token. A refresh token works
once: the response carries the one to use next, and the one sent stops working.
# Introspect token (/es/docs/api-reference/banking/authentication/introspect_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token introspection endpoint (RFC 7662).
Use this endpoint to determine the active state and metadata of an access token or refresh token.
This is useful for resource servers to validate tokens and retrieve their associated information.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* If the token is active, returns token metadata including client\_id, scope, expiration, etc.
* If the token is inactive, expired, or invalid, returns `{"active": false}`
# Revoke token (/es/docs/api-reference/banking/authentication/revoke_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token revocation endpoint (RFC 7009).
Use this endpoint to revoke an access token or refresh token. After revocation,
the token will no longer be valid for authentication or authorization.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* Returns 200 OK with empty body on successful revocation
* The server responds with 200 OK even if the token was already invalid or expired
# Bank Transfers (/es/docs/api-reference/banking/bank-transfers)
Endpoints for creating and managing bank transfers.
## Endpoints [#endpoints]
List bank transfers
Create bank transfers
Prepare bank transfers
# Create bank transfers (/es/docs/api-reference/banking/bank-transfers/create_bank_transfers)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates and sends a batch of bank transfers without the requiring of an additional authorization,
this can be between accounts of the same or different banks,
which allows multiple transfers to be processed together in a single request.
When the server receives the request, it will process it and initiate the transfer of funds
between the source and destination bank accounts. This involves debiting the appropriate amount from
the source account, and crediting the destination account with the transferred funds.
This endpoint is used in cases where the client wants to initiate bank transfers from
their own system or application, without the need to log in to their bank's website or
use a separate banking app. This can be useful for a variety of purposes,
such as to automate financial operations, to streamline the payment process,
or to provide a more convenient user experience.
Considerations:
1. API key role allowed to consume this endpoint is "Administrator".
2. The minimum number of transfers allowed per batch is 1.
3. The maximum number of transfers allowed per batch are 1.000.
# List bank transfers (/es/docs/api-reference/banking/bank-transfers/list_bank_transfers)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List bank transfers of your organization.
This endpoint allows to filters, sort, and control pagination of the transfers you want to fetch.
Considerations:
* API key roles allowed to consume this endpoint are "Administrator", "Preparer", and "Viewer".
# Prepare bank transfers (/es/docs/api-reference/banking/bank-transfers/prepare_bank_transfers)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates and leaves a batch of transfers pending for approval,
these bank transfers can be between accounts of the same or different banks,
which allows multiple transfers to be processed together in a single request.
This endpoint is designed to allow transfers to be reviewed and approved by an administrator
user from the Mono app before being sent to the banks.
When the server receives the request, it will process the batch of transfers and create transfer records in our database
for each of them, the transfers will not be executed immediately, instead, the batch will be queued for
review and approval by an administrator user from Mono app.
Considerations:
1. API key roles allowed to consume this endpoint are "Administrator" and "Preparer".
2. The minimum number of transfers allowed per batch is 1.
3. The maximum number of transfers allowed per batch are 1.000.
# Accounts (/es/docs/api-reference/banking/accounts)
Endpoints for managing bank accounts and retrieving balances.
## Endpoints [#endpoints]
Get accounts
Get account balance
# Get balance (/es/docs/api-reference/banking/accounts/get_bank_account_balance)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get account balance
# Get accounts (/es/docs/api-reference/banking/accounts/list_bank_accounts)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get accounts
# Bank Transfer Webhooks (/es/docs/api-reference/banking/bank-transfer-webhooks)
Receive notifications when bank transfer events occur, including approvals, rejections, fallback routing, and account credits.
## Events [#events]
Bank transfer approved
Bank transfer rejected
Bank transfer fallback routing
Bank transfer changed final state
Account credited
# Bank transfer changed final state (/es/docs/api-reference/banking/bank-transfer-webhooks/webhook_bank_transfer_change_final_state)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a bank transfer changes its final state
(e.g., from approved to declined or from declined to approved).
The `previous_state` field indicates the state the transfer was in before the change.
# Bank transfer approved (/es/docs/api-reference/banking/bank-transfer-webhooks/webhook_bank_transfer_approved)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a bank transfer is approved.
The transfer has been processed successfully and the funds have been sent.
# Account credited (/es/docs/api-reference/banking/bank-transfer-webhooks/webhook_account_credited)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a transfer is successful and the money is in your account.
There are two types of transfers identified by the `type` field:
* `bank_transfer_mono`: A real-time transfer made via Mono's internal system.
* `incoming_wire`: A wire transfer made via ACH. The `extra` field will be empty for this type.
# Bank transfer rejected (/es/docs/api-reference/banking/bank-transfer-webhooks/webhook_bank_transfer_rejected)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a bank transfer is declined.
The `declination_reason` field will contain a string indicating the reason for the rejection.
# Bank transfer fallback routing (/es/docs/api-reference/banking/bank-transfer-webhooks/webhook_bank_transfer_fallback_routing)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a bank transfer is attempted with another routing.
This occurs when the original routing fails and the transfer is retried with a fallback routing option.
The `fallback_reason` field indicates the reason the original routing failed.
# Banks (/es/docs/api-reference/banking/banks)
The list of banks you can send money to, with the code that identifies each one. Use the code to set the destination bank of a bank transfer.
## Endpoints [#endpoints]
Get banks
# Get banks (/es/docs/api-reference/banking/banks/list_banks)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Every operation that has to name a bank takes its `code`, and this endpoint is where those codes come from: preparing or applying a bank transfer and creating a collection intent, for a banking credential, and sending a payout, for a core one.
It retrieves all the banks we support with the `code` each of them is known by here, and the account types supported by each bank you can transfer to (only when the `kind` parameter is `transfers`).
# Card Issuing and Prepaid Balance (/es/docs/api-reference/banking/card-issuing-and-prepaid-balance)
Endpoints for issuing banking cards and operating their prepaid balance. Once issued, manage a card with the [Cards](/docs/api-reference/banking/cards) endpoints.
## Endpoints [#endpoints]
Creates a card
Gets card balance operations
# Topping up and withdrawing the card balance (/es/docs/api-reference/banking/card-issuing-and-prepaid-balance/apply_banking_card_balance_operation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Topping up and withdrawing the card balance
# Creates a card (/es/docs/api-reference/banking/card-issuing-and-prepaid-balance/create_banking_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to create cards
# Gets card balance (/es/docs/api-reference/banking/card-issuing-and-prepaid-balance/get_card_balance)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card balance information
# Gets card balance operations (/es/docs/api-reference/banking/card-issuing-and-prepaid-balance/get_card_balance_operations)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card balance operations
# Card Transaction Webhooks (/es/docs/api-reference/banking/card-transaction-webhooks)
Receive notifications when card transaction events occur, including authorizations, adjustments, reversals, and refunds.
## Events [#events]
Card authorization approved
Card authorization declined
Card withdrawal
Card credit adjustment
Card debit adjustment
Card force debit
Card authorization reversed
Card refund
Card reconciliation
# Card force debit (/es/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_force_debit)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a transaction is made without a previous authorization.
This means that a charge will be done without taking into account the account's balance.
There is no `original_transaction_id` nor `previous_values`.
# Card credit adjustment (/es/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_credit_adjustment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an authorization is updated by a partial refund.
This means a card transaction has been approved and there will be a refund reflected in the
account balance. A partial refund is when part of the purchase value is returned to the account.
# Card authorization reversed (/es/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_authorization_reversed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a transaction is reversed and the value of the initial charge
is returned fully to the account.
# Card debit adjustment (/es/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_debit_adjustment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an authorization is updated with a new amount.
This means a card transaction has been approved and charged, and then a new charge is done
to update the previous authorization.
# Card authorization approved (/es/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_authorization_approved)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction has been authorized.
For example, after making a purchase or a payment with a card. It means a card transaction
has been approved and therefore a charge will be reflected on the account's balance.
# Card withdrawal (/es/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_withdrawal)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a withdrawal has been authorized.
For example, when the card owner withdraws money from an ATM. Includes a `withdrawal_fee` detail.
# Card refund (/es/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_refund)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a refund is made to the account.
The value of the refund can be related or not to a previous authorization.
# Card authorization declined (/es/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_authorization_declined)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction has not been authorized.
Includes a `declination_reason` to understand the reason behind the denied card transaction.
It doesn't reflect any change on the account's balance.
# Card reconciliation (/es/docs/api-reference/banking/card-transaction-webhooks/webhook_card_transaction_reconciliation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction is reconciled.
Reconciliation occurs when the final settlement amount differs from the original authorization.
The `previous_values` field contains the original amounts before reconciliation.
# Collection Links (/es/docs/api-reference/banking/collection-links)
Endpoints for creating and managing collection links and their associated intents.
## Endpoints [#endpoints]
List of collection links
Create a collection link
Get a collection link by ID
# Get a collection link by ID (/es/docs/api-reference/banking/collection-links/get_collection_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
This endpoint allows you to get a collection link by ID
# Create a collection link (/es/docs/api-reference/banking/collection-links/create_collection_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
This endpoint allows you to create a collection link to share it with anybody.
# List of collection links (/es/docs/api-reference/banking/collection-links/list_collection_links)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
This endpoint allows you to list the created collection links
# Get collection intent (/es/docs/api-reference/banking/collection-links/get_collection_intent_by_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get collection intent detail
# Get list of collection intents (/es/docs/api-reference/banking/collection-links/list_collection_intents_by_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
This endpoint allows you to list the successful collection intents
# Collection Link Webhooks (/es/docs/api-reference/banking/collection-link-webhooks)
Receive notifications when collection link payment intents are credited or fail.
## Events [#events]
Collection intent credited
Collection intent confirmed
Collection intent failed
# Collection intent credited (/es/docs/api-reference/banking/collection-link-webhooks/webhook_collection_intent_credited)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection link payment intent has been successfully credited
to the recipient's bank account.
This signifies a successful transaction where the funds have been successfully transferred.
# Collection intent confirmed (/es/docs/api-reference/banking/collection-link-webhooks/webhook_collection_intent_confirmed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection link payment intent has been confirmed
by the payment provider.
This indicates the payment was approved by the provider but the funds have not yet
been credited to the recipient's bank account.
# Collection intent failed (/es/docs/api-reference/banking/collection-link-webhooks/webhook_collection_intent_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection link payment intent could not be processed
by the payment provider and the payment was declined.
The `provider_message` field in the `payment` object will show the reason of failure
given by the provider.
# Batch Webhooks (/es/docs/api-reference/banking/batch-webhooks)
Receive notifications when batch events occur, including duplication, authorization requests, processing, and cancellations.
## Events [#events]
Batch duplicated
Batch authorization requested
Batch sent to process
Batch canceled
# Batch duplicated (/es/docs/api-reference/banking/batch-webhooks/webhook_batch_duplicated)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a batch was created with all its transfers as duplicated
(with previously registered `entity_id`).
# Batch authorization requested (/es/docs/api-reference/banking/batch-webhooks/webhook_batch_authorization_requested)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a batch is requested for authorization.
The batch state will be `pending_otp`.
# Batch canceled (/es/docs/api-reference/banking/batch-webhooks/webhook_batch_canceled)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a batch is canceled.
The batch state will be `cancelled`.
Includes the user who canceled the batch in the `canceled_by_user` field.
# Batch sent to process (/es/docs/api-reference/banking/batch-webhooks/webhook_batch_sent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a batch is sent to process.
The batch state will be `processing_transactions`.
Includes the user who approved the batch in the `approved_by_user` field.
# Spending Controls (/es/docs/api-reference/banking/spending-controls)
Endpoints for managing spending controls, rules, and targets.
## Spending Controls [#spending-controls]
Gets all the Card spending controls
Creates a spending control
Get a spending control
Updates a spending control
Deletes a spending control
## Rules [#rules]
Add or update a rule to a spending control
Delete a rule of a spending control
## Targets [#targets]
List spending control's targets
Add target to a spending control
Remove targets to a spending control
# Gets all the Card spending controls (/es/docs/api-reference/banking/spending-controls/list_spending_controls)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint get all the Card spending controls
# Delete a rule of a spending control (/es/docs/api-reference/banking/spending-controls/delete_spending_control_rule)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to delete a rule to a spending control
# Add target to a spending control (/es/docs/api-reference/banking/spending-controls/add_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to add target to a spending control
# Updates a spending control (/es/docs/api-reference/banking/spending-controls/update_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to update a spending control
# List spending control's targets (/es/docs/api-reference/banking/spending-controls/list_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to list spending control's targets
# Deletes a spending control (/es/docs/api-reference/banking/spending-controls/delete_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to delete a spending control
# Get a spending control (/es/docs/api-reference/banking/spending-controls/get_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to get a spending control
# Creates a spending control (/es/docs/api-reference/banking/spending-controls/create_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to create spending controls
# Remove targets to a spending control (/es/docs/api-reference/banking/spending-controls/remove_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to remove targets to a spending control
# Add or update a rule to a spending control (/es/docs/api-reference/banking/spending-controls/add_or_update_spending_control_rule)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to add or update a rule to a spending control
# Card Transaction Webhooks (/es/docs/api-reference/core/card-transaction-webhooks)
Receive notifications when card transaction events occur, including authorizations, adjustments, reversals, and refunds.
## Events [#events]
Card authorization approved
Card authorization declined
Card withdrawal
Card credit adjustment
Card debit adjustment
Card force debit
Card authorization reversed
Card refund
Card reconciliation
# Card force debit (/es/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_force_debit)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a transaction is made without a previous authorization.
This means that a charge will be done without taking into account the account's balance.
There is no `original_transaction_id` nor `previous_values`.
# Card credit adjustment (/es/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_credit_adjustment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an authorization is updated by a partial refund.
This means a card transaction has been approved and there will be a refund reflected in the
account balance. A partial refund is when part of the purchase value is returned to the account.
# Card authorization reversed (/es/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_authorization_reversed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a transaction is reversed and the value of the initial charge
is returned fully to the account.
# Card debit adjustment (/es/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_debit_adjustment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an authorization is updated with a new amount.
This means a card transaction has been approved and charged, and then a new charge is done
to update the previous authorization.
# Card authorization approved (/es/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_authorization_approved)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction has been authorized.
For example, after making a purchase or a payment with a card. It means a card transaction
has been approved and therefore a charge will be reflected on the account's balance.
# Card withdrawal (/es/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_withdrawal)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a withdrawal has been authorized.
For example, when the card owner withdraws money from an ATM. Includes a `withdrawal_fee` detail.
# Card refund (/es/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_refund)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a refund is made to the account.
The value of the refund can be related or not to a previous authorization.
# Card authorization declined (/es/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_authorization_declined)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction has not been authorized.
Includes a `declination_reason` to understand the reason behind the denied card transaction.
It doesn't reflect any change on the account's balance.
# Card reconciliation (/es/docs/api-reference/core/card-transaction-webhooks/webhook_card_transaction_reconciliation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a card transaction is reconciled.
Reconciliation occurs when the final settlement amount differs from the original authorization.
The `previous_values` field contains the original amounts before reconciliation.
# Collection Intents (/es/docs/api-reference/banking/collection-intents)
Endpoints for creating and managing collection intents.
## Endpoints [#endpoints]
Get list of collection intents
Create a collection intent
Get collection intent
# Get list of collection intents (/es/docs/api-reference/banking/collection-intents/list_collection_intents)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
This endpoint allows you to list your collection intents, optionally filtered by state, account.
and collection link id
# Create a collection intent (/es/docs/api-reference/banking/collection-intents/create_collection_intent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to create a collection intent
# Get collection intent (/es/docs/api-reference/banking/collection-intents/get_collection_intent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get collection intent detail
# Ledger (/es/docs/api-reference/core/ledger)
Endpoints for managing the core ledger system including account holders, accounts, balances, and transactions.
## Tenant [#tenant]
Get the tenant global configuration
## Account Holders [#account-holders]
Creates a third-party account holder
Get account holder information
Find account holder information
Updates a third-party account holder
## Accounts [#accounts]
List of ledger accounts
Creates a ledger account
Updates state of a ledger account
### Balances [#balances]
Get balances
Topup or withdrawal a ledger account balance
### Transactions [#transactions]
Gets account transactions
Gets an account transaction detail
Lists the transactions of every ledger account
### Transfers [#transfers]
Perform a transfer between ledger accounts
Find successful subaccount operation
## Other [#other]
Get FX rates for supported currencies
# Get balances (/es/docs/api-reference/core/ledger/get_ledger_account_balances)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get account balances
# Get account holder information (/es/docs/api-reference/core/ledger/get_account_holder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get detailed information about an account holder by ID.
# Perform a transfer between ledger accounts (/es/docs/api-reference/core/ledger/create_ledger_account_transfer)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Transfer funds smoothly between any two of your subaccounts
### General considerations [#general-considerations]
* This endpoint could be only used for accounts with type `subaccount`.
* Both payer and receiving accounts should be `active`.
* The associated account holders should also be `active`.
### Considerations for same currency transfers [#considerations-for-same-currency-transfers]
* You can specify either `source_amount` or `target_amount`, the other field should be set to null.
It will be set equal to the given amount.
* The currency code of the amount should be equal to the payer and receiving accounts' currency code.
### Considerations for transfers with different currencies [#considerations-for-transfers-with-different-currencies]
* You can specify either `source_amount` or `target_amount`, the other field should be set to null.
It will be calculated according to the given amount, `fx_rate`, and configured fees.
* The currency code of the `source_amount` should be equal to the payer account currency code.
* The currency code of the `target_amount` should be equal to the receiving account currency code.
* The `fx_rate` is the conversion rate from target currency to source currency
For example, with source currency `COP` and target currency `USD`, the rate would be like `4000`.
For source currency `USD` and target currency `COP`, the rate would be like `0.00025`.
The rate includes any FX markup configured (or overridden in the request).
#### How the amounts are calculated [#how-the-amounts-are-calculated]
##### If given target amount [#if-given-target-amount]
`source_amount = target_amount * fx_rate + fixed_fee` (rounded to the appropriate number of decimal places)
##### If given source amount [#if-given-source-amount]
`target_amount = (source_amount - fixed_fee) / fx_rate` (rounded to the appropriate number of decimal places)
#### How the `fx_rate` field works [#how-the-fx_rate-field-works]
If the `fx_rate` field is not given, it's calculated by using the current marked rate
(as returned by the FX rates endpoint) and adding the spread (FX markup).
For example, for a market rate of 4000 and a spread of 1%. `fx_rate` will be `4000 * (1 + 0.01) = 4040`.
If `fx_rate` is given, it's assumed to include the spread already, so the given rate won't be modified in any way.
#### How the `override_fees` field works [#how-the-override_fees-field-works]
There are two fees that apply to this operation:
* A fixed amount fee named `account_to_account_fx`, in the currency of the source amount
* A percentage fee named `account_to_acount_fx_spread`, included in the conversion rate
These fees are configured in the system but can be overridden for individual requests by passing them in `override_fees`.
For `account_to_account_fx`, the `fixed` and `tx_description` fields can be provided to override the defaults.
For `account_to_account_fx_spread`, `percentage` can be overridden.
#### What is returned in `calculated_fees` [#what-is-returned-in-calculated_fees]
For each supported fee (`account_to_account_fx` and `account_to_account_fx_spread`), a fee object is returned in
`calculated_fees` that contains the final amount of the fee.
This amount is always in the currency of the source amount and represents how much was charged for the operation.
The spread fee is calculated from the difference in rates between the actual conversion rate (`fx_rate`) and the market rate.
# Lists the transactions of every ledger account (/es/docs/api-reference/core/ledger/list_ledger_transactions)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the transactions of every ledger account of the tenant in one feed: its subaccounts and its
tenant-level accounts (`root`, `current`, `fees`). Each transaction carries the `account_id` of
the account it belongs to, so both legs of a transfer between two of your accounts appear, one per
account. Under each account, the feed lists the same transactions as that account's own listing,
`GET /v1/ledger/accounts/{account_id}/transactions`.
`sort[field]` selects how the feed is walked:
* `inserted` (default, ascending by default): the order in which the ledger recorded the
transactions. Use it to extract everything recorded since your last run in one pass: store the
`next_cursor` of the last page and send it on the next run, even when `has_more` is `false`. It
serves transactions about 2 minutes after the ledger records them, so a transaction committed late
is never left behind a cursor; for subaccount transactions, the `ledger_account_transaction_created`
webhook delivers them sooner. Once a page in ascending order reaches that delay, its
`next_cursor` moves up to it, even when the page is empty, so the next run does not read the
same range again. The `transaction_at` and `origin_transaction_at` filters are not accepted in
this order.
* `transaction_at` or `origin_transaction_at` (descending by default): the transactions by that
date, with no delay. A walk in these orders does not come back for a transaction recorded after
it passed that transaction's date, so use `inserted` to extract.
Pagination is by cursor only: `next_cursor` reads the next page of the same request and is
rejected with `invalid_cursor` if it is sent with another sort or other filters. It is kept even
when `has_more` is `false`, and it is `null` only when a first page outside ascending `inserted`
order comes back empty. `page_size` can change between pages. There are no page numbers nor
totals.
`reverted_by_id` and `origin_state` are read when the page is served: a transaction already read
can be reversed or change its origin state later. A reversal arrives as its own transaction, whose
`reverts_id` points to the transaction it reverts.
In a date order, a range on that same date, such as `transaction_at` in `transaction_at` order,
narrows what the pages read: set it to skip history you do not need.
# Get FX rates for supported currencies (/es/docs/api-reference/core/ledger/get_fx_rates)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns FX rates for the tenant's supported currencies. If no date is provided, returns the latest rates.
If a date is provided, returns historical rates for that date (in UTC).
# Gets an account transaction detail (/es/docs/api-reference/core/ledger/get_ledger_account_transaction)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets an account transaction detail
# Creates a third-party account holder (/es/docs/api-reference/core/ledger/create_account_holder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a new third-party account holder and associates it to the tenant.
# List of ledger accounts (/es/docs/api-reference/core/ledger/list_ledger_accounts)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists all the ledger accounts related to the tenant
Considerations:
1. This endpoint doesn't return the account balance for every account, so you can use the endpoint to get the balance for a specific account `/v1/ledger/account/:id/balances`
2. You couldn't retrieve more than 500 registers in a single request.
# Find successful subaccount operation (/es/docs/api-reference/core/ledger/find_successful_subaccount_operation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Find successful subaccount operation by external\_id
# Creates a ledger account (/es/docs/api-reference/core/ledger/create_ledger_account)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a ledger account associated to a third-party account holder.
Considerations:
1. You could previusly check the accepted currency codes in the endpoint `/v1/ledger/tenant/config`.
2. The third-party account holder must be `active`.
# Updates state of a ledger account (/es/docs/api-reference/core/ledger/update_ledger_account_state)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Updates state of a subaccount type ledger account.
Considerations:
1. You couldn't modify state of the canceled ledger accounts.
2. You couldn't modify accounts where the type is different of a `subaccount`
# Gets account transactions (/es/docs/api-reference/core/ledger/list_ledger_account_transactions)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets account transactions
# Updates a third-party account holder (/es/docs/api-reference/core/ledger/update_account_holder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Updates a third-party account holder
# Get the tenant global configuration (/es/docs/api-reference/core/ledger/get_tenant_config)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get the configuration variables tailored for your ledger environment by accessing the tenant configuration endpoint.
# Find account holder information (/es/docs/api-reference/core/ledger/find_account_holder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Find account holder information by external\_id or document\_type and document\_number
# Topup or withdrawal a ledger account balance (/es/docs/api-reference/core/ledger/update_ledger_account_balance)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Topup or withdraw a ledger account balance.
Considerations:
1. This endpoint could be only used for accounts with type `subaccount`.
2. The accounts should be `active`.
3. The associated account holder should also be `active`.
4. You can only make operations if the currency code of the amount is the same as the currency code of the account.
# Cards (/es/docs/api-reference/core/cards)
Endpoints for managing cards, cardholders, and digital wallet provisioning. To issue a new card against a ledger account, see [Ledger Cards](/docs/api-reference/core/ledger-cards).
## Cards [#cards]
List cards
Gets card
Update a card
Gets card transactions
Gets information about the card look
## Cardholders [#cardholders]
Create a cardholder
Show a cardholder
Update a cardholder
## PIN Management [#pin-management]
Change plastic card pin
## Plastic Card Activation [#plastic-card-activation]
Generate a URL to activate a plastic card
List plastic card activations
Get plastic card activation
## Digital Wallet Provisioning [#digital-wallet-provisioning]
Apple Pay Tokenization
Google Pay Tokenization
# Gets card (/es/docs/api-reference/core/cards/get_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card with sensitive information
# Update a cardholder (/es/docs/api-reference/core/cards/update_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update a cardholder
# List plastic card activations (/es/docs/api-reference/core/cards/list_plastic_card_activations)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List plastic card activations
# Gets card transactions (/es/docs/api-reference/core/cards/get_card_transactions)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets card transactions
# Get plastic card activation (/es/docs/api-reference/core/cards/get_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get plastic card activation
# Update a card (/es/docs/api-reference/core/cards/update_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to update cards
# Get plastic card activation (/es/docs/api-reference/core/cards/fetch_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get plastic card activation
# Gets the sensitive details of a card (/es/docs/api-reference/core/cards/get_card_look)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the URL of an iframe that displays the card's sensitive details — number,
security code and expiration — so an app or site can show them without ever
handling them. The URL carries a short-lived credential of the cardholder, so
treat it as sensitive: anyone holding it sees the card.
Which fields the iframe renders comes from the card's configuration group; by
default it renders all three.
# Generate a URL to activate a plastic card (/es/docs/api-reference/core/cards/build_plastic_card_activation_link)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a URL to activate a plastic card
# List cards (/es/docs/api-reference/core/cards/list_cards)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List cards. the result will include the cards with the paging information
# Change plastic card pin (/es/docs/api-reference/core/cards/change_card_pin)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to change plastic card PIN
# Apple Pay Tokenization (/es/docs/api-reference/core/cards/provision_apple_pay_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to handle Apple Pay token provisioning. If you want to use this endpoint, communicate with us.
# Show a cardholder (/es/docs/api-reference/core/cards/get_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Show a cardholder
# Create a cardholder (/es/docs/api-reference/core/cards/create_cardholder)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a cardholder
# List plastic card activations (/es/docs/api-reference/core/cards/get_plastic_card_activations)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
List plastic card activations
# Generate an URL to activate a plastic card (/es/docs/api-reference/core/cards/activate_plastic_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create an URL to activate a plastic card
# Google Pay Tokenization (/es/docs/api-reference/core/cards/provision_google_pay_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to handle Google Pay token provisioning. If you want to use this endpoint, communicate with us.
# Tenant Accounts (/es/docs/api-reference/breb-participant/tenant-accounts)
Endpoints for retrieving tenant accounts associated with your authenticated tenant.
## Endpoints [#endpoints]
List tenant accounts
Get tenant account by ID
# Get tenant account by ID (/es/docs/api-reference/breb-participant/tenant-accounts/get_a_tenant_account_by_id)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves a single tenant account by its ID.
# List tenant accounts (/es/docs/api-reference/breb-participant/tenant-accounts/list_tenant_accounts)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists all accounts associated with the authenticated tenant with optional filters.
# Ledger Cards (/es/docs/api-reference/core/ledger-cards)
Issue a card backed by one of your ledger accounts. Once issued, manage it with the [Cards](/docs/api-reference/core/cards) endpoints.
## Endpoints [#endpoints]
Creates a card
# Creates a card (/es/docs/api-reference/core/ledger-cards/create_ledger_card)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to create cards.
Considerations:
1. This endpoint could be only used for accounts with type `subaccount`. To issue a card for
yourself, create a subaccount under the account holder of type `self` and use that one.
# Authentication (/es/docs/api-reference/breb-participant/authentication)
Endpoints for managing OAuth 2.0 tokens used to authenticate requests to the Bre-B Participant API.
The scopes a Bre-B Participant token can carry, and what each one grants, are listed in the [Bre-B Participant authentication guide](/docs/guides/breb-participant/authentication#scopes).
## Endpoints [#endpoints]
Obtain access token using client credentials or refresh token
Revoke an access or refresh token
Introspect token to check validity and metadata
# Obtain access token (/es/docs/api-reference/breb-participant/authentication/get_auth_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token endpoint supporting client credentials and refresh token grant types.
Use this endpoint to obtain an access token that can be used to authenticate
requests to other API endpoints. The token should be included in the
Authorization header as a Bearer token.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Grant Types:**
* **client\_credentials**: Use client ID and secret to obtain an access token
* **refresh\_token**: Use a refresh token to obtain a new access token. A refresh token works
once: the response carries the one to use next, and the one sent stops working.
# Introspect token (/es/docs/api-reference/breb-participant/authentication/introspect_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token introspection endpoint (RFC 7662).
Use this endpoint to determine the active state and metadata of an access token or refresh token.
This is useful for resource servers to validate tokens and retrieve their associated information.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* If the token is active, returns token metadata including client\_id, scope, expiration, etc.
* If the token is inactive, expired, or invalid, returns `{"active": false}`
# Revoke token (/es/docs/api-reference/breb-participant/authentication/revoke_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token revocation endpoint (RFC 7009).
Use this endpoint to revoke an access token or refresh token. After revocation,
the token will no longer be valid for authentication or authorization.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* Returns 200 OK with empty body on successful revocation
* The server responds with 200 OK even if the token was already invalid or expired
# Sandbox (/es/docs/api-reference/breb-participant/sandbox)
These endpoints are only available in the sandbox environment.
Visit the [sandbox guide](/docs/guides/breb-participant/sandbox/collections) for detailed explanations on how to simulate a collection attempt in this environment.
## Endpoints [#endpoints]
Simulate a collection attempt
# Simulate a collection attempt (/es/docs/api-reference/breb-participant/sandbox/create_sandbox_collection_attempt)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Simulates a payment attempt to a collection in the sandbox environment.
**Requirements:**
* The creditor key must exist, be active and needs to be related to a collection
* The associated tenant account must be valid
**Payer information:**
If payer information is not provided, it will be randomly generated.
**Paying a QR:**
Send the QR's `payment_id` to simulate a payment by QR, either as the QR
creation returned it (`CO.COM.CRB.TRXIDP…`) or as the transaction ID read
from the QR itself (`P…`, subfield `01` of the `CO.COM.CRB.TRXID` template).
It must belong to a QR of the creditor key, or the request fails with
`qr_code_not_found`. Leave it out to simulate a payment to the key.
**Error simulation:**
Use the `error` field to simulate different failure scenarios. The payment
will be created successfully but will receive a failure event with the
specified error.
# Payin Webhooks (/es/docs/api-reference/core/payin-webhooks)
Receive notifications across the whole collection flow: the template that accepts payments,
the intent prepared for a payer, the payin itself, and the batch that delivers the funds to
your funding account.
## Templates [#templates]
A template is a reusable configuration tied to a subaccount. These events tell you when it
can accept payins, when it changes, and when it stops accepting them.
Payin template created
Payin template ready
Payin template updated
Payin template minimum paid
Payin template paid
Payin template discarded
Payin template failed
## Intents [#intents]
An intent is a single expected payment declared before the funds arrive. These events follow
it from creation until it is paid, fails, or expires.
Payin intent created
Payin intent ready to pay
Payin intent awaiting payment
Payin intent attempt failed
Payin intent successful
Payin intent failed
Payin intent expired
## Payins [#payins]
A payin is a payment received from a payer. These events follow the money from the moment it
is registered until it is credited to your subaccount — or rejected.
Payin created
Payin approved in provider
Payin subaccount credited
Payin rejected
Payin failed
## Settlement batches [#settlement-batches]
A settlement batch is the single money movement that delivers the funds of many payins to
your funding account.
Payin settlement batch created
Payin settlement batch processing
Payin settlement batch completed
# Payin intent attempt failed (/es/docs/api-reference/core/payin-webhooks/webhook_payin_intent_attempt_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payment attempt for a payin intent fails after
authorization, but the intent survives it.
The payload carries the intent back in `ready_to_pay`, so the payer can retry with the
same payment token while the intent lives. The failed attempt itself is reported by
the `payin_rejected` or `payin_failed` webhook. When the provider rejected the payment
made through the intent's dynamic QR, the QR cannot be paid again, and a
`payin_intent_failed` with `qr_code_rejected` follows shortly after.
# Payin intent ready to pay (/es/docs/api-reference/core/payin-webhooks/webhook_payin_intent_ready_to_pay)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin intent becomes payable: its payment token is
ready.
The payload carries the `payment_tokens` (Bre-B key and QR, when requested) the payer
needs and the `expires_at` after which the intent is no longer payable. It is also
sent when an intent is re-armed after a failed payment attempt.
If a requested QR could not be created, the intent still becomes payable through its
Bre-B key alone: `payment_tokens.qr` comes null and `payment_tokens.qr_error` says why
(`service_failure` — a provider-side failure, nothing to fix on your side).
# Payin intent created (/es/docs/api-reference/core/payin-webhooks/webhook_payin_intent_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered immediately after a payin intent is accepted and persisted.
The intent is in the `created` state: its payment token is still being prepared, so
`payment_tokens` is null and the payer cannot pay it yet.
# Payin template created (/es/docs/api-reference/core/payin-webhooks/webhook_payin_template_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered as soon as a payin template is accepted and persisted.
The template is in the `created` state: the payment tokens of the methods that use
them are still being set up, so `payment_tokens` is null and the template cannot
accept payins yet — `payin_template_ready` announces that. Everything else in the
payload is what the template was created with, plus the fields the platform derives
at creation (`id`, `state`, `paid_amount`, the counters and the timestamps).
# Payin intent awaiting payment (/es/docs/api-reference/core/payin-webhooks/webhook_payin_intent_awaiting_payment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin intent transitions to `awaiting_payment`: a payin
for it was authorized and its payment is in flight.
# Payin template minimum paid (/es/docs/api-reference/core/payin-webhooks/webhook_payin_template_minimum_paid)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered the first time the cumulative `paid_amount` of a payin
template reaches its `total_minimum_amount`.
The template stays payable up to `total_maximum_amount`.
# Payin rejected (/es/docs/api-reference/core/payin-webhooks/webhook_payin_rejected)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin is rejected.
`state_reason` explains the business, configuration or payer condition behind the
rejection (e.g. `amount_exceeds_maximum`, `payer_not_allowed`, `template_expired`,
`bank_rejected`). The payer's funds, if any, are returned by the provider.
# Payin intent successful (/es/docs/api-reference/core/payin-webhooks/webhook_payin_intent_successful)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin intent is spent by its successful payment.
The intent is final: its payment tokens are torn down and it can no longer be paid.
# Payin subaccount credited (/es/docs/api-reference/core/payin-webhooks/webhook_payin_subaccount_credited)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when the payin funds are credited to the subaccount.
`subaccount_amount` is the amount credited, in the subaccount currency. The delivery
of the funds to the funding account is reported separately by the settlement batch
webhooks.
# Payin template discarded (/es/docs/api-reference/core/payin-webhooks/webhook_payin_template_discarded)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin template is discarded.
A template is discarded when it is deleted, when it expires or when it is swept for
inactivity. Its payment tokens are torn down with it, so it can no longer be paid;
`prune_reason` explains why.
# Payin template ready (/es/docs/api-reference/core/payin-webhooks/webhook_payin_template_ready)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin template finishes its payment-token setup and
moves to `ready`.
From this point the template can accept payins. `payment_tokens` carries one entry per
method that issues a token the payer addresses the payment to — for `co_breb`, the
Bre-B key and its QR when one was requested. Methods that need no token (the payer is
sent to the provider instead, as with `co_pse`) have no entry there.
# Payin intent failed (/es/docs/api-reference/core/payin-webhooks/webhook_payin_intent_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin intent fails for good and there is nothing left
to pay.
`state_reason` explains the cause: a payment that failed on a method that does not
re-arm attempts (`payment_failed`), a payment token that never made it
(`key_registration_failed`, `payment_token_registration_failed`), or a dynamic QR
whose payment the provider rejected (`qr_code_rejected`).
# Payin failed (/es/docs/api-reference/core/payin-webhooks/webhook_payin_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin cannot be processed because of a system or
provider failure.
Unlike `payin_rejected`, the cause is not a business condition: `state_reason` is
`lock_timeout` when the template lock could not be acquired in time, or `unknown` for
any other internal failure.
# Payin created (/es/docs/api-reference/core/payin-webhooks/webhook_payin_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin is first persisted, before any confirmation from
the provider.
No funds have moved to the subaccount yet.
With `co_breb` it carries who paid (`instrument.payer`), how much (`amount`) and the
key the payment was received on (`recipient_data`).
With `co_pse` the payin is created with its intent, before the payer pays: it is a
payment attempt, not money received. `instrument.payer` is who is about to pay, and
`transaction_data.cus` is null until the PSE charge is mounted
(`payin_awaiting_payment`).
# Payin settlement batch processing (/es/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_processing)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a settlement batch is accepted by its delivery provider
and moves to `processing`.
# Payin template updated (/es/docs/api-reference/core/payin-webhooks/webhook_payin_template_updated)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered after every successful update of a payin template.
The payload is the updated template plus a `previous_values` object holding only the
fields that changed, with their pre-update values. When an update of
`total_minimum_amount`/`total_maximum_amount` also moves the template between
`ready`, `minimum_paid` and `paid`, the transition travels inside this event
(`previous_values.state`) and the separate state webhooks are not sent.
# Payin approved in provider (/es/docs/api-reference/core/payin-webhooks/webhook_payin_approved_in_provider)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when the provider confirms the payin was approved.
The money exists on the provider side and `funds_delivery_status` is set according to
the program configuration, but the funds have not been credited to the subaccount yet.
# Payin awaiting payment (/es/docs/api-reference/core/payin-webhooks/webhook_payin_awaiting_payment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin moves to `awaiting_payment`: its charge is
mounted and the payer is authorizing it on their bank's page. The payment is not
confirmed yet — that is `payin_approved_in_provider`.
Only methods whose payin exists before the payment emit it (`co_pse`), and it is
the first event that carries the payment's `transaction_data.cus`.
# Payin settlement batch created (/es/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when pending payins are grouped into a settlement batch.
The batch is the single money movement that delivers the funds of the grouped payins
to the funding account. `payins` lists what came in and what will be delivered for
each one; the money has not moved yet.
# Payin settlement batch completed (/es/docs/api-reference/core/payin-webhooks/webhook_payin_settlement_batch_completed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a settlement batch completes: the funds reached the
destination funding account.
There is no failure counterpart: `retryable` is a transient send failure the system
resends on a later run and `failed` is terminal and resolved manually, so neither is
notified.
# Payin intent expired (/es/docs/api-reference/core/payin-webhooks/webhook_payin_intent_expired)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payin intent expires: its window closed with nobody
paying it.
The intent is no longer payable and its `external_id` is free for a new intent.
# Payin template failed (/es/docs/api-reference/core/payin-webhooks/webhook_payin_template_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when the payment-token setup of a payin template fails and it
moves to `failed`.
A `failed` template can never accept payins. `state_reason` explains why the setup
failed (e.g. `key_already_registered`).
# Payin template paid (/es/docs/api-reference/core/payin-webhooks/webhook_payin_template_paid)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when the cumulative `paid_amount` of a payin template reaches
its `total_maximum_amount` and the template transitions to `paid`.
A `paid` template accepts no further payins.
# Collection attempt authorization (/es/docs/api-reference/breb-participant/collection-authorization/webhook_collection_attempt_authorization)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event recording how an authorization resolved. It is emitted for
**every** outcome — you approved, you declined, or Mono could not obtain
a usable answer and applied the collection's `failure_mode` — and the
attempt's `authorization.outcome` says which.
It fires exactly where the attempt carries an `authorization` object, so
an attempt Mono never asked you about produces none: a collection
without `authorization`, one Mono's own rules rejected before you were
asked, or one Mono could not ask about at all because of an incident on
its own side. A request that left Mono and never reached you does
produce one, with `authorization.failure_kind` set to `transport`.
Two things make it worth consuming even though you answered the request
yourself. Under `fail_open` the attempt continues to the bank as if you
had approved it, and if the payment then settles its lifecycle webhook
is indistinguishable from a real approval — this event is the only thing
that tells them apart. And because it reports what Mono *recorded*, it
closes the loop on a response of yours that was subtly malformed: from
your side the request looked answered, while Mono resolved it through
`failure_mode`.
The event goes out before the receiving bank has confirmed, so an
`approved` here can still be followed by
`collection.attempt_unsuccessful` — approving is not settling.
`data` is the collection and the attempt as they stand when this webhook
is delivered, which for this event is normally before the bank has
answered: expect the attempt in `created`, and the collection's
`paid_amount` and `successful_attempts` not yet moved. It is delivered
independently of the attempt's lifecycle webhook, so neither order is
guaranteed; correlate the two by `attempt.id`.
This event is additional, never a replacement: the attempt's own
lifecycle webhook still fires. It is opt-in per webhook subscription.
# Ledger Account Transaction Webhooks (/es/docs/api-reference/core/ledger-account-transaction-webhooks)
Receive notifications when ledger account transactions are recorded for subaccounts.
## Events [#events]
Ledger account transaction created
# Ledger account transaction created (/es/docs/api-reference/core/ledger-account-transaction-webhooks/webhook_ledger_account_transaction_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a new ledger account transaction is recorded for a subaccount.
It includes the transaction details and the origin entity that caused the transaction.
The `details` field structure varies depending on the `origin` value.
Possible origins:
* `subaccount_operation`: A subaccount operation (topup, withdrawal, account-to-account transfer, etc.)
* `payout`: A payout to an external bank account
* `card_transaction`: A card transaction (purchase, ATM withdrawal, etc.)
* `card_presentment_payment`: Final settlement of an international card transaction
* `fee`: A fee charge
* `synced_bank_transaction`: A synced bank transaction from an external bank
* `payin`: A payin collected into a subaccount
# Collection Webhooks (/es/docs/api-reference/breb-participant/collection-webhooks)
Receive notifications when collection state changes or payment attempts occur.
## Events [#events]
Collection created
Collection ready to receive payments
Collection failed
Collection discarded
Collection updated
Collection minimum amount paid
Collection fully paid
Payment attempt successful
Payment attempt unsuccessful
Payment attempt reversed
# Collection created (/es/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a new collection is created.
This event is sent immediately after the collection is accepted and persisted.
At this point, the Bre-B key registration process has not started yet.
The collection will be in `created` state.
# Collection ready (/es/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_ready)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection is ready to receive payments.
This event is sent after the Bre-B key has been successfully registered in the DICE.
The collection will be in `ready` state and will include the `key_type` and `key_value`
fields with the registered key information.
# Collection updated (/es/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_updated)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection is updated by the tenant.
This event is sent when the tenant updates collection fields via the PATCH endpoint.
The `data.previous_values` object contains the previous values of all fields that were changed.
**Important:** If the update causes a state change (e.g., updating `total_minimum_amount`
triggers a transition to `minimum_paid`), `previous_values` will include the previous `state`.
This event does NOT trigger separate state change webhooks like `collection.minimum_paid`.
Only the `collection.updated` webhook is sent.
# Collection paid (/es/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_paid)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection reaches the `paid` state.
This occurs when the `paid_amount` equals the `total_maximum_amount`.
The collection is now complete and will no longer accept payments.
# Collection discarded (/es/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_discarded)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection is discarded.
This event is sent when:
* A collection is automatically discarded due to expiration (`state_reason`: "expired")
* A collection is automatically discarded due to inactivity (`state_reason`: "inactivity")
* A collection is deleted by the tenant via the DELETE endpoint (`state_reason`: "deleted")
The collection will be in `discarded` state.
# Collection failed (/es/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection fails during the key registration process.
This event is sent when the Bre-B key registration in the DICE fails.
The collection will be in `failed` state with a `state_reason` explaining the failure.
The `keys` array will be empty.
# Collection attempt successful (/es/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_successful)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payment attempt against a collection is successful.
The collection's `paid_amount` is updated with the new accumulated amount,
`successful_attempts` is incremented, and the collection state may change to
`paid`, `minimum_paid`, or remain `ready`/`minimum_paid` depending on the amounts.
# Collection minimum paid (/es/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_minimum_paid)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a collection reaches the `minimum_paid` state.
This occurs when the `paid_amount` reaches or exceeds the `total_minimum_amount`.
The collection can still receive more payments until it reaches `paid` state.
# Collection attempt unsuccessful (/es/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_unsuccessful)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a payment attempt against a collection fails or is rejected.
* `rejected`: The payment was rejected by Mono based on collection rules (expired, disabled, amount limits, etc.)
* `failed`: The payment failed due to external reasons not controlled by Mono (bank errors, timeouts, etc.)
The collection's `failed_attempts` is incremented. The collection state remains unchanged.
# Collection attempt reversed (/es/docs/api-reference/breb-participant/collection-webhooks/webhook_collection_attempt_reversed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a previously successful payment attempt against a
collection is reversed (the underlying incoming transfer was reversed by Bre-B
reconciliation).
The reversed amount is subtracted from the collection's `paid_amount`,
`successful_attempts` is decremented, and the collection state is recalculated
from the remaining `paid_amount`: a `paid` or `minimum_paid` collection may
reopen as `ready` or `minimum_paid`. The attempt transitions to `reversed`
with `state_reason: transfer_reversed`.
# Banks (/es/docs/api-reference/core/banks)
The list of banks you can send money to, with the code that identifies each one. Use the code to set the destination bank of a payout.
## Endpoints [#endpoints]
Get banks
# Get banks (/es/docs/api-reference/core/banks/list_banks)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Every operation that has to name a bank takes its `code`, and this endpoint is where those codes come from: preparing or applying a bank transfer and creating a collection intent, for a banking credential, and sending a payout, for a core one.
It retrieves all the banks we support with the `code` each of them is known by here, and the account types supported by each bank you can transfer to (only when the `kind` parameter is `transfers`).
# Payouts (/es/docs/api-reference/core/payouts)
Endpoints for creating payouts and resolving payout targets.
## Endpoints [#endpoints]
Creates a payout
Get payout by ID
Find successful payout by external ID
### For Bre-B method [#for-bre-b-method]
Endpoints exclusive to the `co_breb` payout method.
Resolves a Bre-B target
# Creates a payout (/es/docs/api-reference/core/payouts/create_payout)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Endpoint to create payouts.
Considerations:
1. The source account must be of type `subaccount`.
2. The source account and its account holder should be `active`.
# Resolves a Bre-B target (/es/docs/api-reference/core/payouts/resolve_breb_target)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Endpoint to resolve a Bre-B target for payouts synchronously
# Get payout by ID (/es/docs/api-reference/core/payouts/get_payout)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Endpoint to get a payout by its ID
# Find successful payout by external ID (/es/docs/api-reference/core/payouts/find_successful_payout)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Find successful payout by external\_id
# Descubrimiento de llaves (/es/docs/api-reference/breb-participant/target-discovery)
Usa el descubrimiento de llaves cuando conoces al beneficiario y la cuenta de
destino, pero no sabes qué llave Bre-B apunta a ellos. Empieza por el catálogo
bancario para obtener un código ACH canónico y luego envía los datos del
beneficiario a discovery.
## Endpoints [#endpoints]
Lista o busca bancos
Consulta un banco por su código ACH canónico
Descubre una llave
Para ver el flujo de extremo a extremo, consulta la [guía de descubrimiento de
llaves](/docs/guides/breb-participant/flows/target-discovery).
# Descubrir llave (/es/docs/api-reference/breb-participant/target-discovery/discover_target)
Descubre una llave Bre-B de un beneficiario a partir de los datos que ya
conoces y devuelve una llave utilizable **solo** cuando puede comprobar que
la llave pertenece a esa misma persona y apunta a esa misma cuenta bancaria.
Ambos resultados responden `200`:
* `state=discovered`: se creó una llave de un solo uso para el banco y la
cuenta solicitados. Puedes usarla en una sola transferencia saliente; repetir
discovery produce una resolución nueva.
* `state=not_discovered` con `state_reason=no_exact_target`: no fue posible
crear una llave para ese destino. Esto no implica que la persona no tenga
otra llave.
Ambos incluyen `same_person_keys`, posiblemente vacío: llaves confirmadas de
la misma persona que apuntan a otro destino. Discovery se detiene en la primera
llave exacta, de modo que una respuesta `discovered` solo refleja las
coincidencias observadas antes del ganador y no es exhaustiva. Estas llaves
todavía **no** pueden usarse para transferir. Para usar una, selecciónala explícitamente y llama a
`POST /api/v1/targets/resolve`; después compara el banco y la terminación de
cuenta de la llave recién resuelta con los datos mostrados al ordenante antes de autorizar
la transferencia. Discovery no reserva la llave ni garantiza que siga
apuntando al destino observado.
Usa `GET /api/v1/banks` para convertir un nombre aproximado en el
`account.bank_code` canónico. Discovery solo acepta el código ACH canónico.
El endpoint aplica rate limit por cliente OAuth y por tenant. Si se supera
cualquiera de los límites, responde `429 rate_limit_exceeded` e incluye el
encabezado `Retry-After`.
# Consultar banco (/es/docs/api-reference/breb-participant/target-discovery/get_bank)
Consulta exacta por código ACH canónico. Un código ausente del catálogo
responde `404 bank_not_found`; el catálogo solo incluye los bancos aceptados
por discovery.
# Listar bancos (/es/docs/api-reference/breb-participant/target-discovery/list_banks)
Lista, en orden canónico, todos los bancos que acepta el descubrimiento de
llaves.
Envía `q` para buscar por nombre o alias. La búsqueda tolera tildes,
puntuación, nombres parciales y errores tipográficos, y separa una coincidencia
confiable de una sugerencia:
* `match` contiene un único banco y `data` está vacío cuando la consulta
coincide exactamente con un código ACH, nombre canónico o alias. Este es el
único resultado que puedes usar sin pedir al ordenante que elija.
* `match` es `null` y `data` contiene sugerencias ordenadas cuando la consulta
solo coincide por prefijo o similitud, **aunque haya una sola**. Muéstralas y
deja que el ordenante elija.
* Una búsqueda sin resultados responde `200` con `match: null` y `data` vacío.
En todos los casos, envía a discovery el `ach_code` canónico del banco
seleccionado.
# Fees (/es/docs/api-reference/core/fees)
Endpoints to manage your fee policies — the pricing rules of the fees you charge your users — and to list every fee charged on your accounts, yours and Mono's. See the [Fees](/docs/guides/core/concepts/fees) and [Fee policies](/docs/guides/core/concepts/fees/policies) guides for the concepts behind them.
## Fee policies [#fee-policies]
Creates one or many fee policies atomically
Lists your fee policies, with filters and pagination
Gets a fee policy by ID
Updates the price or settings of a fee policy
Deletes a fee policy
## Per account [#per-account]
Lists the fee policies that apply to a subaccount, and which one wins
## Fees [#fees]
Lists the fees charged on your accounts — yours and Mono's
# Lists fee policies (/es/docs/api-reference/core/fees/list_fee_policies)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists your fee policies — the ones pricing the fees you charge your users — newest first,
paginated. Deleted policies are never listed.
#### Filters [#filters]
* `fee_type` — the kind of fee the policies price.
* `target_type` — the kind of entity they apply to.
* `target_id` — one entity in particular; requires its `target_type`.
* `skip` — whether they are skipped (paused).
To see which policies apply to one specific account and which of them wins, use
[the account's fee policies endpoint](/docs/api-reference/core/fees/list_account_fee_policies)
instead.
# Creates fee policies (/es/docs/api-reference/core/fees/create_fee_policies)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates one or more fee policies at once.
A **fee policy** tells Mono how much to charge your users for a kind of fee — for example,
the fee a subaccount pays for a bank-transfer payout — and whom it applies to: every
account of your users or customers (`tenant`), one program, one account holder or a
single subaccount. When a fee is charged, the most specific policy for the charged account
wins, so you can set a general price and override it only where you need to.
#### How the request works [#how-the-request-works]
Send the policies under `policies`, always as a list, even for a single one. The batch is
**atomic**: if any policy is rejected, none is created, and the error points at the item
that failed with its position in the list (for example `/policies/1/strategies/COP`). The
created policies come back under `policies`, in the order you sent them.
#### Keep in mind [#keep-in-mind]
* Your **base or global policies** — one per fee type, with `target_type: tenant` — are
set up by Mono. You can update them but not create or delete them: they are your default
price, and the policies you create for programs, account holders or accounts override it.
* Only **one active policy per fee type and target**. To change the price of a target that
already has one, update that policy: creating another is rejected with
`fee_policy_already_exists`.
* `strategies` decides the **currencies the policy covers**, and it must stay within the
currencies its target supports. The policy only applies to the currencies it includes;
for the others, the next less specific policy applies.
* `processing_type` and `processing_subtype` must be among the ones the fee type supports.
* A new policy never starts out skipped (paused); use the update endpoint to skip it
afterwards.
See the [fee policies guide](/docs/guides/core/concepts/fees/policies) for how policies are
resolved and priced, and the [conditions reference](/docs/guides/core/concepts/fees/conditions)
for the properties and operators the rules can use.
# Gets a fee policy (/es/docs/api-reference/core/fees/get_fee_policy)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets one of your fee policies by its ID, with its full strategies.
The lookup is limited to your own policies: an ID that doesn't exist, belongs to another
tenant or was deleted is reported as `fee_policy_not_found`.
# Lists fees (/es/docs/api-reference/core/fees/list_fees)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the fees charged on your accounts, newest first, paginated. Use it to reconcile what
was charged, when, on which account and under which rule.
#### The two sides [#the-two-sides]
Two kinds of fees appear here, told apart by `owner`:
* `tenant` — the fees **you charge your own users**, debited on their subaccounts and priced
with your fee policies. They are your revenue.
* `mono` — the fees **Mono charges you** for using the platform, debited on your own
accounts. They are your cost.
Omit `owner` to list both.
#### What each fee carries [#what-each-fee-carries]
The policy that priced it, the rule and the calculation that produced the amount, its
state along the charging lifecycle and the entity that triggered it — a payout, a payin or
a transfer between subaccounts. Filter by any of them, or by the date range of the
transaction that charged the fee.
See the [fees guide](/docs/guides/core/concepts/fees) for the kinds of fees and their
lifecycle.
# Deletes a fee policy (/es/docs/api-reference/core/fees/delete_fee_policy)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes one of your fee policies. From then on it no longer applies — the next less
specific policy takes over the accounts it covered — and it is no longer listed, so its
`target_type`, `target_id` and `fee_type` combination is free for a new policy.
Your **base policies** (`target_type: tenant`) can't be deleted: they guarantee every fee
you charge has a price. Only the policies overriding them — the ones targeting a program,
an account holder or an account — can.
Returns `204 No Content`. Fees already charged with the policy are kept and still reference
it.
# Lists the fee policies that apply to an account (/es/docs/api-reference/core/fees/list_account_fee_policies)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Shows, for one of your subaccounts, every fee policy that could price a fee on it and
which one actually does. Use it to answer "how much will this account be charged, and
why?", or to check the effect of a policy you just created or skipped.
#### How the response is built [#how-the-response-is-built]
The policies come grouped by fee type. Within each group they go from the most to the
least specific target — `account`, `account_holder`, `program`, `tenant` — and exactly one
has `applied: true`: the most specific one that is not skipped or paused. Skipped (paused)
policies are listed with `applied: false`, so it is visible why a policy is not applying.
Only the policies with a strategy in the account's currency bear on the account, and only
the fee types with at least one policy are present.
#### Scope [#scope]
Only subaccounts have policies applied on them. Any other kind of account, or an account
that isn't yours, is reported as `account_not_found`. Narrow the response to a single fee
type with `fee_type`.
See [who a policy applies to](/docs/guides/core/concepts/fees/policies#who-a-policy-applies-to)
in the fee policies guide.
# Updates a fee policy (/es/docs/api-reference/core/fees/update_fee_policy)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Changes the price or the settings of one of your fee policies. Only the fields you send
are replaced; the ones you leave out keep their value.
#### What can change [#what-can-change]
* `strategies`, when sent, **replaces the whole map**: send every currency the policy must
keep. Removing a currency is a matter of sending the map without it. The new strategies
are checked against the policy's target the same way as on creation.
* `skip: true` skips (pauses) the policy: Mono ignores it while resolving, so the next
less specific one applies. Your base policies (`target_type: tenant`) can never be
skipped: they are the last resort guaranteeing every fee has a price.
* `summary`, `transaction_description`, `processing_type` and `processing_subtype` change
freely, within what the fee type supports.
#### What can't [#what-cant]
`fee_type`, `target_type` and `target_id` identify the policy and are immutable. To move a
policy to another target, delete it and create a new one.
Changes apply to the fees charged from then on. Fees already charged keep the price they
were calculated with. See the [fee policies guide](/docs/guides/core/concepts/fees/policies).
# Outgoing Transfers (/es/docs/api-reference/breb-participant/outgoing-transfers)
Endpoints for creating outgoing transfers, resolving targets, and retrieving transfer information.
## Endpoints [#endpoints]
Resolve target
Create outgoing transfer batch
List outgoing transfers
Get outgoing transfer by ID
Find successful outgoing transfer
# Find successful outgoing transfer (/es/docs/api-reference/breb-participant/outgoing-transfers/find_an_successful_outgoing_transfer)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Find an outgoing transfer by external\_id.
# Resolve target (/es/docs/api-reference/breb-participant/outgoing-transfers/resolve_target)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a target resolution request to resolve a payment destination.
Supports multiple resolution formats:
* **plain\_key**: Resolve by cellphone, email, identification, alphanumeric key, or merchant code
* **image**: Resolve by scanning a QR code image (base64-encoded)
* **emvco**: Resolve by raw EMVCo QR code data
* **emvco\_b64**: Resolve by base64-encoded EMVCo QR code data
The response includes the resolved target information including creditor details and account information.
Send `expected_creditor` to resolve only when the key belongs to the person you expect: on a
document mismatch the request fails with `target_creditor_mismatch` and no target is returned.
# Get outgoing transfer by ID (/es/docs/api-reference/breb-participant/outgoing-transfers/get_an_outgoing_transfer_by_id)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves a single outgoing transfer by its ID
# List outgoing transfers (/es/docs/api-reference/breb-participant/outgoing-transfers/list_outgoing_transfers)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves a paginated list of outgoing transfers for the authenticated tenant.
**Filtering options:**
* `state`: Filter transfers by their current state (e.g., created, processing, successful, failed)
* `transaction_at`: Filter transfers by creation datetime range in UTC using `from` and `until` datetime parameters
* `tenant_account_id`: Filter transfers by the source tenant account
* `external_id`: Filter transfers by their external identifier
* `key_type`: Filter transfers by the target's key type (e.g., identification, cellphone, email)
* `key_value`: Filter transfers by the target's key value
* `metadata`: Filter transfers by metadata key-value pairs
**Pagination:**
Use `page_number` and `page_size` parameters to control pagination. Results are ordered by creation date in descending order (newest first).
# Create outgoing transfer batch (/es/docs/api-reference/breb-participant/outgoing-transfers/create_outgoing_transfers)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a batch of outgoing transfers to be processed by the Bre-B system.
To create a batch of outgoing transfers, you must provide either a target or a query
for each transfer.
If a transfer has a validation error, it will be returned as a rejected transfer without
affecting the other transfers or the batch processing.
# Programs (/es/docs/api-reference/core/programs)
A program is the configuration your ledger accounts run under: the currencies it operates and the features it enables. Each ledger account reports its program as `program_id`.
## Endpoints [#endpoints]
Lists programs
# Lists programs (/es/docs/api-reference/core/programs/list_programs)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists your programs, oldest first. The order is deterministic — `id` breaks
ties between programs created at the same instant — so paging through a list
that does not change between requests never repeats or skips a program.
Filters combine with **AND**: `features` and `supported_currencies` each
require a program to satisfy every value passed, and the per-field text
filters narrow further. The one exception is `search`, which matches `name`
**or** `description`, and still ANDs against everything else. Filtering does
not go below the feature level — there is no filter by method or by
configuration value.
A filter that matches no program returns an empty page, not an error.
# Outgoing Transfer Batch Webhooks (/es/docs/api-reference/breb-participant/outgoing-transfer-batch-webhooks)
Receive notifications when batch transfer status changes.
## Events [#events]
Outgoing transfer batch created
Outgoing transfer batch processing
Outgoing transfer batch processed
# Outgoing transfer batch created (/es/docs/api-reference/breb-participant/outgoing-transfer-batch-webhooks/webhook_outgoing_transfer_batch_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a new outgoing transfer batch is created.
This event is sent immediately after the batch is accepted and all transfers are validated.
# Outgoing transfer batch processing (/es/docs/api-reference/breb-participant/outgoing-transfer-batch-webhooks/webhook_outgoing_transfer_batch_processing)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer batch starts processing.
This event indicates that the batch has entered the processing state.
# Outgoing transfer batch processed (/es/docs/api-reference/breb-participant/outgoing-transfer-batch-webhooks/webhook_outgoing_transfer_batch_processed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer batch finishes processing.
This event indicates that all transfers in the batch have reached a terminal state (successful or failed).
# Payins (/es/docs/api-reference/core/payins)
Endpoints for collecting money into your accounts. A template is a reusable configuration
that defines how payments are collected, an intent is a single expected payment declared
before the funds arrive, and a payin is the incoming payment itself. A settlement batch is
the movement that delivers the funds of many payins to your funding account.
## Templates [#templates]
A template is tied to a subaccount and defines the accepted payment methods, amount limits,
expected payers and expiration for the payins collected against it.
Lists templates
Creates a template
Gets a template
Updates a template
Deletes a template
## Intents [#intents]
An intent is a single expected payment declared before the funds arrive. It is created
against a template and tracked until it is paid, fails, or expires.
Lists intents
Creates an intent
Gets an intent
Finds the successful intent for an external\_id
## Payins [#payins]
A payin is a payment received from a payer against one of your templates.
Lists payins
Gets a payin
## Settlement batches [#settlement-batches]
A settlement batch is the single money movement that delivers the funds of many payins to
your funding account.
Lists settlement batches
Gets a settlement batch
## Brands [#brands]
A brand is what the hosted checkout wears: the merchant name, logo, and color a payer sees when paying one of your templates. Every template wears a brand.
Lists brands
Gets a brand
## Sandbox [#sandbox]
These endpoints are only available in the sandbox environment. Use them to simulate a payer
paying a template or an intent, and to provoke the failures you need to test.
Simulates a payment (sandbox)
# Lists payins (/es/docs/api-reference/core/payins/list_payins)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the requesting client's payins, paginated. Results are ordered by
creation date descending (newest first) unless `sort` is provided, and can
be narrowed with the filters below.
Every payin is listed, whether or not its payment completed: a payment that
has not reached the bank yet (`created`, `awaiting_payment`), one the payer
abandoned and one that was rejected all show up next to the approved ones.
Read `state` — or filter by it — to tell them apart.
# Updates a template (/es/docs/api-reference/core/payins/update_template)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Updates specific fields of a payin template. Scoped to the requesting client.
The update is presence-aware: an omitted field keeps its current value and an
explicit null clears a nullable field. `payin_data`, `form_customization` and
`method_params` are partial objects merged field by field; `expected_payers`
replaces the whole list.
Expected payers can only be set on a template one of whose methods validates
them: on a template that only offers `co_pse` the request fails with
`payin_expected_payers_not_supported`.
`method_params` only takes `co_pse`, whose `payer` is the suggested payer the
hosted checkout pre-fills: a complete payer replaces it, null removes it and an
omitted payer keeps it. On a template that does not offer `co_pse` the request
fails with `payin_unsupported_method_params`.
A template cannot be updated while still `created` (its Bre-B setup may be in
flight), in `discarded` or `failed` state, as a consumed (`paid`) single-use
template, or while being pruned (`prune_state` not null). The update is
serialized with payin processing through the template lock.
Updating `total_minimum_amount`/`total_maximum_amount` may transition the
template state (`ready` ⇄ `minimum_paid`, → `paid`, or back out of `paid`).
The transition is included in the `payin_template_updated` webhook — which every
successful update emits, with the previous values of the changed fields — and
does NOT trigger the separate state-change webhooks.
# Lists brands (/es/docs/api-reference/core/payins/list_payin_brands)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the requesting client's payin brands, paginated. The default brand comes
first and the rest follow by name. Deleted brands are not returned.
Each brand carries `templates_count`, how many of the client's live templates
wear it — the same reference that keeps a brand from being deleted.
# Lists templates (/es/docs/api-reference/core/payins/list_templates)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the requesting client's payin templates, paginated. Results are ordered
by creation date descending (newest first) unless `sort` is provided, and can
be narrowed with the filters below. Deleted templates are not returned.
# Simulates a payment (sandbox) (/es/docs/api-reference/core/payins/simulate_payin_payment)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Simulates a payer transferring to one of your templates or intents, running
the provider's real rail: the payin is created, authorized and funded
exactly as a production payment would be, and its outcome reaches you over
the same webhooks.
**Sandbox only.** In any other environment the endpoint answers `403`.
A payment to a template is *unreferenced* — the template accepts many, so
the request brings its own `method` and `amount`. A payment to an intent is
*referenced* — the intent was declared for it and already fixes both.
Use `error` to provoke a failure. Leaving it out simulates a payment that
succeeds, though the payment authorization may still refuse it on its own
validations — an amount outside the template's bounds, a payer it does not
expect. That refusal happens before a payin exists, so the response comes
back with no `payin_id` and the reason instead. Provoking those rejections
is exactly how you test them.
# Gets a brand (/es/docs/api-reference/core/payins/get_payin_brand)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets a payin brand by its ID. Scoped to the requesting client.
# Lists intents (/es/docs/api-reference/core/payins/list_intents)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the client's intents, paginated and optionally filtered.
# Gets an intent (/es/docs/api-reference/core/payins/get_intent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns a single intent by its ID.
# Gets a settlement batch (/es/docs/api-reference/core/payins/get_settlement_batch)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets a settlement batch by its ID, along with the payins it settles and
what each one contributed. Scoped to the requesting client.
The response is the same shape the `settlement_batch_*` webhooks deliver.
# Creates a template (/es/docs/api-reference/core/payins/create_template)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a template: a reusable configuration, tied to a subaccount, that defines how
payins are collected (accepted methods, amount limits, expected payers and expiration).
The request is idempotent on `external_id` — repeating it returns the existing template
instead of creating a duplicate.
`subaccount_id` must reference an account of type `subaccount`.
# Finds the successful intent for an external_id (/es/docs/api-reference/core/payins/find_successful_intent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns the single intent in `successful` state whose `external_id` matches the
given tenant-side identifier, answering "was this operation paid?" without
storing Mono ids. At most one such intent exists per client.
# Gets a payin (/es/docs/api-reference/core/payins/get_payin)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets a payin by its ID. Scoped to the requesting client.
# Gets a template (/es/docs/api-reference/core/payins/get_template)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Gets a payin template by its ID. Scoped to the requesting client.
# Lists settlement batches (/es/docs/api-reference/core/payins/list_settlement_batches)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists the requesting client's settlement batches, paginated. Results are
ordered by creation date descending (newest first) unless `sort` is
provided, and can be narrowed with the filters below.
A row reports how many payins the batch settles; fetch the batch by its ID
to see them.
# Deletes a template (/es/docs/api-reference/core/payins/delete_template)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Soft-deletes a payin template: marks it as deleted so it stops accepting payins and
frees its `external_id` for reuse. Scoped to the requesting client.
A template can be deleted only when it holds no money — no successful payins
(`paid_amount` = 0) and none in flight (`pending_amount` = 0) — and is in a deletable
state: not `created`, `discarded` or `failed`, and not being pruned (`prune_state` is
null). The deletion is serialized with payin processing through the template lock.
# Creates an intent (/es/docs/api-reference/core/payins/create_intent)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates an intent: a single expected payment declared before the funds arrive,
configured from a template the client owns or created templateless against one
of its subaccounts (`template_id` and `subaccount_id` are mutually exclusive).
The request is idempotent on `external_id` — repeating it returns the existing
intent instead of creating a duplicate.
# Target Resolution Webhooks (/es/docs/api-reference/breb-participant/target-resolution-webhooks)
Receive notifications when target resolution status changes.
## Events [#events]
Target resolution created
Target resolution resolved
Target resolution failed
# Target resolution created (/es/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a new target resolution request is created.
This event is sent immediately after the target resolution request is accepted and persisted.
# Target resolution resolved (/es/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_resolved)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a target resolution request is successfully resolved.
This event includes the resolved target with creditor and account details. When the tenant has person data masking enabled (`mask_person_data`), the creditor's `full_name`, `document_number` and `creditor_account.number` are delivered masked (e.g. `Jua*** Pér***`, `*****789`, `*****4321`).
# Target resolution failed (/es/docs/api-reference/breb-participant/target-resolution-webhooks/webhook_target_resolution_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a target resolution request fails.
This event includes the state\_reason field explaining why the resolution failed. When the request carried an `expected_creditor` that the resolved creditor did not match, the reason is `target_creditor_mismatch` and no target was persisted.
# Spending Controls (/es/docs/api-reference/core/spending-controls)
Endpoints for managing spending controls, rules, and targets.
## Spending Controls [#spending-controls]
Gets all the Card spending controls
Creates a spending control
Get a spending control
Updates a spending control
Deletes a spending control
## Rules [#rules]
Add or update a rule to a spending control
Delete a rule of a spending control
## Targets [#targets]
List spending control's targets
Add target to a spending control
Remove targets to a spending control
# Gets all the Card spending controls (/es/docs/api-reference/core/spending-controls/list_spending_controls)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint get all the Card spending controls
# Delete a rule of a spending control (/es/docs/api-reference/core/spending-controls/delete_spending_control_rule)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to delete a rule to a spending control
# Add target to a spending control (/es/docs/api-reference/core/spending-controls/add_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to add target to a spending control
# Updates a spending control (/es/docs/api-reference/core/spending-controls/update_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to update a spending control
# List spending control's targets (/es/docs/api-reference/core/spending-controls/list_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to list spending control's targets
# Deletes a spending control (/es/docs/api-reference/core/spending-controls/delete_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to delete a spending control
# Get a spending control (/es/docs/api-reference/core/spending-controls/get_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to get a spending control
# Creates a spending control (/es/docs/api-reference/core/spending-controls/create_spending_control)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to create spending controls
# Remove targets to a spending control (/es/docs/api-reference/core/spending-controls/remove_spending_control_targets)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to remove targets to a spending control
# Add or update a rule to a spending control (/es/docs/api-reference/core/spending-controls/add_or_update_spending_control_rule)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
You can use this endpoint to add or update a rule to a spending control
# Collections (/es/docs/api-reference/breb-participant/collections)
Endpoints for creating collections to receive payments through the Bre-B network.
## Endpoints [#endpoints]
### Collections [#collections]
Create collections
List collections
Get collection by ID
Find successful collection
Update a collection
Delete a collection
#### Attempts [#attempts]
List all collection attempts
List collection attempts by collection
Get collection attempt by ID
#### QRs [#qrs]
Create QR code
List QR codes
Get QR code
Cancel QR code
# Delete a collection (/es/docs/api-reference/breb-participant/collections/delete_collection)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Deletes a collection by marking it as deleted and scheduling key cancellation.
**Restrictions:**
* A collection can only be deleted if it has no successful payments (`paid_amount` = 0)
* Cannot delete collections in `discarded` or `failed` states
**Behavior:**
* If `paid_amount` > 0, the deletion is rejected with `has_payments` error
* If `paid_amount` = 0, the collection is marked as deleted and the associated
Bre-B key will be canceled asynchronously
* A `collection.discarded` webhook will be sent when the key cancellation completes
# Cancel QR code (/es/docs/api-reference/breb-participant/collections/delete_qr)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Cancels a dynamic QR code. Only dynamic (single\_use) QR codes can be cancelled.
Static (multiple\_use) QR codes cannot be cancelled. A QR code that was already paid,
expired or rejected cannot be cancelled either.
# Get a collection by ID (/es/docs/api-reference/breb-participant/collections/get_collection)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves a specific collection by its ID.
# List QR codes (/es/docs/api-reference/breb-participant/collections/list_qr_codes)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns all QR codes associated with a collection.
Results are ordered by creation date (newest first).
# List collection attempts by a given collection ID (/es/docs/api-reference/breb-participant/collections/list_collection_attempts_by_collection)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists collection attempts for a specific collection with pagination and filtering.
# Create QR code (/es/docs/api-reference/breb-participant/collections/create_qr)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates a QR code for a collection. The collection must be in `ready` or `minimum_paid` state.
The QR usage is determined by the `usage_mode` parameter:
* `single_use`: Creates a dynamic QR (requires amount, has expiration)
* `multiple_use`: Creates a static QR (amount is optional)
When an amount is provided, it must comply with the collection's rules:
* Must be within `minimum_attempt_amount` and `maximum_attempt_amount` range
* Must not exceed remaining balance (`total_maximum_amount - paid_amount`)
# Create collections (/es/docs/api-reference/breb-participant/collections/create_collections)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Creates one or more collections for receiving payments through Bre-B network.
Each collection will be created in `created` state and a Bre-B key will be
requested from the provider. Once the key is registered, a webhook notification
will be sent and the collection state will change to `ready`.
The response categorizes collections into:
* `created`: Successfully created collections
* `duplicated`: Collections with external\_id that already exist (returns existing collection)
* `rejected`: Collections that failed validation
A collection carrying `authorization` requires the collection authorization
feature to be enabled for the tenant; without it that collection is rejected
with `authorization_feature_not_enabled`.
# List collections (/es/docs/api-reference/breb-participant/collections/list_collections)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists collections with pagination and filtering.
# List all collection attempts (/es/docs/api-reference/breb-participant/collections/list_all_collection_attempts)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Lists collection attempts with pagination and filtering.
# Find successful collection (/es/docs/api-reference/breb-participant/collections/find_successful_collection)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Find a collection by external\_id.
# Update a collection (/es/docs/api-reference/breb-participant/collections/update_collection)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Updates specific fields of a collection.
**Restrictions:**
* Cannot update collections in `discarded` or `failed` states or with `prune_status` not null
* Only the following fields can be updated:
* `nickname`
* `enabled`
* `expires_at` (must be in the future)
* `minimum_attempt_amount` (only for multiple\_use)
* `maximum_attempt_amount` (only for multiple\_use)
* `total_minimum_amount` (may trigger state change)
* `total_maximum_amount` (must be >= current `paid_amount`, may trigger state change)
* `expected_payers` (the list sent replaces the current one as a whole; an empty
list or `null` accepts any payer and an absent key leaves it untouched)
* `authorization` (the whole block is replaced, never merged; `null` disables it
and an absent key leaves it untouched)
**Authorization:**
* Setting `authorization` requires the collection authorization feature to be
enabled for the tenant, and is otherwise refused with `authorization_feature_not_enabled`
* `authorization: null` is always accepted, so configuration can always be removed
* While the feature is off the stored configuration is left untouched
**State changes for `total_minimum_amount`:**
* If `paid_amount` >= new `total_minimum_amount`, state changes to `minimum_paid` (if currently `ready`)
* If `paid_amount` \< new `total_minimum_amount`, state changes to `ready` (if currently `minimum_paid`)
**State changes for `total_maximum_amount`:**
* If `ready` or `minimum_paid`, and new `total_maximum_amount` == `paid_amount`, state changes to `paid`
* If `paid` and new `total_maximum_amount` is null, state changes to `ready` or `minimum_paid`
(depending on whether `total_minimum_amount` is set and reached)
* If `paid` and new `total_maximum_amount` > `paid_amount`, state changes to `ready` or
`minimum_paid` (depending on whether `total_minimum_amount` is set and reached)
**Webhooks:**
* A `collection.updated` webhook is sent after the update
* If a state change occurs due to the update, it is included in the `collection.updated` webhook
but does NOT trigger separate state change webhooks
# Get QR code (/es/docs/api-reference/breb-participant/collections/get_qr)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Returns a specific QR code by ID.
# Get a collection attempt by ID (/es/docs/api-reference/breb-participant/collections/get_collection_attempt_by_id)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieves a specific collection attempt by its ID.
# Authentication (/es/docs/api-reference/core/authentication)
Endpoints for the OAuth 2.0 token lifecycle of Core partners: exchange your client credentials for an access token, check whether a token is still active, and revoke it.
The scopes a Core token can carry, and what each one grants, are listed in the [Core authentication guide](/docs/guides/core/authentication#scopes).
## Endpoints [#endpoints]
Obtain access token
Introspect token
Revoke token
# Revoke token (/es/docs/api-reference/core/authentication/core_revoke_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token revocation endpoint (RFC 7009).
Use this endpoint to revoke an access token or refresh token. After revocation,
the token will no longer be valid for authentication or authorization.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* Returns 200 OK with empty body on successful revocation
* The server responds with 200 OK even if the token was already invalid or expired
# Obtain access token (/es/docs/api-reference/core/authentication/core_get_auth_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token endpoint supporting client credentials and refresh token grant types.
Use this endpoint to obtain an access token that can be used to authenticate
requests to other API endpoints. The token should be included in the
Authorization header as a Bearer token.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Grant Types:**
* **client\_credentials**: Use client ID and secret to obtain an access token
* **refresh\_token**: Use a refresh token to obtain a new access token. A refresh token works
once: the response carries the one to use next, and the one sent stops working.
# Introspect token (/es/docs/api-reference/core/authentication/core_introspect_token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
OAuth 2.0 token introspection endpoint (RFC 7662).
Use this endpoint to determine the active state and metadata of an access token or refresh token.
This is useful for resource servers to validate tokens and retrieve their associated information.
**Supported Content Types:**
* `application/x-www-form-urlencoded` (standard OAuth 2.0)
* `application/json`
**Response:**
* If the token is active, returns token metadata including client\_id, scope, expiration, etc.
* If the token is inactive, expired, or invalid, returns `{"active": false}`
# Outgoing Transfer Webhooks (/es/docs/api-reference/breb-participant/outgoing-transfer-webhooks)
Receive notifications when outgoing transfer status changes.
## Events [#events]
Outgoing transfer created
Outgoing transfer target resolved
Outgoing transfer processing
Outgoing transfer held
Outgoing transfer sent to BREB provider
Outgoing transfer successful
Outgoing transfer failed
# Outgoing transfer processing (/es/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_processing)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer starts processing.
# Outgoing transfer held (/es/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_held)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer is held for review or additional processing.
# Outgoing transfer created (/es/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_created)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a new outgoing transfer is created within a batch.
# Outgoing transfer target resolved (/es/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_target_resolved)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when the target for an outgoing transfer is successfully resolved.
This event includes the resolved target. When the tenant has person data masking enabled (`mask_person_data`), the creditor's `full_name`, `document_number` and `creditor_account.number` are delivered masked (e.g. `Jua*** Pér***`, `*****789`, `*****4321`).
# Outgoing transfer failed (/es/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_failed)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer fails.
This event includes the state\_reason field explaining why the transfer failed.
# Outgoing transfer sent to Bre-B provider (/es/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_send_to_breb_provider)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer is sent to the Bre-B provider for processing.
# Outgoing transfer successful (/es/docs/api-reference/breb-participant/outgoing-transfer-webhooks/webhook_outgoing_transfer_successful)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when an outgoing transfer completes successfully.
This is the terminal state indicating the transfer was processed successfully by the Bre-B provider. Even when the tenant has person data masking enabled (`mask_person_data`), this event delivers the creditor's `full_name` and `document_number` in full, so the tenant can reconcile who it paid; `creditor_account.number` stays masked. The same applies to `outgoing_transfer.reapplied`.
# Card Webhooks (/es/docs/api-reference/core/card-webhooks)
Receive notifications when card lifecycle events occur, including plastic card activation, card tokenization OTP requests, and 3DS transaction authentication.
## Events [#events]
Plastic card activation
Card tokenization OTP
3DS transaction OTP
# Card tokenization OTP (/es/docs/api-reference/core/card-webhooks/webhook_card_token_otp)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a tokenization OTP is requested for a card.
This occurs when a card is being added to a digital wallet (e.g., Apple Pay, Google Pay)
and an OTP verification is required to complete the tokenization process.
# 3DS transaction OTP (/es/docs/api-reference/core/card-webhooks/webhook_threeds_otp)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a 3-D Secure (3DS) OTP is requested for an online card transaction.
This occurs when a cardholder makes an online purchase that requires 3DS authentication
and a one-time password must be delivered to the cardholder to confirm the transaction.
# Plastic card activation (/es/docs/api-reference/core/card-webhooks/webhook_plastic_card_activation)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Webhook event triggered when a plastic card activation changes state.
This occurs during the card activation process when the physical card is linked to the system.
The `state` field indicates the result of the activation:
* `activated`: Card was successfully activated
* `failed`: Card activation failed (e.g., error retrieving card details from provider)
* `group_mismatch`: The card's affinity group does not match the expected configuration group
# Recaudos frente a otros productos (/es/docs/guides/core/concepts/payins/comparison)
Mono tiene tres productos que reciben dinero, y elegir el equivocado cuesta una integración. No son niveles de lo mismo: cada uno deja el dinero en un tipo de cuenta distinto, y esa elección decide qué puedes construir encima. Esta página los compara para que sepas si Recaudos es lo que necesitas, y qué te da que los otros no.
Si ya sabes que vas a recaudar hacia subcuentas de Core, sáltate esta página y empieza por [Plantillas](/docs/guides/core/concepts/payins/templates).
## De un vistazo [#de-un-vistazo]
| | **Recaudos de Core** | [Enlaces de recaudo de Banking](/docs/guides/banking/concepts/collection-links) | [Recaudos de Bre-B Participant](/docs/guides/breb-participant/concepts/collections) |
| :------------------------------------ | :-------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------- |
| **Rieles** | Bre-B hoy; PSE y tarjetas en proceso | Solo PSE | Solo Bre-B |
| **Soporte multimoneda** | En proceso | Solo COP | Solo COP |
| **Acreditación en cuentas de ledger** | Sí | No se soporta | No se soporta |
| **Cobros a subcuentas** | Sí, [configurables](/docs/guides/core/concepts/payins/fees) a tu necesidad | No | No |
| **Cómo llega a tu banco** | En [lotes de liquidación](/docs/guides/core/concepts/payins/settlement/funding) hacia tu cuenta de fondeo | Depende de los ciclos ACH | De inmediato, porque Bre-B es un sistema de pagos inmediatos |
| **Integración API-only** | Sí | Sí | Sí |
| **Checkout hospedado por Mono** | [Sí](/docs/guides/core/concepts/payins/templates#checkout-hospedado-o-api-only) | Sí | No |
| **El producto además hace** | Ledger, tarjetas, controles de gasto, dispersiones | Transferencias ACH / Transfiya / Turbo, tarjetas | Transferencias Bre-B salientes, resolución de llaves en el directorio |
## Para qué sirve cada uno [#para-qué-sirve-cada-uno]
### Recaudos de Core [#recaudos-de-core]
Úsalo cuando tu producto **mantiene saldos de usuarios finales** y el dinero tiene que entrar a ellos. Es la mitad de recaudo de Core: cada pago se valida contra reglas que definiste de antemano, se acredita en una subcuenta del ledger de partida doble, se le cobran sus [cargos](/docs/guides/core/concepts/payins/fees) y se entrega en tu cuenta bancaria en [lotes de liquidación](/docs/guides/core/concepts/payins/settlement). Billeteras, saldos de vendedores en marketplaces y cualquier producto donde "¿de quién es este dinero?" tiene respuesta dentro de tu ledger.
Lo que te da y los otros no: el modelo de cuatro entidades ([plantillas](/docs/guides/core/concepts/payins/templates) que definen las reglas una vez, [intenciones de pago](/docs/guides/core/concepts/payins/intents) que identifican de antemano un pago esperado específico, [pagos](/docs/guides/core/concepts/payins/payins) con su ciclo de vida completo y lotes de liquidación), más sobreescritura de cargos por pago y una lista de pagadores permitidos.
### Enlaces de recaudo de Banking [#enlaces-de-recaudo-de-banking]
Úsalo cuando quieres que un cliente **te pague desde su cuenta bancaria por PSE**, y el dinero puede aterrizar en la cuenta de Banking de tu organización. Creas un link, lo compartes, y el pagador completa el pago en el formulario hospedado de Mono o en un frontend que construyas tú. El caso clásico de checkout de e-commerce en Colombia.
### Recaudos de Bre-B Participant [#recaudos-de-bre-b-participant]
Úsalo cuando tu producto se conecta a **Bre-B directamente como tenant participante** en Mono, y además necesitas la otra mitad de la red: despachar transferencias salientes y resolver llaves de pago en el directorio central. Los recaudos acreditan cuentas de tenant, y tú administras las llaves y los códigos QR.
## Elige según tu caso [#elige-según-tu-caso]
| Tu caso | Producto |
| :--------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------- |
| Tus usuarios finales recargan un saldo de billetera que tú les mantienes. | **Recaudos de Core** |
| Marketplace donde cada vendedor tiene un saldo que luego le dispersas. | **Recaudos de Core** (con dispersiones de Core) |
| Checkout de e-commerce pagado desde una cuenta bancaria colombiana por PSE. | [Enlaces de recaudo de Banking](/docs/guides/banking/concepts/collection-links) |
| Envías y recibes en Bre-B como participante, y administras llaves y el directorio. | [Bre-B Participant](/docs/guides/breb-participant/concepts/collections) |
| Necesitas que cada pago quede identificado antes de que llegue el dinero. | Las intenciones de pago de **Recaudos de Core**, o un link o QR por pago en los otros dos |
## Usar más de uno [#usar-más-de-uno]
Se componen. Una billetera de consumo, por ejemplo, puede mantener los saldos en el ledger de Core y recaudar por más de un riel. Ver el [mapeo de casos de uso](/docs/guides/why-mono) para las combinaciones que corren hoy los clientes de Mono. Lo que no conviene es partir el mismo saldo entre dos productos: primero decide dónde cae el dinero, y el resto se sigue de ahí.
## Siguientes pasos [#siguientes-pasos]
* [Conceptos de Recaudos](/docs/guides/core/concepts/payins): el modelo detrás de este producto.
* [Plantillas](/docs/guides/core/concepts/payins/templates): empieza aquí si Recaudos es lo tuyo.
* [Flujos de recaudos](/docs/guides/core/flows/payins): casos de uso de extremo a extremo.
# Cargos (/es/docs/guides/core/concepts/payins/fees)
Un **cargo** es lo que **tú** le cobras a tu propia subcuenta por un pago: un monto fijo, un porcentaje o ambos, descontado del dinero que se acredita cuando llega el pago. Es tu ingreso por el recaudo, no un costo que te imponen. Tú defines las cifras, Mono las aplica en el ledger automáticamente y reporta en `calculated_fees` lo cobrado en cada pago.
Defínelos una vez en tus políticas de fees y aplican a cada pago; sobreescríbelos por plantilla o por intención de pago cuando una campaña, un cliente o un pago puntual necesiten cifras distintas.
## El tipo de cargo [#el-tipo-de-cargo]
Recaudos tiene un solo tipo de cargo, `payin`: se cobra por recibir el pago y se aplica cuando el pago se acredita en la subcuenta. Cada `calculated_fees` que veas contiene solo este tipo.
## Dónde se configuran los cargos [#dónde-se-configuran-los-cargos]
Tus cargos por defecto son **políticas de fees** de tipo `payin`: reglas de precio que administras a través del API para todas las cuentas de tus usuarios o clientes, un programa, un propietario de cuenta o una sola subcuenta (un monto fijo y un porcentaje por moneda y, cuando las necesites, reglas que le pongan un precio distinto a un método o a un segmento). Si usas la funcionalidad de recaudos, Mono crea tu política base; desde ahí la ajustas y la sobreescribes tú mismo. Ver [Políticas de fees](/docs/guides/core/concepts/fees/policies).
## Sobreescribir cargos por plantilla o intención de pago [#sobreescribir-cargos-por-plantilla-o-intención-de-pago]
`payin_data.fee_override` permite que una plantilla, o una intención de pago individual, reemplace tu valor por defecto del programa para sus propios pagos:
```json
{
"payin_data": {
"fee_override": [
{
"type": "payin",
"fixed": { "amount": 50000, "currency": "COP" },
"percentage": "0.01",
"tx_description": "Cargo de recaudo: campaña premium"
}
]
}
}
```
Tres reglas gobiernan las sobreescrituras:
1. Todo monto `fixed` debe estar denominado en la moneda de la subcuenta de la plantilla, y ser `>= 0`.
2. La sobreescritura reemplaza el valor por defecto **como un todo** por tipo de cargo; no hay fusión parcial. La de la intención de pago gana sobre la de su plantilla.
3. Los valores quedan **congelados** en cada pago al crearse: cambiar después la sobreescritura de una plantilla no altera los cargos de pagos ya creados.
## Leer lo que se cobró [#leer-lo-que-se-cobró]
Cada pago reporta lo que cobró en `calculated_fees`, con el tipo de cargo como llave. Ese es el campo contra el que conciliar: `subaccount_amount` ya viene neto, así que el usuario final ve el monto después de tu cargo, y la diferencia es lo que te quedaste.
## Siguientes pasos [#siguientes-pasos]
* [Políticas de fees](/docs/guides/core/concepts/fees/policies): donde se definen los cargos por defecto, y para quién.
* [Pagos](/docs/guides/core/concepts/payins/payins): donde vive `calculated_fees`.
* [Crédito a la subcuenta](/docs/guides/core/concepts/payins/settlement/subaccount): el movimiento de ledger sobre el que van los cargos.
# Recaudos (/es/docs/guides/core/concepts/payins)
**Recaudos** es la forma en que el dinero entra a tus subcuentas de Core. Cubre tanto las **recargas** (un usuario final carga saldo a su propia billetera) como los **recaudos** propiamente dichos (tu negocio le cobra a un cliente). Tú defines las reglas de lo que esperas recibir, Mono le entrega al pagador algo a lo cual pagar (hoy una llave Bre-B o un código QR) y cada pago que llega se valida, se acredita en la subcuenta correcta y se entrega en tu cuenta bancaria, con un webhook en cada paso.
Mono tiene tres productos que reciben dinero, para tres modelos de cuenta distintos.
**Recaudos de Core** (esta sección) recauda hacia **subcuentas de Core**. Úsalo cuando tu
producto maneja saldos en el ledger de Core. Los [enlaces de
recaudo](/docs/guides/banking/concepts/collection-links) recaudan hacia **cuentas de Banking**
mediante links de pago PSE. Los [recaudos de Bre-B
Participant](/docs/guides/breb-participant/concepts/collections) reciben pagos Bre-B
directamente en **cuentas de tenant**. Ver [Recaudos frente a otros
productos](/docs/guides/core/concepts/payins/comparison) para la comparativa completa.
## Las cuatro entidades [#las-cuatro-entidades]
Un pago nunca viaja solo. Cuatro entidades, cada una con su propio ciclo de vida y webhooks, llevan un pago de "esperado" a "dinero en tu cuenta bancaria":
| Entidad | Qué es |
| :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Plantilla** | Una configuración reutilizable atada a una subcuenta. Define los métodos disponibles para recibir pagos, los límites de monto al recibir, los pagadores permitidos, la expiración y el checkout personalizado, entre otros. |
| **Intención de pago** | Un único pago esperado, declarado antes de que llegue el dinero. Úsala cuando cada pago debe identificarse de antemano. |
| **Pago** | El pago que efectivamente llega: quién pagó, cuánto, y en qué punto de su recorrido va el dinero. |
| **Lote de liquidación** | El movimiento único de dinero que entrega los fondos de muchos pagos en tu cuenta de fondeo. |
Dos conceptos de apoyo completan el mapa: los **tokens de pago** (aquello a lo que el pagador realmente paga) y los **cargos** (lo que tú le cobras a tus subcuentas por cada pago). Y dentro de cada pago viven tres sub-entidades propias (el **instrument**, el **recipient data** y el **transaction data**), explicadas en [Pagos](/docs/guides/core/concepts/payins/payins).
## Los cuatro montos [#los-cuatro-montos]
Cada pago lleva cuatro montos, uno por cada tramo del recorrido del dinero. Cada tramo tiene su propia moneda, así que el modelo soporta recaudar en una moneda y contabilizar en otra:
| Campo | Qué mide | Denominado en |
| :------------------ | :---------------------------------------------------------- | :------------------------------- |
| `amount` | Lo que pagó el pagador, según lo liquidó el método de pago. | La moneda del método |
| `merchant_amount` | El valor del pago de cara al comercio. | La moneda de la plantilla |
| `subaccount_amount` | Lo que se acredita en la subcuenta. | La moneda de la subcuenta |
| `funding_amount` | Lo que se entrega en tu cuenta de fondeo. | La moneda de la cuenta de fondeo |
**Hacia dónde va el multi-moneda.** Hoy el único método activo es
[Bre-B](/docs/guides/core/concepts/payins/methods/co-breb), que liquida en COP, así que los
cuatro montos normalmente coinciden. La meta del modelo de cuatro tramos es el recaudo
multi-moneda completo: recibir un pago en la moneda del método mientras el precio, el crédito
a la subcuenta y la liquidación de tu cuenta de fondeo van cada uno en su propia moneda, con
Mono encargándose de las conversiones entre tramos (a una tasa reservada de antemano o a la
tasa live de Mono). Cuando llegue el recaudo multi-moneda, aterriza sobre estos mismos cuatro
campos sin cambios que rompan tu integración.
## Métodos de pago [#métodos-de-pago]
Cada pago se recibe a través de un método de pago. Hoy hay un método disponible:
| Método | Riel | Moneda | El pagador paga con |
| :-------- | :----------------------------------------------------------------------------------------------------- | :----- | :----------------------------- |
| `co_breb` | [Bre-B](/docs/guides/core/concepts/payins/methods/co-breb), el sistema de pagos inmediatos de Colombia | COP | Una llave Bre-B o un código QR |
La estructura crece por método: cuando se lance un método nuevo, aparecerá en [Métodos](/docs/guides/core/concepts/payins/methods) con su propia página, y las plantillas lo aceptarán en `available_methods` sin ningún cambio en las entidades de arriba.
## Explora [#explora]
Cómo se compara con los enlaces de recaudo de Banking y los recaudos de Bre-B Participant.
La configuración reutilizable que define cómo se recaudan los pagos.
Un único pago esperado, declarado antes de que llegue el dinero.
El pago recibido: estados, montos, sub-entidades y razones de rechazo.
Aquello a lo que paga el pagador. Cada método emite el suyo; hoy, llaves Bre-B y códigos QR.
Dos movimientos: cuándo se acredita la subcuenta y cómo Mono te entrega los fondos.
El cargo sobre los pagos y cómo sobreescribirlo por plantilla.
Los métodos de pago con los que puedes recaudar, lado a lado.
Casos de uso de extremo a extremo: recargas, recaudo con llave estática, recaudo con
intenciones de pago, liquidación.
Los 22 eventos del ciclo de vida del pago y cuándo llega cada uno.
## Siguientes pasos [#siguientes-pasos]
* [Plantillas](/docs/guides/core/concepts/payins/templates): empieza aquí: todo comienza con una plantilla.
* [Flujos](/docs/guides/core/flows/payins): elige el flujo que corresponde a tu caso de negocio.
* [Referencia de API de recaudos](/docs/api-reference/core/payins): cada endpoint, petición y respuesta.
# Tokens de pago (/es/docs/guides/core/concepts/payins/payment-tokens)
Un **token de pago** es lo que pones frente al pagador: la dirección a la que se envía el dinero. Cada [método de pago](/docs/guides/core/concepts/payins/methods) define su propio tipo de token. Algunos métodos no necesitan ninguno, porque el pagador se envía al flujo propio del proveedor en vez de pagar a una dirección. Por eso `payment_tokens` es un mapa por método: una entrada por cada método aceptado que emite token, y ninguna para los métodos que no lo hacen.
Hoy, con Bre-B como único método activo, los tokens vienen en dos formas: una **llave** (un alias corto como un número de celular o un código, registrado en el directorio de Bre-B) y un **código QR** opcional que codifica el mismo destino para que el pagador escanee en vez de digitar.
Tanto las plantillas como las intenciones de pago llevan tokens, y la diferencia entre ambas es la diferencia entre un afiche en la pared y una boleta con tu nombre: el token de una plantilla lo comparten todos los pagadores; el token de una intención de pago identifica un único pago esperado.
## El token de Bre-B [#el-token-de-bre-b]
`payment_tokens.co_breb` tiene tres partes:
| Campo | Qué es |
| :---------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key_type` | El tipo de llave: `cellphone`, `email`, `alphanumeric`, `identification` o `merchant_code`. Lo asigna la red al registrar la llave. No se conoce al momento de la creación. |
| `key_value` | La llave en sí: lo que el pagador digita en la app de su banco para direccionar la transferencia. |
| `qr` | El QR generado para la llave, cuando se pidió uno (`create_qr: true`). |
El objeto del QR lleva todo lo necesario para mostrarlo o incrustarlo: el payload EMV en texto plano (`emvco`), la imagen renderizada en base64 (`image`, con `image_width` y `error_correction_level`), el `payment_id` del proveedor, su `usage_mode` y, para QRs dinámicos, el `amount` fijo y el `expires_at`.
Puedes pedir un alias personalizado de llave con `method_params.co_breb.custom_key_value`, y sobreescribir los datos de comercio del QR (`merchant_category_code`, `receiver_city`, `receiver_postal_code`) con `qr_code_data`. Si los omites, Mono genera la llave y usa la configuración de tu programa.
## Llave por plantilla o llave por intención de pago [#llave-por-plantilla-o-llave-por-intención-de-pago]
`method_params.co_breb.key_per` decide a qué nivel se crea la llave Bre-B, y eso define el modelo de recaudo completo:
| `key_per` | El token vive en | Tipo de QR | Los pagos son… |
| :----------------------- | :--------------------- | :---------------------------------------- | :------------------------------------------------------------------------ |
| `template` (por defecto) | La plantilla | Estático: multiuso, sin monto | Anónimos hasta que llegan: cualquier pagador, cualquier número de pagos. |
| `intent` | Cada intención de pago | Dinámico: un solo uso, monto fijo, expira | Referenciados: cada pago se mapea a la intención de pago que lo esperaba. |
Una plantilla con `key_per: "template"` recibe pagos directamente. No puede alojar intenciones de pago, porque una llave compartida no puede decir a qué intención de pago pertenece un pago. Una plantilla con `key_per: "intent"` **no tiene llave propia** (`payment_tokens` queda `null`): cada llave se crea cuando se crea una intención de pago. Una intención de pago sin plantilla también recibe su propia llave dedicada.
## El setup del token es asíncrono [#el-setup-del-token-es-asíncrono]
Registrar un token en la red del método implica una ida y vuelta con callback, y por eso las plantillas y las intenciones de pago nacen en `created`. Ninguna es pagable hasta que el token queda confirmado:
* La plantilla pasa a `ready` y llena `payment_tokens` cuando todos los tokens pendientes quedan confirmados (lo anuncia `payin_template_ready`).
* La intención de pago pasa a `ready_to_pay` y llena `payment_tokens` (lo anuncia `payin_intent_ready_to_pay`).
Si el registro agota sus reintentos, la plantilla termina en `failed` (`key_registration_failed`, `key_already_registered`) o la intención de pago termina en `failed` (`key_registration_failed`, `payment_token_registration_failed`).
## Los tokens se dan de baja [#los-tokens-se-dan-de-baja]
Un token solo vive mientras su dueño pueda ser pagado. Cuando una plantilla es [podada o eliminada](/docs/guides/core/concepts/payins/templates/pruning), o una intención de pago llega a un estado final (`successful`, `failed` o `expired`), Mono da de baja sus tokens ante el proveedor del método. Los campos `prune_state` / `prune_reason` del dueño rastrean ese desmonte.
## Siguientes pasos [#siguientes-pasos]
* [Bre-B](/docs/guides/core/concepts/payins/methods/co-breb): el riel detrás de las llaves y los QRs.
* [Plantillas](/docs/guides/core/concepts/payins/templates): donde viven los tokens estáticos.
* [Intenciones de pago](/docs/guides/core/concepts/payins/intents): donde viven los tokens dinámicos.
# Políticas de fees (/es/docs/guides/core/concepts/fees/policies)
Una **política de fees** es la regla de precio detrás de un fee: dice cuánto cobrar por un tipo de fee y a quién le aplica ese precio. Mantienes un precio general para todas las cuentas de tus usuarios o clientes y lo sobrescribes donde lo necesites — un programa con su propio plan, un cliente empresarial con términos negociados, una sola cuenta que quieres exonerar. Cuando se cobra un fee, Mono elige la política más específica que cubre la cuenta y la aplica.
Las políticas evitan tener que pedirle a Mono cada cambio de precios. Las creas, ajustas, omites (pausas) y eliminas a través del API, y siempre puedes consultar con qué política termina una cuenta — y por qué.
Las políticas solo le ponen precio a los fees que **tú** le cobras a tus usuarios. Los fees
que Mono te cobra siguen tus términos comerciales y son visibles, pero no editables, en el
[listado de fees](/docs/api-reference/core/fees/list_fees).
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una API key de Core con el scope `fees` (`fees:readonly` basta para leer políticas y fees).
* Tener habilitadas las políticas de fees de Core para ti como tenant. Mono las habilita y crea tus **políticas base** — una por cada tipo de fee en uso — durante el onboarding.
* Conocer tus programas, propietarios de cuenta y cuentas por sus identificadores (`pro_`, `acch_`, `lacc_`): son a quienes puede apuntar una política (su target).
## Anatomía de una política [#anatomía-de-una-política]
| Campo | Qué es |
| :-------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fee_type` | El tipo de fee al que le pone precio: `bank_transfer`, `payin`, `account_to_account_fx`… Ver [Tipos de fee](/docs/guides/core/concepts/fees#tipos-de-fee). |
| `target_type` + `target_id` | A quién aplica (el target): todas las cuentas de tus usuarios o clientes (`tenant`), un `program`, un `account_holder` o un `account`, y cuál. |
| `strategies` | El precio, por moneda: las reglas a evaluar y el cálculo al que recurrir si ninguna aplica. Ver [Cómo se calcula el precio](#cómo-se-calcula-el-precio). |
| `processing_type`, `processing_subtype` | Cómo aterriza el fee en el ledger, entre las opciones que permite el tipo de fee. Ver [Tipos de fee y su procesamiento](#tipos-de-fee-y-su-procesamiento). |
| `summary`, `transaction_description` | Una descripción para ti, y la descripción que ven tus usuarios en la transacción del fee. |
| `skip` | Omite (pausa) la política sin borrarla: Mono la ignora y aplica la siguiente menos específica. |
## A quién aplica una política [#a-quién-aplica-una-política]
Una política apunta a uno de cuatro niveles (su target). De menos a más específico:
1. **`tenant`** — la política base o global. Cubre todas las cuentas de tus usuarios o clientes y es el precio de último recurso.
2. **`program`** — todas las cuentas de un [programa](/docs/guides/core/concepts/program).
3. **`account_holder`** — todas las cuentas de un mismo propietario de cuenta: uno de tus usuarios o clientes.
4. **`account`** — una sola subcuenta.
Cuando se cobra un fee en una cuenta, Mono mira las políticas de ese tipo de fee que cubren la cuenta — la suya, la de su propietario, la de su programa y la base o global (`tenant`) — y aplica **la más específica que no esté omitida o pausada**:
```
tenant la política base o global — siempre existe, precio por defecto
└── program la sobrescribe para todas las cuentas del programa
└── account_holder sobrescribe ambas para todas las cuentas del propietario
└── account sobrescribe todo, solo para esta cuenta
```
Tres reglas completan el cuadro:
* **Solo una política activa por tipo de fee y a quién apunta (target).** Para cambiar el precio de un programa, un propietario o una cuenta que ya tiene política, actualízala; crear una segunda se rechaza.
* **La política base siempre existe.** Mono crea tus políticas base o globales (`tenant`). Puedes actualizarlas, pero no crearlas, omitirlas (pausarlas) ni borrarlas — garantizan que todo fee tenga precio.
* **Una política solo cubre las monedas que incluye.** Si la moneda de una cuenta no está entre las `strategies` de la política, esa política no le aplica y aplica la siguiente menos específica.
### Qué monedas debe cubrir una política [#qué-monedas-debe-cubrir-una-política]
Las monedas de `strategies` deben mantenerse dentro de las que soporta aquello a lo que apunta la política (su target):
| A quién apunta (target) | Monedas que pueden cubrir las strategies |
| :---------------------- | :-------------------------------------------------------------------------------- |
| `tenant` | Exactamente todas las monedas que soportan tus programas — ni más ni menos. |
| `program` | Cualquier subconjunto de las monedas del programa. |
| `account_holder` | Cualquier subconjunto de las monedas de las cuentas del propietario de la cuenta. |
| `account` | Exactamente la moneda de la cuenta. |
Una política de `program` o `account_holder` puede entonces ponerle precio a una sola moneda y dejarle las demás a la política base — útil cuando un plan difiere en COP pero no en USD.
### Ver qué política aplica [#ver-qué-política-aplica]
[`GET /v1/ledger/accounts/{account_id}/fees/policies`](/docs/api-reference/core/fees/list_account_fee_policies) lista, para una subcuenta, todas las políticas que la cubren, agrupadas por tipo de fee y ordenadas de la más a la menos específica, con `applied: true` en la que gana. Las políticas omitidas o pausadas aparecen con `applied: false`, así puedes ver por qué una política no está teniendo efecto. Úsalo después de crear u omitir una política para confirmar el resultado.
## Cómo se calcula el precio [#cómo-se-calcula-el-precio]
Cada moneda en `strategies` tiene su propia estrategia (**strategy**): una lista ordenada de reglas (`rules`) y un cálculo por defecto (`default_calculation`). Mono evalúa las reglas en orden y usa el cálculo de la primera cuyas condiciones se cumplan todas; si ninguna aplica, usa el cálculo por defecto. Una estrategia sin reglas siempre usa el cálculo por defecto, que es la forma más simple de definir un precio plano.
### Cálculo [#cálculo]
Un **cálculo** es un monto fijo más un porcentaje del monto de la operación:
| Campo | Significado |
| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------- |
| `fixed` | La parte fija, en la unidad mínima de la moneda. `900000` en COP son 9.000,00. |
| `percentage` | La parte variable, como fracción decimal enviada en string. `"0.004"` es 0.4%. |
| `percentage_mode` | `open` (por defecto): sin límite. `capped`: la parte variable nunca supera `percentage_limit`. `floored`: nunca está por debajo de él. |
| `percentage_limit` | El tope o el piso, en la unidad mínima. Obligatorio para `capped` y `floored`, ausente para `open`. |
Ambas partes son siempre explícitas — envía `0` y `"0"` en las que no cobras — para que ningún fee quede en cero por omisión.
Por ejemplo, en una dispersión de 2.000.000,00 COP, un cálculo con `fixed: 90000`, `percentage: "0.004"`, `percentage_mode: "capped"` y `percentage_limit: 500000` cobra 900,00 fijos más 8.000,00 variables (0.4%); con una dispersión mayor, la parte variable dejaría de crecer en 5.000,00.
### Reglas y condiciones [#reglas-y-condiciones]
Una **regla** aplica su cálculo cuando todas sus **condiciones** se cumplen. Las condiciones comparan una propiedad de la operación — su método, su monto, su moneda, un campo de metadata de la cuenta — contra un valor u otra propiedad. Así le pones un precio distinto a un riel de dispersión, das un descuento por encima de un monto o exoneras a las cuentas marcadas de cierta forma. La lista completa de operadores y de las propiedades que puede mirar cada tipo de fee está en [Condiciones](/docs/guides/core/concepts/fees/conditions).
### Tipos de fee y su procesamiento [#tipos-de-fee-y-su-procesamiento]
Cada tipo de fee permite valores específicos de `processing_type` y `processing_subtype` para los fees que cobras:
| Tipo de fee | `processing_type` | `processing_subtype` | Notas |
| :----------------------------- | :---------------- | :------------------- | :------------------------------------------------------- |
| `bank_transfer` | `inline` | `add_posting` | El fee va dentro de la transacción de la dispersión. |
| `bank_transfer_gmf` | `inline` | `add_posting` | El traslado del GMF, con precio porcentual. |
| `payin` | `inline` | `add_posting` | Se descuenta de lo que se acredita a la subcuenta. |
| `account_to_account_fx` | `inline` | `add_posting` | El cobro fijo por la conversión, en la moneda de origen. |
| `account_to_account_fx_spread` | `inline` | `upcharge` | El margen, incluido en la tasa de cambio. |
## Administrar políticas [#administrar-políticas]
| Acción | Endpoint |
| :------------------------------------- | :-------------------------------------------------------------------------------------------------------------- |
| Crear una o varias políticas | [`POST /v1/core/fees/policies`](/docs/api-reference/core/fees/create_fee_policies) |
| Listar tus políticas | [`GET /v1/core/fees/policies`](/docs/api-reference/core/fees/list_fee_policies) |
| Consultar una política | [`GET /v1/core/fees/policies/{id}`](/docs/api-reference/core/fees/get_fee_policy) |
| Actualizar una política | [`PATCH /v1/core/fees/policies/{id}`](/docs/api-reference/core/fees/update_fee_policy) |
| Eliminar una política | [`DELETE /v1/core/fees/policies/{id}`](/docs/api-reference/core/fees/delete_fee_policy) |
| Ver qué políticas aplican a una cuenta | [`GET /v1/ledger/accounts/{account_id}/fees/policies`](/docs/api-reference/core/fees/list_account_fee_policies) |
Algunas cosas a saber de cada operación:
* **Crear.** Envía las políticas bajo `policies`, siempre una lista (hasta 50). El lote es atómico: si una política se rechaza, no se crea ninguna, y el `path` del error apunta al ítem y campo que falló — `/policies/1/strategies/COP` se lee "la strategy en COP de la segunda política". Una política nueva nunca nace omitida (pausada).
* **Actualizar.** Solo cambian los campos que envías. `strategies` se reemplaza completo, así que envía todas las monedas que la política debe conservar; quitar una moneda es enviar el mapa sin ella. `fee_type`, `target_type` y `target_id` son inmutables — para mover una política, elimínala y crea una nueva. Los cambios aplican a los fees cobrados de ahí en adelante; los ya cobrados conservan su precio.
* **Omitir o pausar.** `skip: true` omite (pausa) una política: la siguiente menos específica toma el relevo hasta que vuelvas `skip` a `false`. Las políticas base no se pueden omitir.
* **Eliminar.** La política deja de aplicar y desaparece de los listados, y aquello a lo que apuntaba (su target) y su tipo de fee quedan libres para una política nueva. Las políticas base no se pueden eliminar. Los fees ya cobrados siguen referenciando la política eliminada.
### Ejemplo: un precio especial para un programa [#ejemplo-un-precio-especial-para-un-programa]
Un programa de usuarios premium paga un fee de dispersión más bajo, y nada por las dispersiones ACH:
```json
{
"policies": [
{
"fee_type": "bank_transfer",
"summary": "Programa premium — fee de transferencia bancaria",
"transaction_description": "Fee de transferencia",
"target_type": "program",
"target_id": "pro_3rbnHF9AtEBSyyfqZAmnii",
"processing_type": "inline",
"processing_subtype": "add_posting",
"strategies": {
"COP": {
"rules": [
{
"summary": "Las dispersiones ACH son gratis",
"conditions": [
{
"operator": "equal",
"left": { "property": "method" },
"right": { "value": "co_ach" }
}
],
"calculation": { "fixed": 0, "percentage": "0" }
}
],
"default_calculation": { "fixed": 300000, "percentage": "0" }
}
}
}
]
}
```
De ahí en adelante, cada dispersión en COP desde una cuenta de ese programa cuesta 3.000,00 — salvo las ACH, que no cuestan nada — mientras las cuentas de tus otros programas conservan el precio de tu política base.
## Errores [#errores]
Los errores de negocio traen un `error_code` y, cuando se refieren a un ítem de un lote, un `path` que lo ubica:
| Código | HTTP | Cuándo |
| :------------------------------------------ | :--- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `fee_policy_not_found` | 404 | La política no existe, no es tuya o fue eliminada. |
| `fee_policy_already_exists` | 409 | Aquello a lo que apunta (el target) ya tiene una política activa para ese tipo de fee. Actualízala en su lugar. |
| `fee_policies_duplicated` | 422 | Dos políticas del lote apuntan a lo mismo (target) para el mismo tipo de fee. |
| `tenant_fee_policy_cannot_be_created` | 422 | `target_type: tenant` no se permite al crear — las políticas base las configura Mono. |
| `fee_policy_target_not_found` | 422 | El `target_id` no existe o no te pertenece. |
| `fee_policy_unsupported_processing_type` | 422 | El `processing_type` no está entre los que permite el tipo de fee. |
| `fee_policy_unsupported_processing_subtype` | 422 | El `processing_subtype` no está entre los que permite el tipo de fee para ese tipo de procesamiento. |
| `fee_policy_invalid_strategy_currencies` | 422 | Las monedas de `strategies` no coinciden con lo que soporta aquello a lo que apunta la política (su target). |
| `fee_policy_invalid_strategy` | 422 | Una regla, condición o cálculo es inválido para el tipo de fee — una propiedad desconocida, operandos incompatibles, un límite de porcentaje que no coincide con su modo. |
| `tenant_fee_policy_cannot_be_skipped` | 422 | `skip: true` en una política base. |
| `tenant_fee_policy_cannot_be_deleted` | 422 | Eliminar una política base. |
| `fee_policy_target_type_required` | 422 | Listar con `target_id` pero sin `target_type`. |
## Siguientes pasos [#siguientes-pasos]
* [Condiciones](/docs/guides/core/concepts/fees/conditions) — todos los operadores y propiedades disponibles para tus reglas.
* [Fees](/docs/guides/core/concepts/fees) — qué pasa una vez una política le puso precio a un fee.
* [Referencia de API de fees](/docs/api-reference/core/fees) — los payloads completos de request y response.
# Fees (/es/docs/guides/core/concepts/fees)
Un **fee** es un cobro asociado a un movimiento de dinero: una dispersión, un pago o una transferencia entre subcuentas. Algunos fees son tuyos — el precio que le cobras a tus propios usuarios o clientes por usar tu producto, debitado de sus subcuentas y acreditado a ti. Otros son de Mono — el precio por usar la plataforma, debitado de tus propias cuentas. Cada fee queda registrado junto a la operación que lo originó, con la regla que le puso precio y el monto exacto, así que siempre puedes conciliar qué se cobró y por qué.
Cuánto cuesta un fee no está fijo en el sistema: lo decide una **política de fees**, una regla de precios que administras por tipo de fee y por grupo de cuentas. Esta página explica los fees en sí; [Políticas de fees](/docs/guides/core/concepts/fees/policies) explica cómo defines los precios.
## Dos lados: tus fees y los fees de Mono [#dos-lados-tus-fees-y-los-fees-de-mono]
| | Fees que tú cobras (`owner: tenant`) | Fees que Mono te cobra (`owner: mono`) |
| :------------------------- | :------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------- |
| **Quién paga** | Tus usuarios o clientes, desde sus subcuentas | Tú, desde tus propias cuentas |
| **Quién recibe** | Tú | Mono |
| **Quién define el precio** | Tú, a través de tus políticas de fees | Mono, según lo acordado en tus términos comerciales |
| **Dónde los ves** | `calculated_fees` en la operación, y el [listado de fees](/docs/api-reference/core/fees/list_fees) | El [listado de fees](/docs/api-reference/core/fees/list_fees) con `owner=mono` |
Ambos lados usan los mismos tipos de fee y la misma mecánica. La única diferencia es quién le paga a quién.
## Tipos de fee [#tipos-de-fee]
Cada fee tiene un **tipo** que dice por qué se cobra. Los tipos disponibles hoy, y el servicio que origina cada uno:
| Tipo de fee | Servicio | Se cobra cuando | Lo cobra |
| :----------------------------- | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | :-------- |
| `bank_transfer` | Dispersiones | Se crea una dispersión a una cuenta bancaria. | Tú y Mono |
| `bank_transfer_gmf` | Dispersiones | Se crea una dispersión a una cuenta bancaria, para trasladar el GMF — el Gravamen a los Movimientos Financieros de Colombia, 0.4% (el "4 × 1000"). | Tú y Mono |
| `payin` | Recaudos | Un pago se acredita en la subcuenta. | Tú |
| `account_to_account_fx` | Transferencias de ledger | Se hace una transferencia entre dos subcuentas de monedas distintas. Un cobro fijo por la conversión. | Tú |
| `account_to_account_fx_spread` | Transferencias de ledger | La misma transferencia. Tu margen sobre la tasa de cambio, incluido en la tasa aplicada. | Tú |
El tipo de fee también fija cómo se puede cobrar — ver [Cómo se cobra un fee](#cómo-se-cobra-un-fee) — y qué propiedades de la operación pueden mirar sus reglas de precio — ver [Condiciones](/docs/guides/core/concepts/fees/conditions).
## Cómo se cobra un fee [#cómo-se-cobra-un-fee]
Cada fee lleva un `processing_type` y un `processing_subtype` que describen cuándo y cómo aterriza en el ledger:
| Procesamiento | Significado |
| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `inline` / `add_posting` | Se cobra como un movimiento propio dentro de la transacción de la operación. El caso más común: el fee aparece junto a la dispersión, el pago o la transferencia que lo causó. |
| `inline` / `upcharge` | Se suma encima del monto de la operación. Lo usa `account_to_account_fx_spread`, donde el margen va incluido en la tasa de cambio. |
| `inline` / `deduct` | Se descuenta del monto de la operación, así que quien recibe obtiene el monto menos el fee. |
| `inline` / `new_tx` | Se cobra de inmediato, en una transacción propia. |
| `deferred` / `new_tx` | Se cobra después, en una transacción propia. |
Cada tipo de fee soporta combinaciones específicas; la política de fees elige entre ellas. Ver la tabla de tipos en [Políticas de fees](/docs/guides/core/concepts/fees/policies#tipos-de-fee-y-su-procesamiento).
## Ciclo de vida de un fee [#ciclo-de-vida-de-un-fee]
Un fee sigue a la operación que lo originó:
| Estado | Significado |
| :--------- | :--------------------------------------------------------------------------- |
| `created` | Se le puso precio al crear la operación; aún no se ha cobrado nada. |
| `pending` | Esperando a que la operación avance. |
| `held` | El monto está reservado en la cuenta hasta que la operación se liquide. |
| `approved` | Cobrado. |
| `declined` | La operación falló antes del cobro; el fee nunca se cobró. |
| `reversed` | Cobrado y luego devuelto — por ejemplo porque la operación misma se reversó. |
## Dónde aparecen los fees [#dónde-aparecen-los-fees]
* **En la operación.** Las dispersiones, los pagos y las transferencias de ledger devuelven los fees que cobraron en `calculated_fees`, con el tipo de fee como llave y el monto de cada uno. Ese es el campo contra el que conciliar por operación.
* **En el listado de fees.** [`GET /v1/core/fees`](/docs/api-reference/core/fees/list_fees) lista todos los fees cobrados en tus cuentas — los tuyos y los de Mono — con la política y la regla que les puso precio, su estado y la operación que los originó. Filtra por owner, tipo, estado, cuenta, política, origen o rango de fechas.
* **Sobrescrituras.** Algunas operaciones permiten reemplazar el precio de la política solo para esa operación: `fee_override` en recaudos y `override_fees` en transferencias de ledger. El fee entonces reporta `overridden: true`, y `overridden_fields` dice qué enviaste.
## Montos [#montos]
Los montos de los fees, como todo monto en el API de Core, se expresan en la unidad mínima de la moneda: `900000` **centavos** en COP son 9.000,00 COP. Los porcentajes son fracciones decimales enviadas como string: `"0.004"` es 0.4%.
## Siguientes pasos [#siguientes-pasos]
* [Políticas de fees](/docs/guides/core/concepts/fees/policies) — cómo definir el precio de cada fee, y para quién.
* [Condiciones](/docs/guides/core/concepts/fees/conditions) — las propiedades y operadores que pueden usar tus reglas de precio.
* [Referencia de API de fees](/docs/api-reference/core/fees) — los endpoints para listar fees y administrar políticas.
# Condiciones (/es/docs/guides/core/concepts/fees/conditions)
Una **condición** es una pregunta de sí o no que una regla de fee le hace a la operación que se está cobrando: ¿esta dispersión va por ACH? ¿El monto supera un umbral? ¿La cuenta está marcada como VIP? Una regla aplica su precio solo cuando todas sus condiciones responden sí. Las condiciones son la forma en que una sola política de fees le pone precios distintos a situaciones distintas, en vez de necesitar una política por caso.
Cada condición compara un **operando** — una propiedad de la operación, o un valor constante que tú escribes — con otro, a través de un **operador**. Esta página es la referencia de los operadores disponibles y de las propiedades que puede mirar cada tipo de fee.
## Anatomía de una condición [#anatomía-de-una-condición]
```json
{
"operator": "greater_equal_than",
"left": { "property": "source_amount" },
"right": { "value": 100000000 },
"negated": false
}
```
| Campo | Significado |
| :--------- | :----------------------------------------------------------------------------------------------- |
| `operator` | La comparación a hacer. Ver [Operadores](#operadores). |
| `left` | El primer operando. Normalmente una propiedad de la operación. |
| `right` | El segundo operando. Lo exige todo operador salvo `is_null`. |
| `negated` | Invierte el resultado. `false` por defecto: un `equal` con `negated` significa "es distinto de". |
Un operando es una de dos cosas:
* **Una propiedad**: `{ "property": "method" }` — el valor se lee de la operación en el momento de cobrar el fee. Algunas propiedades reciben argumentos: `{ "property": "account.metadata", "args": { "name": "plan" } }`.
* **Una constante**: `{ "value": "co_ach" }` — un string, un entero, un booleano, o una lista de ellos. Los decimales viajan como string (`"0.5"`).
Al menos un lado de la condición debe ser una propiedad — comparar dos constantes daría siempre la misma respuesta.
## Operadores [#operadores]
| Operador | Se lee | Operandos |
| :------------------- | :---------------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
| `equal` | left es igual a right | Dos operandos compatibles cualesquiera. Un entero y un decimal se comparan como números. |
| `greater_than` | left es mayor que right | Dos números. |
| `greater_equal_than` | left es mayor o igual que right | Dos números. |
| `less_than` | left es menor que right | Dos números. |
| `less_equal_than` | left es menor o igual que right | Dos números. |
| `in` | left es uno de los valores de right | `left` es un valor único; `right` es una lista de valores del mismo tipo (`{ "value": ["co_ach", "co_transfiya"] }`). |
| `is_null` | left no tiene valor | Solo `left`, y debe ser una propiedad que pueda estar vacía. Sin `right`. |
Dos detalles a tener en cuenta:
* Cuando una propiedad involucrada en una comparación numérica está vacía (`null`), la condición es falsa. Usa `is_null` si necesitas reaccionar a la ausencia de un valor.
* Los operandos deben ser compatibles: comparar un monto con un texto, o un string con una lista de números, se rechaza al crear la política (`fee_policy_invalid_strategy`), no se ignora en silencio después.
## Propiedades [#propiedades]
Qué propiedades puede usar una regla depende del **tipo de fee**: cada uno se origina en un tipo de operación — su **entidad principal** — y puede mirar también las entidades relacionadas con ella. Una propiedad de la entidad principal se referencia por su nombre (`method`); una de una entidad relacionada, como `entidad.propiedad` (`account.name`, `account_holder.type`).
Los montos van en la unidad mínima de su moneda (`1234` son 12,34), los códigos de moneda siguen ISO 4217 (`COP`, `USD`) y los de país ISO 3166-1 alpha-2 (`CO`).
### Dispersiones — `bank_transfer`, `bank_transfer_gmf` [#dispersiones--bank_transfer-bank_transfer_gmf]
Entidad principal: la **dispersión** (payout). Relacionadas: la **cuenta** origen y el **propietario de la cuenta** (account holder).
| Propiedad | Tipo | Significado |
| :--------------------- | :------------ | :----------------------------------------------------------------------------------------- |
| `method` | string | El método de dispersión, por ejemplo `co_ach`, `co_transfiya`, `co_mono_turbo`, `co_breb`. |
| `source_currency_code` | string | La moneda de la cuenta origen. |
| `source_amount` | número o null | El monto debitado de la cuenta origen, en su unidad mínima. |
| `target_currency_code` | string | La moneda del destino. |
| `target_amount` | número o null | El monto entregado en el destino, en su unidad mínima. |
| `target_country_code` | string | El país de la cuenta destino. |
### Recaudos — `payin` [#recaudos--payin]
Entidad principal: el **pago**. Relacionadas: la **cuenta** acreditada (la subcuenta) y su **propietario**.
| Propiedad | Tipo | Significado |
| :------------------------- | :------------ | :------------------------------------------------------------ |
| `method` | string | El método del pago, por ejemplo `co_breb`. |
| `merchant_currency_code` | string | La moneda del monto del comercio — la moneda de la plantilla. |
| `merchant_amount` | número o null | El monto del pago de cara al comercio, en su unidad mínima. |
| `subaccount_currency_code` | string | La moneda de la subcuenta acreditada. |
| `subaccount_amount` | número o null | El monto acreditado a la subcuenta, en su unidad mínima. |
### Transferencias entre subcuentas — `account_to_account_fx`, `account_to_account_fx_spread` [#transferencias-entre-subcuentas--account_to_account_fx-account_to_account_fx_spread]
Entidad principal: la **operación de subcuenta** (la transferencia). Relacionadas: la **cuenta** origen y su **propietario**.
| Propiedad | Tipo | Significado |
| :--------------------- | :------------ | :-------------------------------------------------------------------------------- |
| `operation_type` | string | El tipo de transferencia: `account_to_account_fx` cuando las monedas difieren. |
| `source_currency_code` | string | La moneda de la subcuenta origen. |
| `source_amount` | número o null | El monto debitado de la subcuenta origen, en su unidad mínima. |
| `target_currency_code` | string | La moneda de la subcuenta destino. |
| `target_amount` | número o null | El monto acreditado a la subcuenta destino, en su unidad mínima. |
| `metadata` | string o null | Un campo de la metadata de la transferencia. Recibe `args: { "key": "" }`. |
### Entidades relacionadas: cuenta y propietario de la cuenta [#entidades-relacionadas-cuenta-y-propietario-de-la-cuenta]
Disponibles para todos los tipos de fee anteriores, con el prefijo `account.` o `account_holder.`:
| Propiedad | Tipo | Significado |
| :------------------------ | :------------ | :---------------------------------------------------------------------------------------------------------------------- |
| `account.name` | string o null | El nombre que le diste a la cuenta. |
| `account.metadata` | string o null | Un campo de la metadata de la cuenta. Recibe `args: { "name": "" }`. |
| `account_holder.type` | string | `self` cuando el propietario de la cuenta es tu propia empresa, `third_party` cuando es uno de tus usuarios o clientes. |
| `account_holder.metadata` | string o null | Un campo de la metadata del propietario de la cuenta. Recibe `args: { "name": "" }`. |
La metadata es el gancho más flexible: marca tus cuentas o a sus propietarios con un `plan`, un `segment` o un `tier` al crearlos, y pon precios según eso sin tocar la política cada vez que un usuario cambie de plan.
## Ejemplos [#ejemplos]
Cada ejemplo es una regla dentro de la estrategia de una moneda. Si la operación no cumple las condiciones de la regla, esta no aplica y el precio lo decide la siguiente regla o, cuando ninguna coincide, el `default_calculation` de la estrategia.
**Dispersiones ACH gratis** — la regla aplica cuando el método es exactamente `co_ach`; cualquier otro método paga el `default_calculation`:
```json
{
"conditions": [
{ "operator": "equal", "left": { "property": "method" }, "right": { "value": "co_ach" } }
],
"calculation": { "fixed": 0, "percentage": "0" }
}
```
**Una tarifa más baja para dispersiones grandes** — desde 1.000.000,00 COP. Este ejemplo muestra la estrategia completa, con el `default_calculation` que cobra cuando la regla no aplica:
```json
{
"rules": [
{
"summary": "Tarifa reducida desde 1.000.000,00 COP",
"conditions": [
{
"operator": "greater_equal_than",
"left": { "property": "source_amount" },
"right": { "value": 100000000 }
}
],
"calculation": { "fixed": 150000, "percentage": "0" }
}
],
"default_calculation": { "fixed": 300000, "percentage": "0" }
}
```
Una dispersión de 1.500.000,00 COP cumple la condición y paga 1.500,00; una de 800.000,00 COP no la cumple, así que paga los 3.000,00 del `default_calculation`.
**Los rieles instantáneos cuestan menos** — cualquiera de dos métodos; los demás pagan el `default_calculation` que para el ejemplo seria un valor mas alto:
```json
{
"conditions": [
{
"operator": "in",
"left": { "property": "method" },
"right": { "value": ["co_transfiya", "co_breb"] }
}
],
"calculation": { "fixed": 120000, "percentage": "0" }
}
```
**Exonerar cuentas VIP** — la cuenta se creó con `metadata.plan = "vip"`; las demás cuentas pagan el `default_calculation`:
```json
{
"conditions": [
{
"operator": "equal",
"left": { "property": "account.metadata", "args": { "name": "plan" } },
"right": { "value": "vip" }
}
],
"calculation": { "fixed": 0, "percentage": "0" }
}
```
**Las cuentas sin marcar pagan un recargo** — el campo de metadata está ausente; las cuentas marcadas pagan el `default_calculation`:
```json
{
"conditions": [
{
"operator": "is_null",
"left": { "property": "account.metadata", "args": { "name": "plan" } }
}
],
"calculation": { "fixed": 200000, "percentage": "0.01" }
}
```
Las reglas se evalúan en orden y gana la primera que coincide, así que pon primero las más específicas y deja que `default_calculation` se encargue del resto.
## Siguientes pasos [#siguientes-pasos]
* [Políticas de fees](/docs/guides/core/concepts/fees/policies) — donde viven estas reglas, y cómo se resuelven las políticas.
* [Fees](/docs/guides/core/concepts/fees) — los tipos de fee y por qué se cobra cada uno.
* [Crear políticas de fees](/docs/api-reference/core/fees/create_fee_policies) — el schema completo del request.
# Liquidación (/es/docs/guides/core/flows/payins/settlement)
Recibir el pago es la mitad de la historia; este flujo cubre la otra mitad: cómo el dinero que enviaron los pagadores se convierte en saldo en **tu cuenta bancaria**. Recorre cómo Mono te entrega los fondos, los webhooks que reportan cada paso y qué debe revisar tu conciliación al final del día.
Si aún no has leído el [concepto de Liquidación](/docs/guides/core/concepts/payins/settlement), empieza por ahí: esta página asume que sabes qué es un lote de liquidación.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Pagos llegando a `approved_in_provider` (cualquiera de los [flujos de recaudos](/docs/guides/core/flows/payins) los produce).
* Un endpoint de webhooks suscrito a los eventos `payin_settlement_batch_*`.
## Cómo corre la entrega [#cómo-corre-la-entrega]
1. **Los pagos se acumulan.** Cada pago aprobado con destino a tu cuenta de fondeo espera como `pending_to_deliver`.
2. **Mono los agrupa en un lote.** `payin_settlement_batch_created` lista exactamente qué pagos cubre el lote y cuánto entrega cada uno. Un mismo día puede producir varios lotes.
3. **Corre la entrega.** `processing` significa que la entrega está en curso; `completed`, que el dinero llegó a tu cuenta de fondeo. Si lees un lote en `retryable` o `failed`, Mono se está encargando y no necesitas hacer nada.
4. **Los pagos quedan estampados.** Cada pago cubierto recibe `settlement_batch_id` y `funds_delivery_status: "completed"`.
## Conciliar las liquidaciones [#conciliar-las-liquidaciones]
* **Lote → extracto bancario**: cruza cada `payin_settlement_batch_completed` con un crédito en tu extracto por `total_amount`.
* **Lote → pagos**: `GET /v1/core/payins/settlement_batches/{id}` devuelve los pagos cubiertos con su `funding_amount`. La suma es el total del lote, sin pago faltante ni repetido.
* **Rezagados**: un pago aprobado tarde en el día puede seguir en `pending_to_deliver` al cierre; llega en el último lote del día. Si un pago sigue sin entregarse por más de un día, contacta a soporte de Mono con su `id`.
## Siguientes pasos [#siguientes-pasos]
* [Concepto de Liquidación](/docs/guides/core/concepts/payins/settlement): estados, agrupamiento y los campos involucrados.
* [Pagos](/docs/guides/core/concepts/payins/payins): los campos de entrega por pago.
* [Webhooks](/docs/guides/core/webhooks/payins): los tres eventos del lote de liquidación en contexto.
# Recarga (/es/docs/guides/core/flows/payins/topup)
Una **recarga** le permite a un usuario final cargar dinero a su propio saldo de billetera desde cualquier banco colombiano: tu producto le muestra una llave Bre-B personal (o un QR), él transfiere desde la app de su banco, y segundos después el saldo de su subcuenta refleja la recarga. El patrón es **una plantilla de recarga por subcuenta**, creada una vez y reutilizada en cada recarga. La llave se vuelve la "dirección de depósito" permanente del usuario.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una cuenta de Mono con Core y la feature de recaudos habilitada para tu programa. Ver [Métodos: configuración del programa](/docs/guides/core/concepts/payins/methods).
* Una subcuenta por usuario final. Ver [Ledger](/docs/guides/core/concepts/ledger).
* Un endpoint de webhooks suscrito a los [eventos de recaudos](/docs/guides/core/webhooks/payins).
## Vista de la secuencia [#vista-de-la-secuencia]
## Pasos [#pasos]
1. **Crea la plantilla cuando nace la subcuenta.** Una llamada por usuario, idempotente sobre `external_id` (usa tu propio ID de usuario o cuenta):
```json
{
"external_id": "user-8421-topup",
"type": "topup",
"usage_mode": "multiple_use",
"subaccount_id": "",
"currency": "COP",
"nickname": "Recarga de billetera del usuario 8421",
"method_params": { "co_breb": { "create_qr": true } }
}
```
La plantilla se crea en estado `created`: la llave Bre-B todavía no existe.
2. **Espera `payin_template_ready`.** El webhook (o un `GET` a la plantilla) entrega `payment_tokens.co_breb`: la llave y, como `create_qr` fue true, la imagen del QR estático. Guarda ambos asociados al usuario.
3. **Muestra la llave en tu producto.** El usuario recarga cuando quiera, desde cualquier banco, sin más llamadas a la API: cada transferencia a la llave queda registrada como un pago contra esta plantilla.
4. **Reacciona a los webhooks del pago.** `payin_subaccount_credited` es la señal para actualizar el saldo visible del usuario; `subaccount_amount` es lo acreditado. Las condiciones de rechazo (una transferencia por encima del [tope de Bre-B](/docs/guides/core/concepts/payins/methods/co-breb), un monto fuera de los límites de la plantilla) llegan como `payin_rejected` con su `state_reason`.
5. **Concilia.** Cada pago lleva `template_id` y `subaccount_id`, así que mapearlo a un usuario es un lookup que ya tienes. `instrument.payer` te dice quién envió realmente el dinero.
## Variante: recarga de una sola vez [#variante-recarga-de-una-sola-vez]
Para una única recarga esperada (un depósito de onboarding, el fondeo único de un saldo de campaña), crea la plantilla con `"usage_mode": "single_use"` y, si el monto es fijo, define `minimum_payin_amount` = `maximum_payin_amount`. El primer pago exitoso mueve la plantilla a `paid` y esta no acepta nunca otro pago. Usa `expected_payers` si solo una persona específica puede fondearla.
## Barandas que vale la pena definir [#barandas-que-vale-la-pena-definir]
* `minimum_payin_amount` / `maximum_payin_amount`: acotan cada recarga.
* `expected_payers`: amarra la llave al documento de su dueño, para que nadie recargue la billetera de otro por error.
* `payin_data.metadata`: se estampa en cada pago, útil para etiquetar el producto de billetera o la campaña.
## Siguientes pasos [#siguientes-pasos]
* [Recaudar con una plantilla](/docs/guides/core/flows/payins/collect-with-template): la misma mecánica, apuntada a clientes.
* [Liquidación](/docs/guides/core/flows/payins/settlement): cómo el dinero recargado llega a tu cuenta de fondeo.
* [Plantillas](/docs/guides/core/concepts/payins/templates): cada campo que acepta la plantilla.
# Flujos de recaudos (/es/docs/guides/core/flows/payins)
Un **flujo** es un caso de uso de recaudos completo, de extremo a extremo. Donde [Conceptos](/docs/guides/core/concepts/payins) responde "qué es una plantilla, una intención de pago, un pago?", los flujos responden "cómo dejo que mis usuarios recarguen su billetera, recaudo de muchos pagadores o concilio los pagos uno a uno?".
## Elegir un flujo [#elegir-un-flujo]
La decisión se reduce a dos preguntas: quién paga, y ¿necesitas saber qué pago es cuál antes de que llegue el dinero?
| Tu caso | Flujo |
| :-------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- |
| Usuarios finales cargan saldo a su propia billetera, repetidamente, con una llave permanente. | [Recarga](/docs/guides/core/flows/payins/topup) |
| Muchos pagadores pagan a una llave compartida o un QR estático: donaciones, punto físico, campañas. | [Recaudar con una plantilla](/docs/guides/core/flows/payins/collect-with-template) |
| Cada pago debe identificarse de antemano: checkout, facturas, un QR por compra. | [Recaudar con intenciones de pago](/docs/guides/core/flows/payins/collect-with-intents) |
| Entender cómo el dinero recaudado llega a tu cuenta bancaria. | [Liquidación](/docs/guides/core/flows/payins/settlement) |
Recarga una subcuenta con una llave Bre-B permanente.
Una llave o QR estático, muchos pagadores, conciliación por webhooks.
Una intención de pago por cada pago: QR dinámico, reintentos, conciliación exacta.
Del pago aprobado al dinero en tu cuenta de fondeo.
## Siguientes pasos [#siguientes-pasos]
* [Conceptos](/docs/guides/core/concepts/payins): las entidades que componen estos flujos.
* [Webhooks](/docs/guides/core/webhooks/payins): los eventos que mueven cada flujo.
* [Referencia de API de recaudos](/docs/api-reference/core/payins): los endpoints detrás de ellos.
# Recaudar con intenciones de pago (/es/docs/guides/core/flows/payins/collect-with-intents)
Este flujo declara **cada pago esperado antes de que el dinero se mueva**: por cada orden o factura, tu backend crea una [intención de pago](/docs/guides/core/concepts/payins/intents), Mono le acuña una llave Bre-B dedicada y un QR dinámico, y el pago que llega se vincula a la intención de pago automáticamente. La conciliación deja de ser trabajo de detective: preguntas "¿se pagó la intención de pago `order-1042`?" y la API responde.
Úsalo para checkout de e-commerce, pago de facturas o cualquier caso donde el monto y el pagador se conocen por pago y deben coincidir exactamente.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una cuenta de Mono con Core y la feature de recaudos habilitada para tu programa. Ver [Métodos: configuración del programa](/docs/guides/core/concepts/payins/methods).
* Una plantilla de recaudo creada con `method_params.co_breb.key_per: "intent"`, o la decisión de crear intenciones de pago sin plantilla directamente contra una subcuenta.
* Un endpoint de webhooks suscrito a los [eventos de recaudos](/docs/guides/core/webhooks/payins).
* El concepto de [Intenciones de pago](/docs/guides/core/concepts/payins/intents) fresco en la cabeza.
## Vista de la secuencia [#vista-de-la-secuencia]
## Pasos [#pasos]
1. **Crea una intención de pago por cada pago.** Idempotente sobre `external_id`. Usa tu ID de orden, para que un doble clic no pueda crear dos intenciones de pago:
```json
{
"external_id": "order-1042",
"template_id": "",
"method": "co_breb",
"merchant_amount": { "amount": 8990000, "currency": "COP" },
"method_params": { "create_qr": true }
}
```
Envía exactamente uno de `merchant_amount` o `payer_amount`. Con una intención de pago sin plantilla, reemplaza `template_id` por `subaccount_id`.
2. **Espera `payin_intent_ready_to_pay`.** Entrega `payment_tokens` (la llave propia de la intención de pago y un QR de un solo uso con el monto exacto) y `expires_at`: para Bre-B, 30 minutos. Muestra el QR en tu checkout con un contador.
3. **El cliente paga.** El monto se exige: un pago por cualquier valor distinto al de la intención de pago se rechaza (`intent_amount_mismatch`).
4. **Maneja los tres finales.**
* **Pagado**: `payin_intent_successful` (más el `subaccount_credited` del pago). Confirma la orden.
* **Intento fallido**: `payin_intent_attempt_failed`: el pago rebotó pero la intención de pago se rearmó de vuelta a `ready_to_pay`. Mantén el QR en pantalla; el cliente puede reintentar contra la misma llave mientras dure la ventana.
* **Expirado**: `payin_intent_expired`: la ventana se cerró sin pago. El `external_id` queda libre, así que ofrecer un checkout nuevo para la misma orden es seguro.
5. **Concilia sin almacenar IDs de Mono.** `GET /v1/core/payins/intents/find_successful_by?external_id=order-1042` devuelve la intención de pago exitosa para tu identificador, o nada: una respuesta en una llamada a "¿esto ya se pagó?".
## ¿Con plantilla o sin plantilla? [#con-plantilla-o-sin-plantilla]
* **Con una plantilla `key_per: "intent"`**: las intenciones de pago heredan límites, pagadores esperados y `payin_data`; administras la configuración de la campaña en un solo lugar.
* **Sin plantilla**: cada intención de pago se sostiene sola contra una subcuenta, con su propia configuración. Menos piezas móviles cuando no hay una campaña compartida de la cual hablar.
## Siguientes pasos [#siguientes-pasos]
* [Intenciones de pago](/docs/guides/core/concepts/payins/intents): la máquina de estados completa, el rearmado y la expiración.
* [Tokens de pago](/docs/guides/core/concepts/payins/payment-tokens): los QRs dinámicos en detalle.
* [Recaudar con una plantilla](/docs/guides/core/flows/payins/collect-with-template): la alternativa de pagos anónimos.
# Recaudar con una plantilla (/es/docs/guides/core/flows/payins/collect-with-template)
Este flujo recauda pagos de **muchos pagadores a través de una sola dirección compartida**: una plantilla multiuso emite una llave Bre-B y un QR estáticos, tú los exhibes (en una pantalla, un afiche, el pie de una factura) y cada transferencia que reciben aterriza en tu subcuenta como un pago. No hay llamada a la API por pago: la plantilla es la campaña, y los webhooks son tu registro de quién pagó qué.
Úsalo para donaciones, pagos con QR en punto físico, cuotas recurrentes o cualquier recaudo donde no necesites saber de antemano qué pago específico viene en camino.
## Antes de empezar [#antes-de-empezar]
Vas a necesitar:
* Una cuenta de Mono con Core y la feature de recaudos habilitada para tu programa. Ver [Métodos: configuración del programa](/docs/guides/core/concepts/payins/methods).
* La subcuenta que recibirá los recaudos.
* Un endpoint de webhooks suscrito a los [eventos de recaudos](/docs/guides/core/webhooks/payins).
* El concepto de [Plantillas](/docs/guides/core/concepts/payins/templates) fresco en la cabeza.
## Vista de la secuencia [#vista-de-la-secuencia]
## Pasos [#pasos]
1. **Crea la plantilla de recaudo.** `type: "collection"` es el valor por defecto; hazla `multiple_use` y pide el QR:
```json
{
"external_id": "campana-2026-regreso-a-clases",
"usage_mode": "multiple_use",
"subaccount_id": "",
"currency": "COP",
"reference": "Regreso a clases 2026",
"minimum_payin_amount": { "amount": 1000000, "currency": "COP" },
"total_maximum_amount": { "amount": 5000000000, "currency": "COP" },
"expires_at": "2026-12-31T23:59:59Z",
"method_params": { "co_breb": { "create_qr": true } }
}
```
2. **Espera `payin_template_ready`** y publica la llave y el QR donde los pagadores los vayan a ver.
3. **Deja fluir los pagos.** Cada transferencia se convierte en un pago: `payin_created` → `payin_approved_in_provider` → `payin_subaccount_credited`. La identidad del pagador llega en `instrument.payer`. Así sabes quién pagó, ya que en este flujo nada identifica los pagos de antemano.
4. **Vigila los hitos de la campaña.** Si definiste `total_minimum_amount`, `payin_template_minimum_paid` anuncia que se alcanzó la meta; `payin_template_paid` anuncia el tope (`total_maximum_amount`) y la plantilla deja de aceptar pagos. Reabre una campaña pagada subiendo el tope con un update.
5. **Ciérrala.** Deja que `expires_at` pode la plantilla, o elimínala cuando `paid_amount` esté liquidado y nada quede en vuelo. En ambos casos la llave y el QR se dan de baja y `payin_template_discarded` lo confirma.
## Conciliación en este flujo [#conciliación-en-este-flujo]
Los pagos son anónimos hasta que llegan, así que concilia **listando pagos por `template_id`** y cruzando `instrument.payer` (documento) y `amount`. Si necesitas un cruce exacto pago-a-orden, esa es la señal para usar [intenciones de pago](/docs/guides/core/flows/payins/collect-with-intents) en su lugar.
## Siguientes pasos [#siguientes-pasos]
* [Recaudar con intenciones de pago](/docs/guides/core/flows/payins/collect-with-intents): cuando cada pago debe identificarse de antemano.
* [Liquidación](/docs/guides/core/flows/payins/settlement): cómo el dinero recaudado llega a tu cuenta de fondeo.
* [Webhooks](/docs/guides/core/webhooks/payins): el catálogo completo de eventos sobre el que corre este flujo.
# Liquidación (/es/docs/guides/core/concepts/payins/settlement)
Un pago mueve el dinero dos veces, y los dos movimientos responden preguntas distintas. El **crédito a la subcuenta** es decisión *tuya*: ¿cuándo refleja el pago el saldo del usuario final? La **entrega de los fondos a ti** es trabajo de *Mono*: ¿cómo llega el dinero real a la **cuenta de fondeo**, la cuenta bancaria real que respalda tu programa? Cada movimiento tiene su propia página, porque corren con relojes distintos, campos distintos y webhooks distintos.
| Movimiento | Dinero | Lo decide | Se reporta por |
| :------------------------------------------------------------------------------------ | :------ | :-------- | :-------------------------------------------------------------- |
| [Crédito a la subcuenta](/docs/guides/core/concepts/payins/settlement/subaccount) | Virtual | Tú | `payin_subaccount_credited`, el estado del pago |
| [Entrega a tu cuenta de fondeo](/docs/guides/core/concepts/payins/settlement/funding) | Real | Mono | `funds_delivery_status`, los eventos `payin_settlement_batch_*` |
Los dos son genuinamente independientes: con el trigger `on_approval`, la subcuenta se acredita **antes** de que el dinero real te haya llegado. La entrega se pone al día con su propio calendario.
El trigger que eliges, el webhook y el monto que ven tus usuarios.
Lotes de liquidación y su ciclo de vida.
## Siguientes pasos [#siguientes-pasos]
* [Flujo de liquidación](/docs/guides/core/flows/payins/settlement): el recorrido de extremo a extremo con webhooks.
* [Pagos](/docs/guides/core/concepts/payins/payins): los campos sobre los que viven ambos movimientos.
* [Referencia de API de lotes de liquidación](/docs/api-reference/core/payins#settlement-batches): los schemas completos.
# Crédito a la subcuenta (/es/docs/guides/core/concepts/payins/settlement/subaccount)
Este es el primero de los [dos movimientos de liquidación](/docs/guides/core/concepts/payins/settlement): el **dinero virtual**, el crédito en el ledger de Core que hace visible el pago como saldo en la subcuenta. Este movimiento es decisión *tuya*: tú eliges cuándo ocurre. El segundo movimiento, el dinero real llegando a tu cuenta bancaria, es trabajo de Mono y tiene su propia página: [Entrega a tu cuenta de fondeo](/docs/guides/core/concepts/payins/settlement/funding).
## Elegir el trigger [#elegir-el-trigger]
`subaccount_disbursement_trigger` decide el momento del crédito en el ledger. Tu programa define el valor por defecto, y cada plantilla o intención de pago puede sobreescribirlo para sus propios pagos:
| Trigger | La subcuenta se acredita cuando… |
| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------- |
| `on_approval` | El proveedor aprueba el pago. El ajuste natural para Bre-B, donde la aprobación y el movimiento del dinero están a segundos de distancia. |
| `on_funds_delivery` | El dinero real llega a su cuenta destino. |
## Cómo se ve el crédito [#cómo-se-ve-el-crédito]
El crédito lo anuncia el webhook `payin_subaccount_credited` y el pago pasa a `subaccount_credited` (para la mayoría de las integraciones, la señal de "pago completo"). `subaccount_amount` es el valor acreditado, en la moneda de la subcuenta, y los [cargos](/docs/guides/core/concepts/payins/fees) cobrados sobre el pago ya están reflejados: `calculated_fees` en el pago los desglosa.
Desde este momento el usuario final puede ver y usar el saldo, sin importar dónde esté el dinero real.
## Independiente de la entrega de fondos [#independiente-de-la-entrega-de-fondos]
Los dos movimientos de liquidación son genuinamente independientes: con `on_approval`, la subcuenta se acredita **antes** de que el dinero real te haya llegado. La [entrega a la cuenta de fondeo](/docs/guides/core/concepts/payins/settlement/funding) se pone al día con su propio calendario, y reporta por sus propios campos y webhooks. Elige `on_funds_delivery` cuando tu producto no deba mostrar saldo hasta que el dinero sea físicamente tuyo.
## Siguientes pasos [#siguientes-pasos]
* [Entrega a tu cuenta de fondeo](/docs/guides/core/concepts/payins/settlement/funding): el segundo movimiento.
* [Pagos](/docs/guides/core/concepts/payins/payins): los estados y campos alrededor del crédito.
* [Cargos](/docs/guides/core/concepts/payins/fees): qué se deduce al acreditar la subcuenta.
# Entrega a tu cuenta de fondeo (/es/docs/guides/core/concepts/payins/settlement/funding)
Este es el segundo de los [dos movimientos de liquidación](/docs/guides/core/concepts/payins/settlement): el **dinero real**, los fondos recaudados viajando a tu **cuenta de fondeo**, la cuenta bancaria real que respalda tu programa. Este movimiento es trabajo de *Mono* y nunca bloquea el primero (el crédito en el ledger que ven tus usuarios, cubierto en [Crédito a la subcuenta](/docs/guides/core/concepts/payins/settlement/subaccount)). Corre detrás de él, con su propio calendario, y reporta por sus propios campos y webhooks.
## Rastreo por pago [#rastreo-por-pago]
Cada pago rastrea su propia entrega:
* `funds_delivery_status`: `null` hasta que el pago queda `approved_in_provider`, luego `pending_to_deliver` → `processing` → `completed`.
* `funds_destination`: `funding_account` significa que los fondos te llegan en un lote de liquidación; `method_receiving_account`, que la entrega se completa de inmediato y el pago nunca aparece en un lote de liquidación.
* `settlement_batch_id`: el lote que entregó los fondos, cuando uno cubre este pago.
## Lotes de liquidación [#lotes-de-liquidación]
Mono te entrega los fondos en **lotes de liquidación**. Al conciliar, ten en cuenta tres cosas:
1. Un lote cubre muchos pagos: comparten `settlement_batch_id`, y `total_amount` es la suma de sus `funding_amount`.
2. Un mismo día puede producir varios lotes.
3. Un pago aprobado tarde en el día puede llegar en el último lote del día en vez de uno anterior.
## Ciclo de vida del lote [#ciclo-de-vida-del-lote]
Cada uno de estos estados se notifica por [webhook](/docs/guides/core/webhooks/payins). Al leer un lote también puedes ver `retryable` o `failed`: Mono se está encargando de la entrega y no necesitas hacer nada. Ninguno de los dos se notifica.
## Leer un lote [#leer-un-lote]
`GET /v1/core/payins/settlement_batches/{id}` devuelve el lote con sus pagos cubiertos: lo que cada uno recaudó (`amount`), lo que entrega (`funding_amount`) y las `funding_amount_rates` detrás de la conversión cuando las monedas difieren (vacías hoy, porque todo es COP). La respuesta tiene la misma forma que entregan los webhooks `payin_settlement_batch_*`. El endpoint de listado reporta `payins_count` por lote.
## Siguientes pasos [#siguientes-pasos]
* [Flujo de liquidación](/docs/guides/core/flows/payins/settlement): el recorrido de extremo a extremo con webhooks.
* [Crédito a la subcuenta](/docs/guides/core/concepts/payins/settlement/subaccount): el primer movimiento.
* [Referencia de API de lotes de liquidación](/docs/api-reference/core/payins#settlement-batches): los schemas completos.
# Errores de plantilla (/es/docs/guides/core/concepts/payins/templates/errors)
Una [plantilla](/docs/guides/core/concepts/payins/templates) lleva `state_reason` en sus dos estados terminales, y significan cosas muy distintas:
* **`failed`**: la plantilla nunca llegó a ser pagable. La preparación de su token de pago agotó sus reintentos, así que ningún pagador pudo alcanzarla.
* **`discarded`**: la plantilla sí fue pagable y su vida terminó: fue podada o borrada, y sus tokens de pago fueron desmontados.
Lees el código en los webhooks [`payin_template_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_failed) y [`payin_template_discarded`](/docs/api-reference/core/payin-webhooks/webhook_payin_template_discarded), y en la plantilla misma con [get](/docs/api-reference/core/payins/get_template) o [list](/docs/api-reference/core/payins/list_templates).
### Ejemplo de payload del webhook [#ejemplo-de-payload-del-webhook]
```json
{
"timestamp": "2026-02-11T14:22:07.913204Z",
"event": {
"type": "payin_template_failed",
"data": {
"id": "cpit_034EASMHlq36oZISbc9W4b",
"state": "failed",
"state_reason": "key_already_registered",
"external_id": "campaign-2026-02",
"subaccount_id": "csub_034EASMIAFVq06j2NxhnIJ"
}
}
}
```
## Fallida: la plantilla nunca llegó a ser pagable [#fallida-la-plantilla-nunca-llegó-a-ser-pagable]
Una plantilla se crea en `created` y sólo llega a `ready` cuando su token de pago existe. Estas son las formas en que esa preparación termina en vez de lograrlo.
| Código | Descripción | Causa | Solución sugerida |
| -------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `key_already_registered` | La llave ya está tomada | El valor de llave personalizado que pediste ya está registrado en la red de pagos inmediatos: las llaves son únicas en toda la red. | Crea la plantilla otra vez con otro `custom_key_value`, o deja que Mono genere la llave. |
| `key_registration_failed` | Falló el registro de la llave | Registrar la llave de pago en la red agotó sus reintentos. | Crea la plantilla otra vez. Si se repite, contacta a soporte con el `id` de la plantilla. |
| `collection_registration_failed` | Falló el registro del cobro | Mono no pudo registrar el cobro con el proveedor del método de pago. | Crea la plantilla otra vez. Si se repite, contacta a soporte con el `id` de la plantilla. |
| `unknown` | Desconocido | Una falla sin clasificar durante la preparación. | Contacta a soporte con el `id` de la plantilla. |
No hay forma de reintentar la preparación de una plantilla que ya falló. El estado es
terminal. Crea una nueva: una plantilla que termina `failed` o `discarded` libera su
`external_id`, así que puedes reusar el mismo identificador para el reemplazo.
## Descartada: la vida de la plantilla terminó [#descartada-la-vida-de-la-plantilla-terminó]
Tres salidas, y sólo una la pediste tú. Las tres desmontan los tokens de pago, así que cualquier llave o QR ya distribuido deja de funcionar.
| Código | Descripción | Causa | Solución sugerida |
| ------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `deleted` | Borrada | Borraste la plantilla por la API. | Esperado. Crea una plantilla nueva cuando necesites volver a cobrar. |
| `expired` | Expirada | La plantilla pasó su `expires_at` y fue podada. | Define una ventana más larga en la siguiente, o crea una plantilla nueva. |
| `inactivity` | Inactividad | La plantilla pasó demasiado tiempo sin actividad y fue podada. Cubre los dos casos: nunca pagada, y pagada alguna vez pero inactiva desde entonces. | Crea una plantilla nueva. Si el cobro es de larga vida, mantenlo en uso o reemítelo. |
Para una plantilla podada, `prune_reason` conserva la distinción fina que `state_reason`
colapsa: `inactivity_without_attempts` e `inactivity_with_attempts` se vuelven ambos
`inactivity`. Lee `prune_reason` cuando necesites distinguir "nunca nadie pagó" de "los pagos
se detuvieron" (ver [Poda](/docs/guides/core/concepts/payins/templates/pruning)).
## Cómo manejarlos [#cómo-manejarlos]
Una plantilla que falla o se descarta no es un error en un pago; es un cobro que ya no existe. Lo que importa es que **dejes de distribuir su token de pago**, porque a los pagadores que tengan una llave o un QR suyo se les rechazará el pago con [`template_deleted`, `template_expired` o `template_invalid_state`](/docs/guides/core/concepts/payins/payins/errors#condiciones-de-la-plantilla).
```ts
app.post('/webhook', (req, res) => {
const { event } = req.body;
if (event.type === 'payin_template_failed') {
const { id, state_reason } = event.data;
if (state_reason === 'key_already_registered') {
// Elige otro valor de llave y crea la plantilla de nuevo
} else {
// Falló la preparación: reintenta la creación, y alerta si se repite
}
}
if (event.type === 'payin_template_discarded') {
const { id, state_reason } = event.data;
// Deja de mostrarle a los pagadores la llave o el QR de esta plantilla
// `deleted` es tuyo; `expired` e `inactivity` son la poda
}
res.status(200).send();
});
```
Cuando contactes a soporte por una plantilla, entrega el `id` de la plantilla y el
`external_id` con el que la creaste.
## Siguientes pasos [#siguientes-pasos]
* [Errores de pago](/docs/guides/core/concepts/payins/payins/errors): por qué se rehusó un pago individual.
* [Poda](/docs/guides/core/concepts/payins/templates/pruning): cuándo y por qué se barren las plantillas.
* [Webhooks](/docs/guides/core/webhooks/payins): cada evento de plantilla y su payload.
# Plantillas (/es/docs/guides/core/concepts/payins/templates)
Una **plantilla** responde una sola pregunta: *¿cómo entran los pagos a esta subcuenta?* La creas una vez (como abrir un punto de recaudo para una billetera, una campaña o un cliente) y queda ahí como la definición permanente de lo que esperas recibir. Los pagadores pagan, y cada pago entrante se verifica contra la plantilla antes de quedar registrado como un [pago](/docs/guides/core/concepts/payins/payins).
En una plantilla nunca manejas el dinero: es pura configuración. El dinero llega después, como pagos creados contra ella.
La creación es idempotente sobre `external_id`: repetir la petición devuelve la plantilla existente en vez de crear un duplicado.
## Qué puede hacer una plantilla [#qué-puede-hacer-una-plantilla]
La plantilla es la entidad más capaz de Recaudos. Todo lo de abajo es opcional más allá de lo básico. Cada capacidad tiene su propia sección o página:
* **Recibir un pago o muchos**: `usage_mode` ([un solo uso o multiuso](#un-solo-uso-o-multiuso)).
* **Servir recargas o recaudos**: `type` ([recarga o recaudo](#recarga-o-recaudo)).
* **Emitir aquello a lo que paga el pagador**: el [token de pago](/docs/guides/core/concepts/payins/payment-tokens) de cada método aceptado; para Bre-B, una llave y un QR opcional.
* **Acotar los montos**: por pago y acumulados ([límites y contadores](#límites-de-monto-y-contadores)).
* **Restringir quién puede pagar**: [`expected_payers`](#restringir-quién-puede-pagar).
* **Alojar intenciones de pago**: con llaves por intención de pago, para que cada pago llegue [identificado de antemano](/docs/guides/core/concepts/payins/intents).
* **Configurar cada pago que produce**: trigger de acreditación, metadata y sobreescrituras de cargos ([configuración heredada](#configuración-que-hereda-cada-pago)).
* **Ofrecer un checkout**: integra API-only con tu propia UI, o usa el checkout hospedado de Mono, apuntado a una de tus marcas y pidiendo los campos extra que necesites ([checkout hospedado o API-only](#checkout-hospedado-o-api-only)).
* **Rastrear su propio progreso**: `paid_amount`, `pending_amount` y los contadores `successful_payins` / `failed_payins`.
* **Expirar y limpiarse sola**: expiración (como fecha `expires_at` o duración `expires_in`, mutuamente excluyentes al crear) y [pruning](/docs/guides/core/concepts/payins/templates/pruning).
## Recarga o recaudo [#recarga-o-recaudo]
El campo `type` declara el propósito de la plantilla:
| Tipo | Propósito | Caso típico |
| :----------- | :-------------------------------------------------------------------------------- | :----------------------------------------- |
| `topup` | Un usuario final carga dinero a su **propia** subcuenta. | Recarga de billetera con llave permanente. |
| `collection` | Tu negocio recauda dinero **de clientes** hacia una subcuenta. Valor por defecto. | Checkout, facturación, donaciones. |
## Un solo uso o multiuso [#un-solo-uso-o-multiuso]
`usage_mode` define cuántos pagos acepta la plantilla:
* **`single_use`**: exactamente un pago exitoso. El primer pago mueve la plantilla a `paid`, y una plantilla single-use pagada es historia cerrada: no se puede actualizar ni reabrir.
* **`multiple_use`**: cualquier número de pagos, limitado solo por los totales acumulados (abajo) y la expiración.
## Ciclo de vida [#ciclo-de-vida]
* **`created`**: la plantilla fue aceptada y persistida, pero sus tokens de pago todavía se están preparando, así que aún no puede recibir pagos. El setup es asíncrono: para Bre-B con `key_per: "template"` (el valor por defecto), la plantilla espera en `created` hasta que la red confirma la llave.
* **`ready`**: la plantilla puede recibir pagos. `payment_tokens` lleva una entrada por cada método aceptado que emite token: para Bre-B, la llave y el QR cuando se pidió. Los métodos que no necesitan token no tienen entrada ahí.
* **`minimum_paid`**: el `paid_amount` acumulado alcanzó `total_minimum_amount`. La plantilla sigue aceptando pagos hasta `total_maximum_amount`. Un update que suba el mínimo por encima de lo ya pagado la devuelve a `ready`.
* **`paid`**: se alcanzó el máximo acumulado, o la plantilla `single_use` recibió su pago. Una plantilla `multiple_use` puede reabrirse (de vuelta a `ready` o `minimum_paid`) si un update sube o elimina `total_maximum_amount`.
* **`discarded`**: la plantilla fue podada (expiración o inactividad) o eliminada, y sus tokens de pago fueron dados de baja. `prune_reason` explica cuál fue el caso.
* **`failed`**: el setup de los tokens de pago agotó sus reintentos (por ejemplo, falló el registro de la llave Bre-B). `state_reason` explica por qué.
Cada razón detrás de una plantilla `failed` o `discarded` está en [Errores de plantilla](/docs/guides/core/concepts/payins/templates/errors).
Las transiciones de estado causadas por un **update** (subir o bajar los totales) viajan dentro del webhook `payin_template_updated` con los valores anteriores. Los webhooks de estado separados no se envían en ese caso. Todas las demás transiciones emiten su propio [webhook](/docs/guides/core/webhooks/payins).
## Límites de monto y contadores [#límites-de-monto-y-contadores]
Dos pares de límites, todos denominados en la `currency` de la plantilla:
| Campo | Alcance | Efecto |
| :---------------------------------------------- | :-------- | :-------------------------------------------------------------------------------------- |
| `minimum_payin_amount` / `maximum_payin_amount` | Cada pago | Un pago fuera del rango se rechaza (`amount_below_minimum` / `amount_exceeds_maximum`). |
| `total_minimum_amount` / `total_maximum_amount` | Acumulado | Mueven los estados `minimum_paid` y `paid`. |
La plantilla también mantiene contadores: `paid_amount` (acumulado recaudado), `pending_amount` (autorizado, en vuelo) y los conteos `successful_payins` / `failed_payins`.
## Restringir quién puede pagar [#restringir-quién-puede-pagar]
`expected_payers` es una lista opcional de documentos de identidad (tipo y número). Cuando está definida, solo esos pagadores pueden pagar la plantilla: un pago de cualquier otra persona se rechaza con `payer_not_allowed`. Para Bre-B, el documento del pagador llega con el pago mismo, así que la verificación no le exige nada al pagador más allá de pagar desde su propio banco.
## Configuración por método [#configuración-por-método]
`method_params` lleva la configuración de cada método aceptado. Para `co_breb`: si se genera un QR (`create_qr`), un alias personalizado de llave (`custom_key_value`), si la llave se crea por plantilla o por intención de pago (`key_per`) y los datos de comercio del QR. El detalle vive en [Tokens de pago](/docs/guides/core/concepts/payins/payment-tokens) y [Bre-B](/docs/guides/core/concepts/payins/methods/co-breb).
## Configuración que hereda cada pago [#configuración-que-hereda-cada-pago]
`payin_data` guarda la configuración que hereda cada pago creado desde la plantilla:
* `subaccount_disbursement_trigger`: cuándo se acredita la subcuenta: `on_approval` (apenas el proveedor aprueba) u `on_funds_delivery` (cuando el dinero llega a su cuenta destino). Ver [Crédito a la subcuenta](/docs/guides/core/concepts/payins/settlement/subaccount).
* `metadata`: se adjunta a cada pago, para tu propia conciliación.
* `fee_override`: sobreescrituras de cargos por plantilla. Ver [Cargos](/docs/guides/core/concepts/payins/fees).
* `redirect_url`: adonde vuelve el pagador tras pagar en el checkout hospedado. Ver [Checkout hospedado o API-only](#checkout-hospedado-o-api-only).
Una intención de pago creada desde la plantilla puede sobreescribir estos valores para su propio pago.
## Checkout hospedado o API-only [#checkout-hospedado-o-api-only]
Una plantilla soporta dos modos de integración para el lado que ve el pagador:
* **API-only**: tu producto es dueño de toda la experiencia del pagador: lees el [token de pago](/docs/guides/core/concepts/payins/payment-tokens) de la plantilla o de la intención de pago y muestras la llave o el QR en tu propia app, página o recibo. Todos los flujos de estas guías funcionan así.
* **Checkout hospedado por Mono**: Mono sirve la página de pago por ti: un checkout hospedado donde el pagador completa el pago, renderizado con la **marca** a la que apunta la plantilla. Comparte el `checkout_url` de la plantilla, o el `url` de una intención de pago, y el pagador paga ahí sin que construyas una pantalla de pago.
Estos campos controlan el checkout hospedado:
| Campo | En | Qué es |
| :------------------- | :---------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- |
| `checkout_url` | Plantilla (respuesta) | La URL del checkout hospedado de la plantilla. |
| `url` | Intención de pago (respuesta) | La página hospedada donde el pagador completa esa intención de pago específica. |
| `redirect_url` | `payin_data` | Adonde se redirige al pagador tras completar un pago en el checkout hospedado. Si se omite, se usa la página de voucher por defecto de Mono. |
| `form_customization` | Plantilla | Cómo se personaliza el checkout para esta plantilla: la marca con la que se renderiza y los campos extra que le pide al pagador. |
`redirect_url` y `form_customization` solo tienen efecto en el checkout hospedado; una integración API-only no los necesita.
### Qué lleva `form_customization` [#qué-lleva-form_customization]
Dos cosas, y ninguna de las dos es el branding en sí:
| Campo | Tipo | Qué es |
| :-------------- | :------------- | :---------------------------------------------------------------------------------------------------------------------- |
| `brand_id` | string \| null | La marca con la que el checkout se renderiza para esta plantilla. `null` significa que usa su presentación por defecto. |
| `custom_fields` | array \| null | Los campos que el checkout le pide al pagador **además del monto**: una lista ordenada, no un objeto libre. |
El objeto `form_customization` completo vuelve `null` cuando la plantilla no tiene ni marca ni campos personalizados.
Cada entrada de `custom_fields` es un campo tipado:
| Propiedad | Qué hace |
| :-------------- | :-------------------------------------------------------------------------- |
| `label` | El nombre del campo que ve el pagador. Obligatorio. |
| `type` | Qué escribe el pagador: `text` (por defecto), `number`, `boolean` o `date`. |
| `default_value` | Valor que se muestra pre-llenado en el formulario. |
| `required` | Si el pagador está obligado a llenarlo. Por defecto `false`. |
| `editable` | Si el pagador puede cambiar el valor pre-llenado. Por defecto `true`. |
```json
{
"form_customization": {
"brand_id": "cpitplb_5tgliBmzjZ6mpQPRbQjfKj",
"custom_fields": [
{
"label": "Número de orden",
"type": "text",
"default_value": "ORD-1042",
"editable": false
},
{ "label": "Fecha de entrega", "type": "date", "required": true }
]
}
}
```
### Marcas [#marcas]
Una **marca** es el aspecto reutilizable que viste tu checkout hospedado: un nombre, el texto del botón de pago, un color de acento, un logo y el mensaje que se muestra en el voucher después de pagar. Le pertenece a ti, no a una plantilla en particular. Varias plantillas pueden apuntar a la misma marca, y cambiar la marca las cambia todas a la vez. Una de tus marcas es la predeterminada, la que se usa cuando una plantilla nueva no nombra ninguna.
Las marcas se administran hoy desde el **dashboard de Core**, no por API: `brand_id` es la única parte de ellas que aparece en el contrato de Recaudos, y es una referencia a una marca que ya existe en tu cliente. Las marcas se resuelven dentro de tu propio alcance, así que un `brand_id` que no sea tuyo se rechaza.
## Actualizar y eliminar [#actualizar-y-eliminar]
Los updates reconocen la presencia de cada campo: un campo omitido conserva su valor, un `null` explícito limpia uno anulable. `payin_data` y `form_customization` se fusionan campo por campo, pero un campo que contiene una lista se reemplaza completo. Enviar `custom_fields` cambia la lista entera, no hay fusión campo a campo dentro de ella, y lo mismo aplica a `expected_payers`. Poner `form_customization` en `null` limpia los campos personalizados **y** desvincula la marca. `custom_merchant_name` queda fijo en la creación y no se puede actualizar. Una plantilla no se puede actualizar mientras sigue en `created`, una vez `discarded` o `failed`, siendo una single-use ya consumida, o mientras está siendo podada. Cada update exitoso emite `payin_template_updated` con los valores anteriores de los campos que cambiaron.
La eliminación es un soft delete: la plantilla deja de aceptar pagos y su `external_id` queda libre para reutilizarse. Solo se permite cuando la plantilla no retiene dinero (`paid_amount` y `pending_amount` en cero) y no está en estado `created`, `discarded` ni `failed`.
## Pruning [#pruning]
Las plantillas no viven para siempre: un barrido diario retira las que ya cumplieron (expiradas, inactivas o eliminadas) y le devuelve sus tokens de pago al proveedor. Qué reglas se disparan, los periodos de gracia que puedes configurar y qué significa el webhook `payin_template_discarded` para tu integración están en [Pruning](/docs/guides/core/concepts/payins/templates/pruning).
## Modelo de datos [#modelo-de-datos]
El schema completo, con las formas de petición y respuesta, vive en la [referencia de API de plantillas](/docs/api-reference/core/payins#templates).
## Siguientes pasos [#siguientes-pasos]
* [Tokens de pago](/docs/guides/core/concepts/payins/payment-tokens): la llave y el QR que emite la plantilla.
* [Intenciones de pago](/docs/guides/core/concepts/payins/intents): declarar cada pago esperado individualmente.
* [Recaudar con una plantilla](/docs/guides/core/flows/payins/collect-with-template): el flujo de extremo a extremo.
# Pruning (/es/docs/guides/core/concepts/payins/templates/pruning)
Las plantillas no viven para siempre. Una plantilla que ya nadie paga sigue reteniendo [tokens de pago](/docs/guides/core/concepts/payins/payment-tokens) vivos (llaves Bre-B y códigos QR registrados en la red), y eso cuesta dinero y agrega ruido. El **pruning** es el barrido diario que retira las que ya cumplieron y devuelve sus tokens.
No llamas a nada para que ocurra: Mono lo corre para cada programa con Recaudos habilitado. Lo que tú controlas es cuándo se dispara (la expiración de la plantilla y los periodos de gracia de tu programa) y qué haces cuando pasa (el webhook `payin_template_discarded`).
## Qué hace podable a una plantilla [#qué-hace-podable-a-una-plantilla]
Cuatro reglas, y la que se dispare queda registrada en `prune_reason`:
| Regla | Una plantilla se poda cuando… | Periodo de gracia por defecto |
| :---------------------------- | :---------------------------------------------------------------- | :---------------------------- |
| `expired` | Su `expires_at` ya pasó. | 1 día después de expirar |
| `inactivity_without_attempts` | Nunca recibió un pago y lleva en `ready` desde que se creó. | 30 días |
| `inactivity_with_attempts` | Ha recibido pagos, pero el **último** ya es demasiado viejo. | 90 días |
| `deleted` | La eliminaste: el delete dispara el mismo desmonte, de inmediato. | Ninguno |
Los periodos de gracia son **valores por defecto, configurables por programa**: pídele a Mono alargar o acortar cualquiera de los tres al habilitar Recaudos, o después.
## El desmonte corre en dos fases [#el-desmonte-corre-en-dos-fases]
El pruning no solo cambia un estado: primero hay que devolverle los tokens al proveedor, y solo después se registra el resultado.
1. **Se dan de baja los tokens.** Mono le pide al proveedor de cada método descartar los tokens de pago de la plantilla. Desde ese momento la llave Bre-B y su QR dejan de funcionar: ningún pago nuevo puede llegarle a la plantilla.
2. **Se cierra la plantilla.** Una plantilla en `ready` o `minimum_paid` transiciona a `discarded` y emite [`payin_template_discarded`](/docs/guides/core/webhooks/payins). Una plantilla `paid` conserva su estado `paid` (ya está consumida) y solo registra los campos de pruning.
Mientras corre el desmonte, tres campos lo rastrean:
| Campo | Qué te dice |
| :------------- | :---------------------------------------------------------------------- |
| `prune_state` | `pruning` mientras se devuelven los tokens, `pruned` una vez devueltos. |
| `prune_reason` | Cuál de las cuatro reglas se disparó. |
| `pruned_at` | Cuándo terminó el desmonte. |
Un prune que se atasca (una confirmación del proveedor que nunca llega) lo retoma el barrido del día siguiente, así que los tokens nunca quedan vivos por accidente.
## Las intenciones de pago también se limpian [#las-intenciones-de-pago-también-se-limpian]
Las intenciones de pago tienen su propio reloj: cada una la cierra su propio job programado cuando se acaba su ventana, y sus tokens se desmontan al llegar a un estado final (`successful`, `failed` o `expired`). El barrido diario también mira las intenciones de pago, pero solo como red de seguridad, para atrapar aquellas cuyo job propio nunca corrió.
## Qué hacer al respecto [#qué-hacer-al-respecto]
* **Trata `payin_template_discarded` como definitivo.** Deja de mostrar la llave o el QR de esa plantilla: ya no le van a llegar pagos.
* **Define `expires_at` a conciencia.** Es la única regla de prune que controlas por plantilla; las otras dos dependen de los periodos de gracia de tu programa.
* **No uses la inactividad para cerrar una campaña.** Si un recaudo terminó, elimina la plantilla: obtienes el desmonte de inmediato en vez de esperar el periodo de gracia.
## Siguientes pasos [#siguientes-pasos]
* [Plantillas](/docs/guides/core/concepts/payins/templates): la entidad que esta página retira.
* [Tokens de pago](/docs/guides/core/concepts/payins/payment-tokens): lo que se le devuelve al proveedor.
* [Webhooks](/docs/guides/core/webhooks/payins): los eventos de la plantilla, incluido `payin_template_discarded`.
# Errores de intención de pago (/es/docs/guides/core/concepts/payins/intents/errors)
Una [intención de pago](/docs/guides/core/concepts/payins/intents) lleva `state_reason` en sus dos estados sin éxito:
* **`failed`**: no queda nada por pagar. O el token de pago nunca llegó a existir, o un pago cerró la intención de pago definitivamente.
* **`expired`**: la ventana se cerró sin que nadie pagara.
Lees el código en los webhooks [`payin_intent_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_failed) y [`payin_intent_expired`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_expired), y en la intención de pago misma con [get](/docs/api-reference/core/payins/get_intent) o [list](/docs/api-reference/core/payins/list_intents).
### Ejemplo de payload del webhook [#ejemplo-de-payload-del-webhook]
```json
{
"timestamp": "2026-02-11T14:22:07.913204Z",
"event": {
"type": "payin_intent_failed",
"data": {
"id": "cpii_034EASMIAFVq06j2NxhnIJ",
"state": "failed",
"state_reason": "payment_token_registration_failed",
"external_id": "order-88213",
"template_id": "cpit_034EASMHlq36oZISbc9W4b"
}
}
}
```
## Las razones propias de la intención de pago [#las-razones-propias-de-la-intención-de-pago]
Lo que le puede pasar a una intención de pago antes de que exista un pago, más el comodín para un pago que falló sin una explicación que valga la pena darle al pagador.
| Código | Descripción | Causa | Solución sugerida |
| ----------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `expired` | Ventana cerrada | Nadie pagó antes del `expires_at`. Para Bre-B la ventana es de 30 minutos. | Crea una intención de pago nueva. La anterior libera su `external_id` al expirar. |
| `key_registration_failed` | Falló el registro de la llave | Registrar la llave de pago propia de la intención de pago en la red agotó sus reintentos. | Crea la intención de pago otra vez. Si se repite, contacta a soporte con el `id` de la intención de pago. |
| `payment_token_registration_failed` | Falló la creación del token de pago | La llave se registró, pero el token de pago no pudo armarse a partir de ella. | Crea la intención de pago otra vez. Si se repite, contacta a soporte con el `id` de la intención de pago. |
| `payment_failed` | El pago falló | Un pago cerró la intención de pago por una razón que no le dice nada al pagador: una falla de infraestructura como un timeout, un fallo del ledger o una caída del proveedor. | Lee el `state_reason` del pago para el detalle; crea una intención de pago nueva para reintentar. |
| `unknown` | Desconocido | El pago que cerró la intención de pago falló sin una razón conocida. | Contacta a soporte con el `id` de la intención de pago. |
## Razones heredadas del pago [#razones-heredadas-del-pago]
Cuando un pago cierra una intención de pago por una razón sobre la que el pagador o el comercio pueden actuar, la intención de pago adopta **la razón del pago con el mismo nombre** en vez de aplanarla en `payment_failed`. Cada una significa exactamente lo mismo que en el pago, así que la tabla de allá es la referencia:
| Grupo | Códigos |
| :--------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- |
| [Límites de monto](/docs/guides/core/concepts/payins/payins/errors#límites-de-monto) | `amount_below_minimum`, `amount_exceeds_maximum`, `amount_below_total_minimum`, `amount_exceeds_total_maximum`, `intent_amount_mismatch` |
| [Restricciones del pagador](/docs/guides/core/concepts/payins/payins/errors#restricciones-del-pagador) | `payer_not_allowed` |
| [Banco y riesgo](/docs/guides/core/concepts/payins/payins/errors#rechazo-del-banco) | `bank_rejected`, `risk_control` |
| [Condiciones de la plantilla](/docs/guides/core/concepts/payins/payins/errors#condiciones-de-la-plantilla) | `template_paid`, `template_expired`, `template_disabled`, `template_deleted`, `template_not_found`, `template_invalid_state` |
La división es deliberada: lo que el pagador puede corregir (el monto, no ser él quien puede pagar) y lo que el comercio necesita distinguir (de qué forma dejó de recibir pagos el cobro, o que la cuenta receptora no puede recibir dinero) conservan su nombre; las fallas de infraestructura no, porque no significan nada para quien está mirando un checkout.
Estas razones heredadas sólo aparecen con métodos de pago que **no re-arman** la intención de
pago tras un pago fallido. Bre-B, el único método disponible hoy, sí re-arma: un pago fallido
devuelve la intención de pago a `ready_to_pay` para que el pagador reintente con la misma
llave mientras dure la ventana, y la intención de pago no se cierra. Hasta que exista otro
método, una intención de pago que termine `failed` llevará una de [sus razones
propias](#las-razones-propias-de-la-intención-de-pago). Manéjalas de forma defensiva en vez de
esperarlas.
## Un pago fallido no siempre es una intención de pago fallida [#un-pago-fallido-no-siempre-es-una-intención-de-pago-fallida]
La mayoría de los pagos fallidos **no** cierran la intención de pago. Con un método que re-arma, la intención de pago vuelve a `ready_to_pay` y emite [`payin_intent_attempt_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_intent_attempt_failed). El intento fallido lo reporta el propio webhook `payin_rejected` o `payin_failed` del pago, que lleva la razón real.
O sea, hay dos preguntas distintas y dos eventos distintos:
| Pregunta | Evento | Dónde vive la razón |
| :--------------------------------------- | :--------------------------------------------- | :--------------------------------------------------------------------------------------------------- |
| ¿Por qué falló este intento? | `payin_rejected` / `payin_failed` | El `state_reason` del pago (ver [Errores de pago](/docs/guides/core/concepts/payins/payins/errors)). |
| ¿Por qué esta orden ya no puede pagarse? | `payin_intent_failed` / `payin_intent_expired` | El `state_reason` de la intención de pago (esta página). |
## Cómo manejarlos [#cómo-manejarlos]
```ts
app.post('/webhook', (req, res) => {
const { event } = req.body;
// La orden todavía puede pagarse: el pagador sólo tiene que reintentar
if (event.type === 'payin_intent_attempt_failed') {
// Mantén el checkout abierto; la llave sigue siendo válida
}
// La orden terminó
if (event.type === 'payin_intent_failed' || event.type === 'payin_intent_expired') {
const { external_id, state_reason } = event.data;
switch (state_reason) {
case 'expired':
// Nadie pagó a tiempo: ofrece un checkout nuevo
break;
case 'key_registration_failed':
case 'payment_token_registration_failed':
// La intención de pago nunca llegó a ser pagable: créala de nuevo
break;
default:
// Un pago la cerró: lee el pago para el detalle y emite una intención de pago nueva
}
}
res.status(200).send();
});
```
No infieras el pago por la ausencia de una falla. `GET
/v1/core/payins/intents/find_successful_by?external_id=...` lo responde directo: existe a lo
sumo una intención de pago `successful` por identificador.
## Siguientes pasos [#siguientes-pasos]
* [Errores de pago](/docs/guides/core/concepts/payins/payins/errors): la razón detrás de cada intento fallido.
* [Errores de plantilla](/docs/guides/core/concepts/payins/templates/errors): por qué la plantilla detrás de la intención de pago dejó de ser pagable.
* [Recaudar con intenciones de pago](/docs/guides/core/flows/payins/collect-with-intents): el flujo de punta a punta.
# Intenciones de pago (/es/docs/guides/core/concepts/payins/intents)
Una **intención de pago** es un pago esperado, declarado antes de que el dinero se mueva. Donde una [plantilla](/docs/guides/core/concepts/payins/templates) dice "acepto pagos bajo estas reglas", una intención de pago dice "estoy esperando **este** pago": un pagador, un monto, una ventana de tiempo, un token de pago propio. Cuando el pago llega, Mono lo vincula de vuelta a la intención de pago. Así sabes exactamente qué orden, factura o sesión de checkout fue pagada, antes de leer un solo campo del pago.
Usa intenciones de pago cuando cada pago debe identificarse de antemano: checkout de e-commerce, pago de facturas o cualquier flujo donde "quién paga qué" importa más que "llegó dinero".
## Con o sin plantilla [#con-o-sin-plantilla]
Una intención de pago se crea de una de dos maneras (`template_id` y `subaccount_id` son mutuamente excluyentes):
* **Desde una plantilla**: la intención de pago hereda las reglas de la plantilla (límites, pagadores esperados, `payin_data`) y puede sobreescribir algunas para sí misma. Para Bre-B, la plantilla debe crear sus llaves por intención de pago (`method_params.co_breb.key_per: "intent"`); una plantilla con llave compartida no puede vincular pagos a intenciones de pago individuales.
* **Sin plantilla**: la intención de pago se crea directamente contra una subcuenta y lleva su propia configuración. Mono le aprovisiona un token de pago dedicado de un solo uso.
La creación es idempotente sobre `external_id`: repetir la petición devuelve la intención de pago existente. Cuando una intención de pago **expira**, su `external_id` queda libre para una nueva. Eso es lo que hace seguro el ciclo de reintentos de un checkout.
## Montos [#montos]
Envía exactamente uno de `merchant_amount` o `payer_amount`; el otro se deriva. Con el único método disponible hoy (Bre-B, COP) ambos montos son iguales. Los dos campos existen para que el recaudo entre monedas llegue después sin cambiar el contrato. El monto se **exige**: un pago Bre-B por un monto distinto se rechaza (`intent_amount_mismatch`) y la intención de pago se rearma para un reintento.
## Ciclo de vida [#ciclo-de-vida]
* **`created`**: aceptada y persistida; el token de pago todavía se está preparando, así que `payment_tokens` es `null` y el pagador aún no puede pagar.
* **`ready_to_pay`**: la intención de pago es pagable: `payment_tokens` lleva la llave Bre-B (y el QR de un solo uso, cuando se pidió) y `expires_at` queda definido. Para Bre-B la ventana es de 30 minutos.
* **`awaiting_payment`**: un pago para la intención de pago fue autorizado y está en vuelo.
* **`successful`**: el pago fue confirmado; la intención de pago queda consumida, sus tokens se dan de baja y no puede pagarse nunca más.
* **`failed`**: no queda nada por pagar: el token de pago nunca se logró registrar (`key_registration_failed`, `payment_token_registration_failed`) o un pago falló en un método que no rearma (`payment_failed`).
* **`expired`**: la ventana se cerró sin que nadie pagara. `state_reason` es `expired`.
Cada razón con la que una intención de pago puede terminar, y qué hacer con cada una, está en [Errores de intención de pago](/docs/guides/core/concepts/payins/intents/errors).
**Rearmado**: cuando un intento de pago falla después de la autorización pero la intención de pago sigue viva, esta vuelve a `ready_to_pay`. El pagador puede reintentar contra la misma llave mientras dure la ventana. El intento fallido en sí lo reporta el webhook `payin_rejected` o `payin_failed` del pago; la intención de pago emite `payin_intent_attempt_failed`.
## Responder "¿esto ya se pagó?" [#responder-esto-ya-se-pagó]
`GET /v1/core/payins/intents/find_successful_by?external_id=...` devuelve la única intención de pago `successful` para un identificador de tu lado. Existe a lo sumo una por cliente. Le permite a tu conciliación preguntar por tu propio ID de orden sin almacenar IDs de Mono.
## Checkout hospedado [#checkout-hospedado]
`url` es la página hospedada por Mono donde el pagador completa esa intención de pago específica. Compártela con el pagador cuando prefieras no construir tú la pantalla de pago. Ver [Checkout hospedado o API-only](/docs/guides/core/concepts/payins/templates#checkout-hospedado-o-api-only).
## Cargos [#cargos]
En una intención de pago, `calculated_fees` llega **vacío**. Los cargos realmente cobrados aparecen en el [pago](/docs/guides/core/concepts/payins/payins).
## Modelo de datos [#modelo-de-datos]
El schema completo vive en la [referencia de API de intenciones de pago](/docs/api-reference/core/payins#intents).
## Siguientes pasos [#siguientes-pasos]
* [Tokens de pago](/docs/guides/core/concepts/payins/payment-tokens): llaves por intención de pago y códigos QR dinámicos.
* [Recaudar con intenciones de pago](/docs/guides/core/flows/payins/collect-with-intents): el flujo de extremo a extremo.
* [Webhooks](/docs/guides/core/webhooks/payins): los siete eventos de la intención de pago.
# Errores de pago (/es/docs/guides/core/concepts/payins/payins/errors)
Un [pago](/docs/guides/core/concepts/payins/payins) lleva `state_reason` sólo en sus dos estados sin éxito, y el estado en el que cae te dice **quién detuvo el pago**:
* **`rejected`**: Mono o el banco receptor rechazaron el pago. Una condición de negocio, de configuración, del pagador o de la cuenta receptora no se cumplió. Los fondos del pagador, si los hubo, los devuelve el proveedor.
* **`failed`**: el pago se rompió. O el rail de pagos reportó una falla cuando el pago ya estaba autorizado, o Mono no pudo terminar de procesarlo.
Lees el código en los webhooks [`payin_rejected`](/docs/api-reference/core/payin-webhooks/webhook_payin_rejected) y [`payin_failed`](/docs/api-reference/core/payin-webhooks/webhook_payin_failed), y en el pago mismo con [get](/docs/api-reference/core/payins/get_payin) o [list](/docs/api-reference/core/payins/list_payins).
### Ejemplo de payload del webhook [#ejemplo-de-payload-del-webhook]
```json
{
"timestamp": "2026-02-11T14:22:07.913204Z",
"event": {
"type": "payin_rejected",
"data": {
"id": "cpi_034EASMIAFVq06j2NxhnIJ",
"state": "rejected",
"state_reason": "amount_exceeds_maximum",
"template_id": "cpit_034EASMHlq36oZISbc9W4b",
"intent_id": null,
"merchant_amount": { "currency": "COP", "amount": 850000 }
}
}
}
```
## Rechazado: el pago fue rehusado [#rechazado-el-pago-fue-rehusado]
### Funcionalidad y configuración [#funcionalidad-y-configuración]
Algo de tu lado de la cuenta no está listo para recibir este pago. Ninguna de estas depende del pagador.
| Código | Descripción | Causa | Solución sugerida |
| ------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `payins_feature_disabled` | Recaudos deshabilitados para el tenant | La funcionalidad de recaudos no está habilitada en tu cuenta. | Contacta a Mono para habilitarla antes de cobrar. |
| `method_disabled` | Método de pago deshabilitado | El método que usó el pagador está deshabilitado en tu cuenta. | Contacta a Mono para habilitar el método. |
| `method_not_allowed` | Método no permitido aquí | El método está habilitado para ti, pero no permitido para esta plantilla o intención de pago. | Crea la plantilla o la intención de pago con un método que tu programa permita. |
| `subaccount_inactive` | Subcuenta inactiva | La subcuenta que recibiría el dinero no está activa. | Reactiva la subcuenta, o cobra hacia una que esté activa. |
### Condiciones de la plantilla [#condiciones-de-la-plantilla]
La [plantilla](/docs/guides/core/concepts/payins/templates) a la que llegó el pago ya no lo acepta.
| Código | Descripción | Causa | Solución sugerida |
| ------------------------ | ----------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `template_disabled` | Plantilla deshabilitada | La plantilla tiene `enabled: false`, así que no recibe pagos mientras siga así. | Habilita la plantilla, o deja de distribuir su token de pago. |
| `template_invalid_state` | Plantilla no pagable | El estado de la plantilla no acepta pagos (por ejemplo, ya está `paid`). | Revisa el `state` de la plantilla; crea una nueva si el cobro terminó. |
| `template_deleted` | Plantilla borrada | La plantilla fue borrada y sus tokens de pago fueron desmontados. | Crea una plantilla nueva y distribuye su token. |
| `template_expired` | Plantilla expirada | El pago llegó después del `expires_at` de la plantilla. | Crea una plantilla nueva, o extiende la ventana antes de que venza. |
### Condiciones de la intención de pago [#condiciones-de-la-intención-de-pago]
La [intención de pago](/docs/guides/core/concepts/payins/intents) a la que llegó el pago no puede recibirlo.
| Código | Descripción | Causa | Solución sugerida |
| ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `intent_invalid_state` | Intención de pago no pagable | El estado de la intención de pago no acepta pagos: casi siempre porque ya está `successful` y no puede pagarse dos veces. | Crea una intención de pago nueva para un pago nuevo. |
| `intent_expired` | Intención de pago expirada | El pago llegó después de que se cerró la ventana de la intención de pago. Para Bre-B la ventana es de 30 minutos. | Crea una intención de pago nueva; su `external_id` se libera cuando la anterior expira. |
| `intent_amount_mismatch` | El monto no coincide | El monto de la intención de pago se exige, y el pagador envió uno distinto. | Muéstrale el monto exacto. La intención de pago se re-arma, así que puede reintentar con la misma llave. |
### Límites de monto [#límites-de-monto]
El monto está fuera de lo que acepta la plantilla o la intención de pago. Los dos primeros son por pago; los dos últimos son sobre el acumulado ya cobrado.
| Código | Descripción | Causa | Solución sugerida |
| ------------------------------ | ------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `amount_below_minimum` | Bajo el mínimo por pago | El monto está por debajo de `minimum_payin_amount`. | Muéstrale el mínimo al pagador antes de que pague. |
| `amount_exceeds_maximum` | Sobre el máximo por pago | El monto está por encima de `maximum_payin_amount`. | Muestra el máximo, o divide el cobro en varios pagos. |
| `amount_below_total_minimum` | Bajo el mínimo acumulado | El pago dejaría a la plantilla por debajo de `total_minimum_amount` y no puede aceptarse así. | Pide un monto que alcance el mínimo. |
| `amount_exceeds_total_maximum` | Sobre el máximo acumulado | El pago pasaría a la plantilla por encima de `total_maximum_amount`. | Compara `paid_amount` contra el total; sube el total o cierra la plantilla. |
### Restricciones del pagador [#restricciones-del-pagador]
| Código | Descripción | Causa | Solución sugerida |
| ------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `payer_not_allowed` | Pagador no permitido | La plantilla o la intención de pago restringen quién puede pagar con [`expected_payers`](/docs/guides/core/concepts/payins/templates#restringir-quién-puede-pagar), y el pagador no está en la lista. | Verifica el tipo y número de documento que registraste, o quita la restricción. |
### Rechazo del banco [#rechazo-del-banco]
Esto es del lado **receptor**, no del pagador. Antes de aceptar el dinero, Mono le pregunta al banco que tiene la cuenta destino si puede recibir el pago. Un rechazo ahí detiene el pago.
| Código | Descripción | Causa | Solución sugerida |
| --------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `bank_rejected` | Rechazado por el banco receptor | El banco que tiene la cuenta receptora rechazó el pago entrante: la cuenta puede estar desactivada, o el banco ya no le permite recibir dinero. | No es reintentable. El pagador no puede hacer nada al respecto. Revisa con Mono el estado de la cuenta antes de volver a recaudar. |
## Fallido: el pago se rompió [#fallido-el-pago-se-rompió]
### Reportado por el rail de pagos [#reportado-por-el-rail-de-pagos]
El pago ya estaba autorizado cuando el rail reportó una falla. Son condiciones de infraestructura, no decisiones sobre tu cobro.
| Código | Descripción | Causa | Solución sugerida |
| ---------------------- | ----------------------- | --------------------------------------------------------- | -------------------------------------------------------------- |
| `risk_control` | Bloqueado por riesgo | Los controles de riesgo del proveedor detuvieron el pago. | Revisa el patrón del pago. Puede requerir contactar a soporte. |
| `provider_unavailable` | Proveedor no disponible | El proveedor de pagos estaba fuera de servicio. | Nada sobre el pago; el pagador puede reintentar. |
| `breb_timeout` | Timeout de Bre-B | El rail Bre-B no respondió a tiempo. | El pagador puede reintentar. |
| `mono_timeout` | Timeout de Mono | Un sistema interno de Mono no respondió a tiempo. | El pagador puede reintentar. Si persiste, contacta a soporte. |
| `internal_error` | Error interno | Una falla sin clasificar al procesar el pago. | Contacta a soporte con el `id` del pago. |
| `transfer_reversed` | Transferencia revertida | El rail revirtió la transferencia después de aceptarla. | No entregues contra este pago. Concílialo como no cobrado. |
### Reportado por el rail sobre la plantilla [#reportado-por-el-rail-sobre-la-plantilla]
Estas dos son la visión que el rail tiene del cobro detrás de tu plantilla, así que sólo llegan cuando el pago ya está en vuelo.
| Código | Descripción | Causa | Solución sugerida |
| -------------------- | -------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `template_paid` | Ya estaba pagado | El rail encontró el cobro ya pagado: una plantilla de un solo uso sólo puede recibir un pago. | Crea una plantilla nueva para el siguiente pago. |
| `template_not_found` | No existe en el rail | El rail no tiene un cobro para el token al que se dirigió el pago. | Contacta a soporte con el `id` del pago y el `id` de la plantilla. |
### Procesamiento de Mono [#procesamiento-de-mono]
| Código | Descripción | Causa | Solución sugerida |
| -------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ |
| `lock_timeout` | Timeout del lock | Mono no pudo tomar a tiempo el lock de procesamiento de la plantilla: normalmente por muchos pagos simultáneos contra una misma plantilla. | El pagador puede reintentar. Si es frecuente, reparte la carga en varias plantillas. |
| `unknown` | Desconocido | Cualquier otra falla interna, o una razón que el rail reportó sin contraparte de este lado. | Contacta a soporte con el `id` del pago. |
Algunos códigos existen en las dos rutas: `bank_rejected` y los de monto y pagador entre
ellos. Cuando la validación de Mono rehúsa el pago, este queda `rejected`; cuando el rail
reporta la misma falla después de que el pago fue autorizado, queda `failed`. Lee `state`
junto con `state_reason` en vez de ramificar sólo por la razón.
## Cómo manejarlos [#cómo-manejarlos]
### Guía de reintentos [#guía-de-reintentos]
"Reintentable" aquí significa que el pagador puede volver a pagar contra el mismo token de pago. Ningún pago se reintenta por sí solo.
| Código | Reintentable | Recomendación |
| -------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------- |
| `provider_unavailable`, `breb_timeout`, `mono_timeout` | Sí | Pídele al pagador que reintente en unos minutos. |
| `lock_timeout` | Sí | Reintenta de inmediato; el lock dura poco. |
| `intent_amount_mismatch`, `amount_below_minimum`, `amount_exceeds_maximum` | Sí | Muestra el monto correcto y deja que el pagador reintente. |
| `template_expired`, `template_paid`, `intent_expired` | No | El cobro terminó. Crea una plantilla o una intención de pago nueva. |
| `bank_rejected` | No | La cuenta receptora no puede recibir pagos. Resuélvelo con Mono. |
| `payer_not_allowed` | No | Corrige `expected_payers`, o cobra al pagador registrado. |
| `transfer_reversed` | No | El dinero se devolvió. Concilia como no cobrado. |
| `internal_error`, `unknown`, `template_not_found` | No | Contacta a soporte con el `id` del pago. |
### Reaccionar al webhook [#reaccionar-al-webhook]
```ts
app.post('/webhook', (req, res) => {
const { event } = req.body;
if (event.type === 'payin_rejected' || event.type === 'payin_failed') {
const { id, state, state_reason } = event.data;
switch (state_reason) {
case 'amount_below_minimum':
case 'amount_exceeds_maximum':
case 'intent_amount_mismatch':
// Muéstrale al pagador el monto que acepta el cobro
break;
case 'payer_not_allowed':
// Indica que este pagador no es el esperado
break;
case 'template_expired':
case 'template_paid':
case 'intent_expired':
// El cobro terminó: emite uno nuevo
break;
case 'transfer_reversed':
// El rail devolvió el dinero; concilia como no cobrado
break;
case 'bank_rejected':
// La cuenta receptora no puede recibir pagos: alerta a tu equipo, no al pagador
break;
default:
// Infraestructura o desconocido: registra el `id` y deja reintentar
}
}
res.status(200).send();
});
```
Cuando contactes a soporte por un pago, entrega el `id` del pago, el `template_id` o
`intent_id` al que pertenece y, para Bre-B, el `transaction_data.end_to_end_id`, que
identifica la transferencia en toda la red.
## Siguientes pasos [#siguientes-pasos]
* [Errores de plantilla](/docs/guides/core/concepts/payins/templates/errors): por qué una plantilla deja de ser pagable.
* [Errores de intención de pago](/docs/guides/core/concepts/payins/intents/errors): por qué una intención de pago termina sin recibir un pago.
* [Webhooks](/docs/guides/core/webhooks/payins): cada evento de pago y su payload.
# Pagos (/es/docs/guides/core/concepts/payins/payins)
Un **pago** es el registro que Mono crea cuando el dinero realmente llega contra una [plantilla](/docs/guides/core/concepts/payins/templates) o una [intención de pago](/docs/guides/core/concepts/payins/intents). Captura quién pagó, cuánto en cada tramo del recorrido (los [cuatro montos](/docs/guides/core/concepts/payins)), qué token lo recibió y dos máquinas de estado: el ciclo de vida del pago en sí y la entrega de los fondos en tu cuenta de fondeo.
Nunca creas un pago directamente; lo crea la llegada del dinero. Tu integración lee los pagos (`list` y `get`), los concilia contra tus órdenes y reacciona a sus [webhooks](/docs/guides/core/webhooks/payins).
## Ciclo de vida [#ciclo-de-vida]
* **`created`**: el pago fue registrado y está siendo validado y autorizado. Ningún fondo se ha movido a la subcuenta.
* **`awaiting_payment`**: autorizado; el pago está en vuelo en el proveedor. Un estado breve, sin webhook propio.
* **`approved_in_provider`**: el proveedor confirmó el pago. El dinero existe del lado del proveedor; `funds_delivery_status` se define según la configuración de tu programa.
* **`subaccount_credited`**: los fondos se acreditaron en la subcuenta por `subaccount_amount`. Para la mayoría de las integraciones esta es la señal de "pago completo". La entrega en tu cuenta de fondeo se reporta aparte con los eventos del [lote de liquidación](/docs/guides/core/concepts/payins/settlement/funding).
* **`rejected`**: una condición de negocio, configuración o del pagador detuvo el pago. Los fondos del pagador, si los hubo, los devuelve el proveedor.
* **`failed`**: una falla del sistema o del proveedor impidió procesarlo.
## Las sub-entidades del pago [#las-sub-entidades-del-pago]
Tres estructuras viven dentro de cada pago y le pertenecen solo a él: ninguna otra entidad las lleva. Juntas responden las tres preguntas de cualquier pago recibido: *¿quién lo pagó, dónde aterrizó y qué transacción del riel fue?* Cada una es una estructura por método: su `kind` (o el `method` del pago) te dice qué forma leer, así que los métodos nuevos agregan formas sin cambiar el pago.
### Instrument: quién pagó [#instrument-quién-pagó]
`instrument` es el origen del pago del lado del pagador: la cuenta de donde salió el dinero y, dentro de ella, la persona o empresa dueña (`instrument.payer`). Para Bre-B no se recolecta nada de antemano. La red lo entrega con el pago:
| Parte | Forma (Bre-B) | Lleva |
| :----------------- | :---------------- | :---------------------------------------------------------------------------------- |
| `instrument` | `co_breb_account` | Número de cuenta, tipo de cuenta, moneda y el NIT de la institución participante. |
| `instrument.payer` | `co_person` | Nombres, apellidos, tipo y número de documento, y si es persona natural o jurídica. |
Contra esto mismo se valida [`expected_payers`](/docs/guides/core/concepts/payins/templates), y es tu principal herramienta para saber quién pagó (para conciliación, y para las verificaciones de identidad que tu propio compliance corra sobre el dinero entrante).
### Recipient data: dónde aterrizó [#recipient-data-dónde-aterrizó]
`recipient_data` es el lado receptor: a qué [token de pago](/docs/guides/core/concepts/payins/payment-tokens) fue direccionado el pago. Para Bre-B lleva la llave (`key_type`, `key_value`) y el `payment_id` del proveedor (el identificador del código QR, cuando el pagador escaneó uno). Es el punto de unión de vuelta al dueño del token: `key_value` coincide con el `payment_tokens.co_breb.key_value` de la plantilla o la intención de pago que lo emitió, y `payment_id` con el del QR. Úsalo para distinguir campañas cuando varias plantillas recaudan hacia la misma subcuenta, o para diferenciar pagos por llave de pagos por QR.
### Transaction data: qué transacción del riel [#transaction-data-qué-transacción-del-riel]
`transaction_data` identifica el pago en el riel mismo. Para Bre-B lleva el `end_to_end_id`: el identificador de red de la transferencia subyacente, el valor a citar en disputas o cuando rastrees un pago con soporte de Mono o con el banco del pagador.
## Razones de rechazo y falla [#razones-de-rechazo-y-falla]
`state_reason` solo se define en los estados `rejected` y `failed`, y el estado te dice quién detuvo el pago:
| Estado | Qué pasó | Razones |
| :--------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rejected` | Mono o el banco receptor lo rehusaron: una condición de configuración, de la plantilla, de la intención de pago, de monto, del pagador o de la cuenta receptora no se cumplió. | Funcionalidad y configuración, condiciones de la plantilla y de la intención de pago, límites de monto, restricciones del pagador y `bank_rejected`. |
| `failed` | El pago se rompió: el riel reportó una falla después de autorizar, o Mono no pudo terminar de procesarlo. | Fallas del riel y la reversión, más `lock_timeout` y `unknown`. |
Cada código, con su causa y qué hacer al respecto, está en [Errores de pago](/docs/guides/core/concepts/payins/payins/errors).
## Campos de entrega de fondos [#campos-de-entrega-de-fondos]
Más allá de su propio estado, el pago rastrea cómo te llega el dinero real. El detalle vive en [Entrega a tu cuenta de fondeo](/docs/guides/core/concepts/payins/settlement/funding):
* `funds_destination`: `funding_account` significa que los fondos te llegan en un lote de liquidación; `method_receiving_account`, que la entrega se completa de inmediato y el pago nunca aparece en un lote de liquidación.
* `funds_delivery_status`: `null` hasta que el pago queda `approved_in_provider`, luego `pending_to_deliver` → `processing` → `completed`.
* `settlement_batch_id`: el lote de liquidación que entregó los fondos, cuando uno cubre este pago.
`subaccount_disbursement_trigger` registra cuándo se acredita la subcuenta (`on_approval` u `on_funds_delivery`), heredado de la plantilla o la intención de pago que produjo el pago. Ver [Crédito a la subcuenta](/docs/guides/core/concepts/payins/settlement/subaccount).
## Campos de conciliación [#campos-de-conciliación]
`metadata` fusiona el `payin_data.metadata` de la plantilla y de la intención de pago de las que proviene el pago (los valores de la intención de pago ganan). `template_id`, `intent_id`, `subaccount_id` y `funding_account_id` vinculan el pago con todo lo que lo rodea. `calculated_fees` desglosa lo cobrado, por [tipo de cargo](/docs/guides/core/concepts/payins/fees).
## Modelo de datos [#modelo-de-datos]
El schema completo y los filtros de listado viven en la [referencia de API de pagos](/docs/api-reference/core/payins#payins).
## Siguientes pasos [#siguientes-pasos]
* [Liquidación](/docs/guides/core/concepts/payins/settlement): los dos movimientos detrás de cada pago.
* [Cargos](/docs/guides/core/concepts/payins/fees): qué contiene `calculated_fees`.
* [Webhooks](/docs/guides/core/webhooks/payins): los cinco eventos del pago.
# Bre-B (/es/docs/guides/core/concepts/payins/methods/co-breb)
**Bre-B** es el sistema de pagos inmediatos de Colombia, operado por el Banco de la República: transferencias banco a banco que se liquidan en segundos, a toda hora, direccionadas por una **llave** (un alias corto como un número de celular, registrado en un directorio central) o un **código QR**. `co_breb` es el método de Recaudos que opera sobre ese riel: Mono registra una llave por ti, el pagador le paga desde la app de su propio banco, y el dinero se valida y acredita en segundos.
Es el único método de Recaudos disponible hoy, y liquida exclusivamente en COP.
## Cómo funciona un pago por Bre-B [#cómo-funciona-un-pago-por-bre-b]
1. Tu [plantilla](/docs/guides/core/concepts/payins/templates) (o [intención de pago](/docs/guides/core/concepts/payins/intents)) solicita un [token de pago](/docs/guides/core/concepts/payins/payment-tokens) Bre-B. Mono registra la llave ante la red, un paso asíncrono que anuncian los webhooks de `ready` / `ready_to_pay`.
2. Le muestras la llave o el QR al pagador.
3. El pagador paga desde la app de su banco, digitando la llave o escaneando el QR. Sin formulario de checkout, sin datos de tarjeta, sin credenciales de tu lado.
4. La red le presenta el pago a Mono, que lo valida contra tus reglas (montos, pagadores esperados, estado de la plantilla) y lo autoriza o rechaza en la misma interacción.
5. El [pago](/docs/guides/core/concepts/payins/payins) queda registrado y avanza por su ciclo de vida; los webhooks te mantienen informado.
Como el pago se origina en el banco del pagador, la identidad del pagador (`instrument.payer`: nombres, documento, tipo de persona) y su cuenta (`instrument.data`) llegan **con el pago**. Eso es lo que permite exigir `expected_payers` sin pedirle nada al pagador.
## Llaves y códigos QR [#llaves-y-códigos-qr]
| Token | Alcance | Comportamiento |
| :---------------------------- | :---------------------------- | :----------------------------------------------------------------------- |
| Llave (`key_per: "template"`) | Compartida: toda la plantilla | Estática: cualquier pagador, cualquier número de pagos (`multiple_use`). |
| QR estático | Compartido: toda la plantilla | Codifica la llave de la plantilla; sin monto fijo. |
| Llave (`key_per: "intent"`) | Una intención de pago | Creada por pago esperado; ata el pago a la intención de pago. |
| QR dinámico | Una intención de pago | Un solo uso, monto fijo, expira con la intención de pago. |
Los tipos de llave los asigna la red al registrarla: `cellphone`, `email`, `alphanumeric`, `identification` o `merchant_code`. Puedes pedir un alias personalizado con `method_params.co_breb.custom_key_value` y los datos de comercio del QR con `qr_code_data`.
## Límites y tiempos [#límites-y-tiempos]
* **Tope por transacción**: un pago individual por Bre-B tiene un tope de **$12.110.000 COP**. Es el límite por transacción de la red tal como lo aplica Mono; la red lo ajusta periódicamente, así que tómalo como un valor vigente y no como una constante. Un pago por encima lo rechaza el riel. El `maximum_payin_amount` de una plantilla solo puede apretar este tope, nunca superarlo.
* **Ventana de la intención de pago**: una intención de pago Bre-B es pagable durante **30 minutos** después de `ready_to_pay`. Dentro de esa ventana, los intentos fallidos rearman la intención de pago: el pagador puede reintentar contra la misma llave.
* **Liquidación**: los pagos aprobados llegan a tu cuenta de fondeo en [lotes de liquidación](/docs/guides/core/concepts/payins/settlement/funding).
## Siguientes pasos [#siguientes-pasos]
* [Tokens de pago](/docs/guides/core/concepts/payins/payment-tokens): el modelo de llaves y QR en detalle.
* [Flujo de recarga](/docs/guides/core/flows/payins/topup): una llave permanente por subcuenta.
* [Recaudar con intenciones de pago](/docs/guides/core/flows/payins/collect-with-intents): QRs dinámicos por pago.
# Métodos (/es/docs/guides/core/concepts/payins/methods)
Un **método de pago** es el riel por el que viaja un pago, más todo lo que ese riel implica: a qué paga el pagador, en qué monedas liquida, sus límites y cuánto sabe Mono del pagador. Las plantillas declaran los métodos que aceptan en `available_methods`; cada pago registra el que efectivamente lo entregó en `method`.
Hoy hay un método disponible. La matriz crece una columna por método a medida que se lancen nuevos, sin cambiar plantillas, intenciones de pago ni pagos.
## Matriz de métodos [#matriz-de-métodos]
| Capacidad | [`co_breb`](/docs/guides/core/concepts/payins/methods/co-breb) |
| :------------------------------- | :------------------------------------------------------------------------------------------------- |
| Riel | Bre-B, el sistema de pagos inmediatos de Colombia |
| El pagador paga a | Una llave Bre-B o un código QR ([token de pago](/docs/guides/core/concepts/payins/payment-tokens)) |
| Moneda de liquidación | COP |
| Datos del pagador (`instrument`) | Llegan con el pago; no se recolecta nada de antemano |
| Validación de `expected_payers` | Sí: contra el documento del pagador que llega con el pago |
| Ventana de la intención de pago | 30 minutos desde `ready_to_pay` |
| Reintento tras intento fallido | Sí: la intención de pago se rearma mientras viva |
| Límite por transacción | Tope de la red Bre-B (ver [Bre-B](/docs/guides/core/concepts/payins/methods/co-breb)) |
## Configuración del programa [#configuración-del-programa]
Habilitar los recaudos es un setup a nivel de programa que acuerdas con Mono. No hay un switch de autoservicio. Cuando lo solicites, prepárate para definir, por método:
* **Métodos y monedas**: con qué métodos recauda tu programa, y en qué monedas.
* **Cuenta de fondeo**: la cuenta bancaria donde aterriza el dinero real. Ver [Entrega a tu cuenta de fondeo](/docs/guides/core/concepts/payins/settlement/funding).
* **Trigger por defecto**: el `subaccount_disbursement_trigger` del programa (las plantillas y las intenciones de pago pueden sobreescribirlo).
* **Periodos de gracia del pruning**: cuánto sobreviven las plantillas a la expiración o la inactividad antes de descartarse.
* **Identidad de comercio**: el nombre de comercio por defecto que ve el pagador y, para Bre-B, los datos de comercio del QR (código de categoría, ciudad, código postal) y las reglas de nombrado de llaves.
## Siguientes pasos [#siguientes-pasos]
* [Bre-B](/docs/guides/core/concepts/payins/methods/co-breb): el método disponible hoy, en detalle.
* [Tokens de pago](/docs/guides/core/concepts/payins/payment-tokens): llaves y QRs a través de los métodos.
* [Flujos](/docs/guides/core/flows/payins): qué construir con ellos.