Mono Colombia

Intents

A single expected payment, declared before the funds arrive.

An intent is one expected payment, declared before the money moves. Where a template 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

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. For PSE, any template that offers co_pse can host intents, and every PSE payment goes through one.
  • 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.

PSE intents

A PSE intent works like any other, with three differences:

  • It carries the payer upfront. Send instrument (the payer's bank and details) and method_params.ip_address. The PSE page lists every field.
  • Its payin exists from the start. Mono creates the payin together with the intent, in created, and announces it with payin_created.
  • It allows one attempt. A PSE intent never re-arms: if the payment fails, the intent ends failed. Create a new intent to let the payer try again.

When something PSE needs is missing or invalid, creation fails:

ResponseWhen
422, with a path to the fieldA field is missing or malformed: no instrument, no method_params, an IPv6 address, an invalid email or phone number.
400 payin_instrument_requiredinstrument is null.
400 payin_invalid_method_paramsmethod_params holds values PSE cannot use.
400 payin_unsupported_bankbank_code is not in the PSE bank catalog.
400 payin_expected_payers_not_supportedThe intent names expected payers, which PSE cannot enforce.

Amounts

Provide exactly one of merchant_amount or payer_amount; the other is derived. With the two methods available today (Bre-B and PSE, both in 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. With PSE, the payment link is generated for the intent's amount, so the payer cannot pay a different one. Send PSE amounts in whole pesos, a multiple of 100 in the API's cents: the PSE network settles whole pesos only.

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. For PSE, payment_tokens.pse_url carries the payment link, and the window is 20 minutes.
  • awaiting_payment: a payment for the intent was authorized and is in flight. A PSE intent moves here right after ready_to_pay, once its payment link is generated. It does not expire in this state: it waits for PSE to report the outcome.
  • 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, such as PSE. In that case, the intent keeps the payin's reason, for example payer_insufficient_funds.
  • 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.

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. PSE does not re-arm: one intent, one attempt.

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

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. With PSE, the checkout creates the intent before the payer fills in the form, so instrument stays null until they send it. If they do not send it within 20 minutes, the intent expires. See Hosted checkout or API-only.

Fees

An intent's calculated_fees arrives empty. The fees actually charged appear on the payin.

Data model

The full schema lives in the Intents API reference.

Next steps

On this page