Verification Risk Tiers

Introduction

When a cardholder links a card, Astrada verifies that they own it. You can choose how strict that verification is per subaccount, using a risk tier. A higher tier adds stronger proof of ownership at the cost of more cardholder interaction; a lower tier optimizes for conversion.

This page explains the four tiers, how to set one, and how the tier changes the enrollment experience. For the strictest tier's two-hold flow see HIGHEST Verification; to test against sandbox cards see Sandbox Testing & Test Cards.

The tiers

TierStrengthCardholder experienceWho can set it
LOWLenient3DS only when the issuer or regulation requires it; some issuer-unsupported declines are toleratedAstrada (internal)
MEDIUMStandard (default)3DS is always requested; cardholder may be challengedYou or Astrada
HIGHStrong3DS is always requested plus a transient authorization hold (no settled charge)You or Astrada
HIGHESTStrongest3DS challenge; if the issuer approves without challenging, the cardholder confirms two small temporary holdsYou or Astrada

New subaccounts default to MEDIUM. LOW is internal-only — it is a floor that Astrada manages; you can always raise a subaccount to a stricter tier, but you cannot self-assign LOW.

How each tier behaves

In every Stripe-backed tier, the card's CVC must pass.

  • LOW — Astrada asks the network to run 3DS only when risk or regulation (for example EU SCA) calls for it; for many cards 3DS is skipped. If the issuer declines for a "soft" reason such as 3DS not supported, the enrollment is still allowed; hard-decline reasons (lost/stolen/fraud, expired, insufficient funds, and similar) always fail.
  • MEDIUM — 3DS is always engaged and the issuer is asked to challenge the cardholder. There is no soft-decline tolerance: a declined verification surfaces a specific reason to the cardholder.
  • HIGH — same 3DS posture as MEDIUM, plus a transient authorization hold on the card (requested at $0, retried at $1 if the issuer requires a non-zero amount). The hold is released automatically — the cardholder never sees a settled charge — and Astrada gains additional fraud-risk signals from the authorization.
  • HIGHEST — Astrada attempts a 3DS challenge. If the issuer challenges the cardholder, the challenge itself is sufficient and enrollment completes. If the issuer approves frictionlessly (no challenge), the cardholder must complete a second factor: confirm two small temporary holds ($0.50–$0.99 each) read from their banking app. See HIGHEST Verification.

3DS is a preference, not a guarantee. For MEDIUM/HIGH/HIGHEST, Astrada asks the issuer to challenge, but the issuer decides the final authentication flow and may still approve frictionlessly. The verification response reports what actually happened via authenticationFlow (see below).

Card networks

The tier you set applies to both Visa and Mastercard, but the underlying provider differs:

  • Visa — all tiers (LOW/MEDIUM/HIGH/HIGHEST) run through Astrada's Stripe verification path.
  • Mastercard — 3DS always runs through Astrada's Mastercard network path at every tier. The Stripe-backed second factor is engaged only at HIGHEST (for the two-hold confirmation); LOW/MEDIUM/HIGH behave as standard Mastercard 3DS.

How verification runs

For MEDIUM/HIGH/LOW the cardholder either sails through (frictionless) or completes a bank challenge — the SDK performs every step below for you; the diagram shows what happens under the hood. (HIGHEST adds a second factor — see HIGHEST Verification.)

sequenceDiagram
    autonumber
    participant Browser as Cardholder browser
    participant Backend as Your app (via the SDK)
    participant Astrada as Astrada API
    participant StripeJS as Stripe.js
    participant ACS as Issuer (ACS)
    Browser->>Backend: Cardholder submits card
    Backend->>Astrada: POST /card-verifications/3ds
    Astrada->>Astrada: Card + CVC checks, then SetupIntent or PaymentIntent (per tier)
    alt Frictionless
        Astrada-->>Backend: state = completed (no challenge)
        Backend-->>Browser: Success
    else 3DS challenge required
        Astrada-->>Backend: currentStepId = stripe-3ds + clientSecret
        Browser->>StripeJS: handleNextAction(clientSecret)
        StripeJS->>ACS: Render bank challenge
        ACS-->>StripeJS: Cardholder completes / fails
        Browser->>Backend: Notify result
        Backend->>Astrada: POST /steps/stripe-callback
        Astrada-->>Backend: state = completed | failed
        Backend-->>Browser: Final outcome
    end

Which provider runs the verification depends on the network and the tier:

flowchart TD
    A[Verification on a subaccount] --> B{Subaccount tier set?}
    B -- no --> D[Default path:<br/>TokenEx/IXOPAY Visa · TNS Mastercard]
    B -- yes --> C{Network}
    C -- Visa --> E[Stripe at the set tier]
    C -- Mastercard --> F{Tier = HIGHEST?}
    F -- yes --> G[TNS 3DS + Stripe two-hold second factor]
    F -- no --> H[TNS 3DS only]

What each path validates

Every Stripe-backed tier runs explicit card-existence, card-status, and CVC checks on each attempt — at every tier, including LOW — alongside the issuer's 3DS decision, rather than inferring them from it.

ValidationDefault (no tier)LOWMEDIUMHIGH
Card exists / activefrom 3DS responseexplicitexplicitexplicit
CVC matchesinferredstrictstrictstrict
3DS authenticationrequired (+ bypass rules)automatic (engaged only when SCA/risk warrant)challenge preferredchallenge preferred
Authorization holdnonenonenone$0 → $1 fallback, voided (never settles)
Stripe fraud signalsn/an/an/acaptured
  • The floor is identical for LOW/MEDIUM/HIGH: card existence, active status, and CVC are always validated. LOW is "lenient" only in that it permits a few issuer-side signals (the contact-issuer family, and "issuer can't do 3DS for this card") to bypass to success — it does not skip the card check, and it never bypasses a CVC mismatch.
  • HIGH is incrementally stricter than MEDIUM: the auth hold surfaces extra decline signals (insufficient funds, velocity) plus Stripe's fraud layer.

Setting a subaccount's tier

Set the tier on a subaccount's verificationPolicy using the Update Subaccount endpoint:

PATCH /subaccounts/{subaccountId}
Authorization: Bearer <token with subaccounts:write>

{
  "verificationPolicy": {
    "stripeValidationLevel": "HIGH"
  }
}
  • Accepts MEDIUM, HIGH, or HIGHEST. Requesting LOW returns 403 — contact Astrada to set a LOW floor.
  • Set stripeValidationLevel to null to clear the override and return to Astrada's default path.
  • The policy is per subaccount. There is no inheritance from the parent account at request time — set the tier on each subaccount you want to change.

An optional safeguard sits alongside the tiers: with verificationPolicy.failedAttemptLockout enabled, a card that fails verification too many times is throttled across all networks until it clears (or you clear it). It applies to MEDIUM/HIGH (HIGHEST has its own per-card lockout). See Verification Attempt Lockout.

Every completed verification reports how 3DS resolved:

authenticationFlowMeaning
challengeThe issuer challenged the cardholder (they interacted).
frictionlessThe issuer approved without challenging the cardholder.
null3DS did not run (for example, a LOW skip).
{ "id": "…", "state": "completed", "currentStepId": null, "authenticationFlow": "frictionless" }

At HIGHEST, authenticationFlow is the branch point: challenge completes immediately; anything else routes the cardholder into the two-hold confirmation described in HIGHEST Verification.

What the cardholder sees

The happy paths and routing are below; every rejection and error state lives in Error States & Remediation. Unless noted, the screen is the same across LOW / MEDIUM / HIGH; HIGHEST adds the two-hold second factor covered in HIGHEST Verification.

Happy paths

Frictionless success — most enrollments

The bank approves without a challenge; the cardholder reaches success in a few seconds and sees no charge. Same UI at every tier (HIGH may briefly show a $0/$1 pending hold that never settles).

Pre-submitResult

3DS challenge success

The bank asks for an extra check — an OTP, an "approve in your app" prompt, or a password. The challenge UI is rendered by the bank, so copy and method vary by issuer.

Pre-submitResult

Rejections, errors & recovery

Every failure path — CVC mismatch, bank declines, the contact-issuer family, 3DS authentication failures, hard-fraud rejections, infrastructure errors, and mid-challenge recovery — plus the structured error contract (errorCode / category / retryable) and remediation for each, is catalogued in Error States & Remediation.

Routing

Account isn't on Stripe → default flow

Most existing customers have no tier set; their cardholders get the same default verification flow as before — no Stripe iframe, no new copy. Setting a tier is the only way onto the Stripe path.

Pre-submitResult

Glossary

  • 3DS (3-D Secure) — the protocol issuers use to confirm the cardholder (OTP, in-app approval, or password). The challenge UI is rendered by the issuer's ACS (Access Control Server), not by us.
  • SCA (Strong Customer Authentication) — a regulatory requirement (PSD2 in the EU); when it applies, 3DS engages even at LOW.
  • No-amount card check — confirms the card without authorizing any amount (MEDIUM/LOW; Stripe SetupIntent).
  • Small-hold card check — a brief authorization hold to confirm the card (HIGH; $0, or $1 if the issuer rejects $0). Canceled before any funds settle.
  • Permitted-exception record — a successful enrollment despite a soft issuer signal (contact-issuer family, or 3DS-unsupported). Only LOW produces these; stored as AUTOMATIC_BYPASS.
  • Hard-fraud codesstolen_card, lost_card, fraudulent, pickup_card, restricted_card, security_violation. Reject at every tier ("Card not eligible").
  • Contact-issuer familycall_issuer, do_not_honor, transaction_not_allowed, service_not_allowed, revocation_of_authorization, revocation_of_all_authorizations. HIGH/MEDIUM reject with "Contact your bank"; LOW bypasses silently.

Next steps