Payments
Payment integration and widget
Payment integration with Wealth Reader has two phases: preparing the immutable intent in your backend and mounting the secure widget in the frontend.
The API key (X-API-Key) must never appear in HTML, client-side JavaScript, browser logs or URL parameters.
1. Bank directory
To find available banks and obtain their logos, names and requirements, query the institutions endpoint directly from your backend. Each institution includes two logos: logo, the one Wealth Reader recommends displaying (its own vector logo when available, otherwise the provider's, as indicated by logo_source), and logo_fallback, the provider's logo, for your interface to use if the first fails to load:
curl --request GET 'https://api.wealthreader.com/payments/entities/?country=ES'
This endpoint is public: do not send X-API-Key. Doing so does not change the response, consumes a call from your quota and, if payments were disabled for your account, would turn a working query into a 503.
It also accepts optional query parameters:
country: two-letter ISO code (e.g.ES,FR,DE,IT,PT...).ALLor an empty value disables country filtering.search(aliasq): text search by name or code (e.g.santander,bbva).code: retrieves a specific institution by its exact code.payment_method: filters by supported method (e.g.sepa_credit_transfer).limitandoffset: result pagination.
Abbreviated example response:
{
"success": true,
"total": 2,
"entities": [
{
"code": "santander-es",
"name": "Banco Santander",
"country": "ES",
"logo": "https://cdn.wealthreader.com/santander.svg",
"logo_fallback": "https://assets.exthand.com/bsdk/banks/logos/ES/santander.svg",
"logo_source": "wealthreader",
"payment_methods": ["sepa_credit_transfer", "instant_sepa_credit_transfer"],
"requires_debtor_iban": true
},
{
"code": "bbva-es",
"name": "BBVA",
"country": "ES",
"logo": "https://cdn.wealthreader.com/bbva.svg",
"logo_fallback": "https://assets.exthand.com/bsdk/banks/logos/PT/bbva.svg",
"logo_source": "wealthreader",
"payment_methods": ["sepa_credit_transfer", "instant_sepa_credit_transfer"],
"requires_debtor_iban": false
}
]
}
Recommended pattern for displaying a logo with automatic fallback:
<img src="https://cdn.wealthreader.com/santander.svg"
data-fallback="https://assets.exthand.com/bsdk/banks/logos/ES/santander.svg"
alt="Banco Santander" width="160" height="48"
onerror="if (this.dataset.fallback && this.src !== this.dataset.fallback) { this.src = this.dataset.fallback; } else { this.hidden = true; }">
In /payments/entities/, both fields are always present and are null when no logo exists. Institutions returned by POST /payments/?action=profile-institutions and the widget use the same logo and logo_fallback as optional fields: they are absent when no usable logo exists. Logos point only to cdn.wealthreader.com or assets.exthand.com; if your page uses CSP, add both hosts to img-src.
Managed profiles (such as the donation demo): If you use a preconfigured profile such as
cruz_roja_demo, query the institutions and conditions fixed by the server by callingPOST /payments/?action=profile-institutionswith the body{"profile": "cruz_roja_demo"}.
2. Create a payment intent in the backend
When the customer decides to pay at checkout, your server generates a unique idempotency key and requests immutable payment creation from the Wealth Reader API.
Standard merchant model (own beneficiary)
The merchant specifies its receiving account, the amount in cents (amount_minor), the payment reference and the web origin where the widget will load:
: "${WR_API_KEY:?Defina WR_API_KEY en el entorno seguro de su backend}"
: "${WR_PAYMENT_IDEMPOTENCY_KEY:?Genere una clave UUID v4 o de alta entropía para este intento}"
curl --request POST 'https://api.wealthreader.com/payments/?action=create' \
--header 'Content-Type: application/json' \
--header "X-API-Key: ${WR_API_KEY}" \
--header "Idempotency-Key: ${WR_PAYMENT_IDEMPOTENCY_KEY}" \
--data '{
"amount_minor": 1500,
"currency": "EUR",
"beneficiary": {
"name": "Comercio Online S.L.",
"iban": "ES9121000418450200051332"
},
"reference": "Pedido #78901",
"customer_reference": "pedido-78901",
"allowed_origin": "https://tienda.example.com",
"allowed_institution_codes": ["santander-es", "bbva-es", "caixabank-es", "sabadell-es"],
"locale": "es"
}'
Note: allowed_institution_codes is optional. If omitted, the user can choose any institution in the catalogue.
Managed profile model (donation demo)
If you integrate the cruz_roja_demo managed profile:
curl --request POST 'https://api.wealthreader.com/payments/?action=create' \
--header 'Content-Type: application/json' \
--header "X-API-Key: ${WR_API_KEY}" \
--header "Idempotency-Key: ${WR_PAYMENT_IDEMPOTENCY_KEY}" \
--data '{
"profile": "cruz_roja_demo",
"institution_code": "santander-es",
"amount_minor": 100,
"customer_reference": "donativo-demo-0042",
"allowed_origin": "https://tienda.example.com",
"locale": "es",
"expected_mode": "live"
}'
API response
The API responds by confirming the immutable intent and providing the data needed to initialize the widget:
{
"success": true,
"payment": {
"id": "11111111-1111-4111-8111-111111111111",
"amount_minor": 1500,
"currency": "EUR",
"state": "ready",
"interaction_status": "not_started",
"payment_status": "not_initiated",
"payment_attestation": {
"mode": "live",
"provider_binding": "PROVIDER_BINDING"
},
"widget": {
"url": "https://widget.wealthreader.com/payments/",
"token": "SHORT_LIVED_WIDGET_TOKEN",
"expires_at": "2026-09-04T15:30:00+00:00"
}
}
}
Your backend must deliver only payment.id and payment.widget.token to the user's browser.
3. Mount the widget in the frontend
There are two ways to mount the widget in your web interface: the official declarative script or the programmatic JavaScript API.
Option A: Declarative script (load-payments.js)
Embed the container and loader in your checkout page:
<div id="wr-payment-container"></div>
<script>
document.querySelector('#wr-payment-container').addEventListener(
'wealthreader:payment',
(event) => {
console.log('Evento de pago recibido:', event.detail.type, event.detail);
if (event.detail.type === 'payment_status') {
console.log('Estado actual:', event.detail.status);
}
if (event.detail.type === 'flow_closed') {
// La interacción del usuario ha finalizado.
// Consulte el estado financiero definitivo desde su backend.
}
}
);
</script>
<script
src="https://widget.wealthreader.com/js/load-payments.js"
data-target="#wr-payment-container"
data-payment-intent-id="11111111-1111-4111-8111-111111111111"
data-widget-token="SHORT_LIVED_WIDGET_TOKEN"
data-locale="es">
</script>
Option B: JavaScript API (WealthReaderPayments.mount)
If you use frameworks such as React, Vue or Angular, or an SPA flow:
import { useEffect, useRef } from 'react';
// Cargue previamente https://widget.wealthreader.com/js/load-payments.js
const target = document.getElementById('wr-payment-container');
// Los eventos llegan como CustomEvent del DOM sobre el propio contenedor.
target.addEventListener('wealthreader:payment', (event) => {
const detail = event.detail;
if (detail.type === 'payment_status') {
console.log('Estado del pago:', detail.status);
}
if (detail.type === 'flow_closed') {
// Notificar al backend para comprobar la liquidación
}
});
window.WealthReaderPayments.mount({
target: target,
paymentIntentId: '11111111-1111-4111-8111-111111111111',
widgetToken: 'SHORT_LIVED_WIDGET_TOKEN',
locale: 'es'
});
mount() does not accept any callbacks: its keys are target, paymentIntentId, widgetToken, locale, widgetOrigin and apiOrigin. Any other key is silently discarded, so passing onEvent in the configuration would neither raise an error nor ever execute. Always listen for the container's wealthreader:payment event.
The widget obtains its configuration directly from Wealth Reader's servers using the single-use token. If the bank requires the debit account to be identified before redirecting, the widget requests the debtor IBAN directly from the payer without the merchant having to process it.
Main widget events
type |
Meaning |
|---|---|
ready |
The widget has initialized and immutable information is available. |
authorization_started |
The user has started bank authorization (redirected to SCA or the banking app). |
processing |
Bank authentication has finished and the system is processing the operation. |
payment_status |
Reports a payment status change (pending, settled, rejected, etc.). |
height_changed |
Dynamic iframe height adjustment to avoid scrollbars. |
flow_closed |
The user has closed the widget or the technical interaction has ended. |
Remember that flow_closed only confirms window closure; it does not constitute payment confirmation. Your backend must always verify the status through the server-to-server call.
Next step
Review Intents and idempotency.