SCA Factors & Implementation

The diagram below shows a high-level workflow of how your application user journey at the least must enforce in order to comply with SCA requirements.


Instead of enforcing a UI component which will authenticate Striga (LSPEU) directly with the end user, such that we manage sessions, we have taken a legally heavier approach in not interfering directly with whatever user/password management flows your application may already have setup. This is the approach developed by our legal/compliance team and requires regulatory notification on our end, however preserves UX with the least invasive possible components.

In outsourcing password management and user session management to you, us as the licensed provider in the flow are at risk and depend upon your implementation of these APIs to remain compliant. Namely, please ensure to note the following -

  • We do not manage user passwords and hence will need to be made aware when a user successfully completes a forgot password workflow on your application or has a failed login attempt. We are legally required to track failed login attempts to enforce a time based lockout and expect your application to hit our /user/record-event to notify us of these events. More information will be shared by compliance about what happens in various scenarios as part of our onboarding process.
  • Please do not at any point in time store TOTP/Passkey secrets and attempt to circumvent SCA provisions by acting on behalf of the user. This comes with high fines and penalties.
  • Ensure the implementation of a strong password/OAuth/login mechanism and industry best practices to secure user accounts.
  • When a user wishes to reset their SMS OTP factor (i.e. change mobile number), their TOTP factor or Passkey, we are required to enforce that it is the user acting on their own behalf, prompting an automated liveness check on such events. A passkey reset may be aimed at one credential: pass the credentialId from /user/passkey/list on /user/2fa/reset/initiate and only that passkey is removed, with the reset closing in the /complete call; without it every passkey is removed and the replacement registration closes the reset.
  • Removing a passkey is itself an SCA event: /user/passkey/delete binds a challenge to the credential and returns the assertion request for that passkey alone, and /user/passkey/delete/confirm must be answered with an assertion by the passkey being removed; no other factor is accepted. Users may hold up to two passkeys. See Managing passkeys on the Strong Customer Authentication page.
  • Adding a factor is itself a sensitive action: /user/start/setup-two-factor, /user/passkey/register/start and /user/passkey/import/start require an active SCA session granted within the last 15 minutes, else 30070 (SCA_REQUIRED, reason NO_SESSION or EXPIRED, exactly as the SCA-gated reads report it). Run /user/login/start + /user/login/complete and retry inside the window. An open 2FA reset for that factor whose /complete call has already removed the old factor authorizes the call as well (that is how a native app re-imports its device key after a reset), and users without an SCA-gated (EUR/USDC) account yet are exempt.
  • Native apps may enrol a hardware-backed, biometric-gated key as a passkey through /user/passkey/import/start + /user/passkey/import/complete. Attestation (Apple App Attest, Android Key Attestation) is mandatory and your application must be configured for it per platform; keep SMS or TOTP enrolled as the fallback, because attestation fails on a small share of genuine devices and the key does not survive a reinstall or a biometric re-enrolment. See Device-bound passkeys (native apps).
  • We recommend using the concept of "Trusted Beneficiaries" and "Standing Orders" to enable the smoothest possible UX for automated flows.

Lifetimes and limits

ItemValue
SCA session180 days
Enrolment session window15 minutes from the session grant (30070 EXPIRED once older)
History sub-window90 days
SMS code5 minutes
TOTP / passkey challenge10 minutes
Passkey removal challenge10 minutes
Passkey import challenge10 minutes, single use
Attempt cap per challenge5
Resend (SMS)Twilio-managed; deduped per channel
Resend (passkey / TOTP)Not supported (31100 for passkey; TOTP has no server challenge)

Error codes

CodeMeaning
30031Invalid verification code, or Dynamic Linking mismatch (the response reason field disambiguates).
30043User is suspended; SCA flows reject.
30070SCA session required or expired (reason NO_SESSION / EXPIRED); the enrolment start endpoints return it too once the session is older than 15 minutes. Re-authenticate via /user/login/start + /user/login/complete.
30073whitelistedIbanId or whitelistedAddressId not found or not ACTIVATED.
30074verificationMethod was supplied but the application's scaEnabled is not true.
30075Address trust is only supported for USDC, USDC_POLYGON, USDC_SOL.
31097Requested factor is not enrolled for this user.
31099Factor is not in the application's allowedScaMethods.
31100Resend not supported for passkey.
64009Whitelisted USDC address must be trusted before creating a standing order.
64010Standing-order factor not enrolled.
64011Whitelisted IBAN must be trusted before creating a CRYPTO_TO_FIAT standing order.
100004Passkey limit reached. A user may hold at most two passkeys; remove one with /user/passkey/delete + /user/passkey/delete/confirm before enrolling another.
100010Device-bound passkeys are not configured for this platform on the application. Contact Striga with the Team ID + bundle ids (iOS) or package name + signing digests (Android).
100011Passkey import proof invalid: a proof.clientData member does not match the challenge or key, the public key cannot be parsed, or the signature does not verify.
100012Device attestation failed on /passkey/import/complete; the message names the failing step (NONCE, CHAIN, POLICY, APP_ID, PACKAGE, KEY_BINDING, …).
999995Challenge unknown, expired or already used - on passkey removal (challengeId) and on passkey import (challengeId / platform do not match the import in progress). Start again.
999998Generic factor error: on passkey removal, credential not found, assertion not made by the passkey being removed, assertion rejected, challenge consumed or too many attempts; on passkey import, the credentialId is already in use (the message field disambiguates).