Developers API & Widget
EN

Payments

Payment intents and idempotency

A payment intent makes the economic terms of a transaction immutable before the user interacts with the widget. This prevents a malicious client from changing the amount, destination account or payment reference in the frontend.

Creation parameters (POST /payments/?action=create)

1. Standard merchant model (own beneficiary)

In this model, the merchant specifies all collection details:

Field Type Required Description and rules
amount_minor Integer Yes Amount in minor units (cents). For example, 1500 represents 15,00 EUR.
currency String Yes Three-letter ISO 4217 currency code. Currently EUR.
beneficiary Object Yes Destination account details: name (account holder, 1–140 characters) and iban (valid IBAN without spaces, with its checksum verified). Only these two keys are accepted.
reference String Yes Payment reference shown on the bank statement (maximum 140 characters).
customer_reference String Yes Internal order or customer identifier (1–128 characters: letters, numbers, ., _, :, -; must begin with a letter or number).
allowed_origin String Yes Exact HTTPS origin that will embed the widget (e.g. https://tienda.example.com), without a path.
allowed_institution_codes Array No List of permitted institution codes (e.g. ["santander-es", "bbva-es"]). If omitted, any institution in the catalogue is allowed.
locale String No Widget interface language. This version only accepts es; any other value returns 422 invalid_locale.
expected_mode String No mock or live. Checks that you are calling the intended environment; it does not switch environments. A mismatch returns 409 payment_mode_mismatch.

The body uses a strict allowlist: sending a field not listed in this table returns 422 invalid_request, as does omitting a required field. The Content-Type: application/json header is mandatory (415 json_required), and the body is limited to 32 KB.

2. Managed profile model (donations / demos)

For regulated use cases or public demos such as cruz_roja_demo, the server enforces the financial rules and fixes the official destination accounts:

Field Type Required Description and rules
profile String Yes Profile identifier (e.g. cruz_roja_demo).
institution_code String Yes Code of the institution selected by the user (from profile-institutions).
amount_minor Integer Yes Amount bounded by the profile policies (e.g. 1–100 cents).
customer_reference String Yes Your system's own audit reference.
allowed_origin String Yes HTTPS origin of the widget.
locale String No Widget language (es).
expected_mode String No Expected mode (mock or live).

When profile is specified, the server automatically assigns the official beneficiary and the corresponding payment reference. Do not send beneficiary or reference in managed profile requests.

The Idempotency-Key rule

The Idempotency-Key header is mandatory for every creation request. It must contain 16–128 visible ASCII characters, without spaces (for example, a UUID v4). Its scope is limited to the authenticated company:

  • Same key and same body: returns the original, previously created intent with idempotent_replay: true. No new charge is created and the bank order is not duplicated.
  • Same key and different body: immediately returns HTTP 409 Conflict.
  • Same key in another company: belongs to a completely isolated idempotency namespace.

If a creation request is interrupted by a network failure or timeout, retry with exactly the same Idempotency-Key header and the same body. Do not generate a new key for a transient error.

Persistence and lifecycle

  1. Persist first: The intent is recorded in the database before returning the response or interacting with any bank connector.
  2. Ephemeral widget token: The response token (payment.widget.token) has a short lifetime (typically 15–30 minutes) and can only be used from the declared allowed_origin.
  3. Concurrency control: The system uses optimistic concurrency control and leases to prevent two simultaneous requests from authorizing or changing the same intent.

Common error codes

HTTP Code Cause Recommended action
400 idempotency_key_required The Idempotency-Key header is missing or malformed. Generate a valid key of 16–128 visible ASCII characters, without spaces.
409 idempotency_conflict The key has been reused with different payment details. Generate a new key for different payments, or reuse the identical body.
409 payment_mode_mismatch expected_mode does not match the environment you are calling. Check whether you are targeting sandbox or production; the deployment sets the mode, not the request.
401 invalid_api_key The X-API-Key header is missing or malformed, or the credential does not exist or is inactive. Check the credential. Never include it in the frontend.
403 payments_not_allowed The API key does not have the PAYMENTS product enabled. Contact Wealth Reader support to enable payments for your account.
403 payment_profile_not_allowed The managed profile is not enabled for your company. Ask Wealth Reader to enable the profile.
429 api_limit_reached Your credential has exhausted its cumulative call counter. Retrying or waiting will not resolve this: there is no time window or automatic replenishment. Request a higher limit.
415 json_required The Content-Type: application/json header is missing. Send the body as JSON.
422 invalid_request A required field is missing or an unrecognized field was sent. Check the parameter table: the body uses a strict allowlist.
422 invalid_institution The selected institution is unavailable or invalid. Query GET /payments/entities/ for valid codes.
422 invalid_amount The amount is below the minimum (€0.01) or is not an integer. Check that you are sending an integer in cents (amount_minor).

Next step

Continue with Statuses, polling and reconciliation.

Last updated