Strong customer authentication (redirect)
PaySaxas offers the redirect approach only. The embedded and decoupled approaches are not implemented and will not be — the customer authenticates on our surface, and you never see, relay or store their credentials.
The flow
sequenceDiagram
participant T as Your service
participant P as PaySaxas XS2A
participant C as Customer (PSU)
participant A as PaySaxas auth surface
T->>P: POST /v1/consents (or /v1/payments/...)
P-->>T: 201 + consentId/paymentId + _links.scaRedirect
T->>C: send them to scaRedirect
C->>A: authenticates (password + SMS OTP)
A->>C: shows exactly what is being granted
C->>A: approves or declines
A->>C: redirect to your TPP-Redirect-URI?state=...
C->>T: returns with state
T->>P: GET .../status
P-->>T: valid | rejected | RCVD → ACSP ...
What the customer sees
For a consent: your name, the data types requested, the accounts (only those they hold), and the validity period.
For a payment: the amount and the payee, bound into the authentication itself — see dynamic linking below.
Dynamic linking (payments)
A payment ceremony is cryptographically bound to this amount and this payee. The binding is computed from the persisted payment, so an authorisation cannot be replayed against different money. If the payment changes after the ceremony was created, the authorisation fails — by design.
The state parameter
Unusually, we generate state, not you. Most OAuth-shaped flows have the
client mint it; here the authorisation resource is ours, so the value is
created when the authorisation is created and handed to you twice — appended
to the scaRedirect link, and appended again to your redirect URI when the
customer returns.
| Source | Generated by PaySaxas when the authorisation is created |
| Format | UUID v4 — 36 characters, lowercase hex with hyphens, [0-9a-f-] |
| Lifetime | The authorisation’s own: 15 minutes, or until it is finalised, declined or expires |
| Reuse | Never. A new authorisation always carries a new value |
| Where you get it | The scaRedirect href, as ?state=… |
| Where it comes back | Your TPP-Redirect-URI, as ?state=… |
What to do with it. Store it against the pending authorisation on your
side — keyed by consentId or paymentId — and when the customer returns,
compare the value on the incoming request with the one you stored. If they
differ, or you hold no pending authorisation for it, discard the callback.
That comparison is the whole point: without it, anyone who can reach your
redirect URI can claim a customer just approved something. It is not proof
that the approval succeeded — always confirm with GET .../status — but it
ties the return to a flow you actually started.
Treat it as opaque. The format above is what we emit today; do not parse it, derive anything from it, or assume it stays a UUID.
Redirect URIs
- Must be absolute https, with no fragment.
- Bound when the authorisation is created and never re-read afterwards. A redirect URI sent on a later request is ignored, which is what stops an authorisation being pointed somewhere else mid-flight.
- We append the
statewe generated. Verify it before trusting the return.
Timing
An authorisation lives for 15 minutes. After that the customer sees an expiry message and you must start again — the link is not renewable, since a stale approval prompt is exactly what a social-engineering attack wants.
Polling scaStatus
GET /v1/consents/{consentId}/authorisations/{authorisationId}
GET /v1/payments/{product}/{paymentId}/authorisations/{authorisationId}
Values: received, started, psuAuthenticated, finalised, failed.
Where authorisationId comes from
Take it from the creation response. POST /v1/consents and
POST /v1/payments/{product} both return authorisationId as a top-level
field, next to consentId / paymentId:
{
"consentId": "…",
"consentStatus": "received",
"authorisationId": "AUTH-…",
"_links": { "scaRedirect": { "href": "…" }, "scaStatus": { "href": "…" } }
}
If you no longer hold it, list the sub-resources:
GET /v1/consents/{consentId}/authorisations
GET /v1/payments/{product}/{paymentId}/authorisations
→ { "authorisationIds": ["AUTH-…"] }
Do not parse the id out of the scaStatus href. It appears there, but URL
shape is not part of this contract and may change without a breaking-change
notice; the field and the list above are the supported routes. Either follow
_links.scaStatus as an opaque href, or use the id — not a substring of one.
Only the implicit authorisation start is offered — POST /v1/consents
already returns scaRedirect, so there is no separate authorisation resource to
create. The templated sub-resource exists for polling only.
When SCA is not repeated
Under RTS Art. 10a (as amended by (EU) 2022/2360), account data stays readable for 180 days after the customer’s authentication, without a fresh ceremony. The four-per-day unattended cap still applies throughout.
Payments always require a ceremony. We do not offer TRA, low-value or trusted-beneficiary exemptions on this channel.