> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Risk Declines & 3D Secure

> What a risk decline returns, how to tell it apart from a payment method decline, whether you can force or skip 3D Secure and when your instruction is ignored, how a challenge reaches the shopper, and what you can see about declines, chargeback and fraud rates in the Portal.

Risk sits between your order request and the payment method, and it shows up in three places: a
code on the response, an authorization step for the shopper, and numbers in the Portal. This page
covers all three. For the full code list see
[Error codes](/docs/en/developer-docs/tools-and-references/developer-tools/error-codes); for which code
belongs to which *limit*, see
[Error Codes & Limits Supplement](/docs/en/support/error-codes-and-limits-supplement).

## What does the API return when a payment is declined by risk?

Two codes, both returned on the order request itself with HTTP `406`:

| Code | Meaning |
| - | - |
| `D0001` | Risk rejection |
| `D0002` | Risk transaction limit |

They are the risk decision surfacing directly. Treat both as **final for that order** — they are a
decline, not a retryable transport failure and not a review queue you can wait on. `D0001` also
exists on the payout, card, and point-topup surfaces with the same meaning, so a single handler can
cover it; `D0002` belongs to the payin surface.

## How do I tell a risk decline from a payment method decline in my code?

It depends which surface you are reading, and this is the part that catches people out.

| Where you read it | Can you tell? |
| - | - |
| The **synchronous response** to order create | **Yes.** `D0001` / `D0002` (HTTP 406) for risk; a payment channel rejection carries its own code and HTTP status |
| **Order inquiry** and the **payment notification** | **Not from the code alone.** Both a risk decline and a payment-method-reported risk decline are reported under the same generic failure code, and only the accompanying description distinguishes them |

Concretely, on the asynchronous surfaces the failure arrives as `orderFailedReason`, a JSON value
with `orderFailedCode` and `orderFailedDescription`. `orderFailedCode` is a **category**, not the
specific code you saw on the synchronous response, and `orderStatus` is the ordinary closed-order
status — there is no risk-specific order status.

Two practical rules follow:

1. **Record the synchronous code when you get one.** It is the only place the distinction is
   machine-readable. Persist it against your own order.
2. **Do not branch on the description string.** It is free text intended for a human reading a
   support ticket, and it is not a stable interface.

Redirects are never proof of anything either way: the authoritative result is the notification or an
order inquiry — see [Payment results](/docs/en/support/payment-results-faq).

## My payment was refused for a limit. Which limit, and can I change it?

Limits come from several places and they use **different codes from the risk codes above**:

| Code | Limit |
| - | - |
| `A0005` | Merchant limit exceeded |
| `A0008` | Payment amount outside the permitted range |
| `A0020` | Payment amount exceeds the single-transaction limit |
| `A0021` | Accumulated amount exceeds the daily limit |
| `B0002` | User limit exceeded |
| `D0002` | Risk transaction limit — the risk decision, not one of the above |

Which of these you can influence, how the daily figure is counted, and when headroom is released
are covered in [Error Codes & Limits Supplement](/docs/en/support/error-codes-and-limits-supplement) and
[Payin Agreements & Limits](/docs/en/support/payin-agreements-and-limits-faq). The short version: the
amount range and the ceilings come from what your account is set up with, so changing them is a
conversation, not a Portal setting — with one exception, below.

## Can I set my own per-card or per-user restrictions?

Yes, through the transaction limit rule API, which is the one risk-adjacent control you operate
yourself. It is API-only — there is no Portal screen for it. Endpoints, request shape, what a rule
can key on, and how to read or stop one are in
[Transaction Limit Rules](/docs/en/support/transaction-limit-api-faq).

Three things worth knowing before you arm one:

* **A triggered rule declines the payment as a risk decision** — the shopper's payment fails with
  the risk codes above, not with a distinct "your rule fired" code. So on the asynchronous surfaces
  you cannot attribute a decline to your own rule; keep your own record of what you restricted.
* **A rule can affect an order that already exists.** Risk is consulted when the shopper actually
  pays, not when you created the order, so an order created before you armed the rule but paid after
  will be evaluated against it. Only payments that already succeeded are untouched.
* **It only ever adds restriction.** There is no rule direction that exempts a card or a user from
  screening.

## When does 3D Secure happen, and can I decide it?

You can express a preference on the order, using `threeDsDecision` inside the card information:

| Value | Your instruction |
| - | - |
| `3DS_FORCE` | Put this payment through 3D Secure |
| `NO_3DS` | Skip 3D Secure for this payment |

Any other value is rejected as invalid card information. But the field is a **preference, not a
switch**, and three things sit above it:

1. **The risk decision.** Your value is one input to risk, which returns the decision that is
   actually used.
2. **What the payment method can do.** The decision has to be satisfiable by the route the payment
   takes. Where nothing can satisfy it, the order fails rather than silently downgrading.
3. **The issuer.** Even on a 3D Secure payment, whether the shopper is *challenged* or passes
   without interaction is the issuer's call, not ours and not yours.

So `3DS_FORCE` is best read as "do not skip this", and `NO_3DS` as "skip it if that is allowed".

## I sent `NO_3DS` and the shopper was still asked to verify. Did I send it wrong?

Probably not. Given the order above, an authentication step can still appear because the risk
decision or the payment route required it. The field is validated, so a genuinely malformed value
would have failed the request with an invalid-card-information error rather than being ignored — if
your order was accepted, the value was understood.

The other case to rule out is scope: the field is only effective for **card, Apple Pay and Google
Pay** payments where the shopper is present. On any other payment method it does nothing.

## Can I set a 3D Secure decision on a subscription renewal or another merchant-initiated charge?

No, and this is why it looks like it is being ignored: the value is **accepted and validated but not
applied** to merchant-initiated card transactions. Subscription renewals are merchant-initiated, so
a `threeDsDecision` on one has no effect and produces no error.

Where authentication for a recurring series happens is at the point the shopper is present — the
first payment, or the mandate setup. See
[Subscriptions](/docs/en/support/subscription-faq) and
[Tokenization](/docs/en/developer-docs/integration/tokenization/overview).

## How does a challenge reach the shopper, and what must my own front end handle?

Whenever an extra step is needed, the order comes back with the status that means authorization is
required, and an `orderAction` describing where to send the shopper:

| `actionType` | What to do |
| - | - |
| `WEB` | Send the shopper to `webUrl` |
| `DEEPLINK` | Send the shopper to `deeplinkUrl` |

If you use the Waffo-hosted checkout, this is handled for you — the shopper is taken to the
authentication page and brought back. If you build your own front end, you must:

1. Detect the authorization-required status on the create response (and on inquiry).
2. Parse `orderAction` and branch on `actionType`.
3. Perform a **full-page** navigation. Do not attempt it inside a small frame: authentication pages
   commonly refuse to render framed, and the shopper is left staring at an empty box.
4. On return, do not treat the return itself as the result. Poll an order inquiry, or wait for the
   payment notification.

The action envelope, including what else `actionData` can carry for methods that need a code, a QR
or bank details, is in [Order create field guide](/docs/en/support/order-create-field-guide) and
[Redirects & action types](/docs/en/support/cashier-redirect-and-action-type-faq).

## Is risk evaluated on every transaction in real time?

Yes. The evaluation happens **inside the payment request**, before the payment is attempted, and the
outcome is applied to that order immediately. There is no batch pass and no later re-scoring of a
payment that already went through.

That also explains a pattern that looks inconsistent: a shopper's first payment succeeding and a
second one being declined is entirely normal. Each payment is assessed on its own, in its own
context — amount, method, history, what happened in between — so the outcome can differ between two
payments from the same person minutes apart.

## Where can I see risk-declined transactions in the Portal?

Per order, not as a category:

| Screen | What it gives you |
| - | - |
| Payments list | The order and its status. There is **no failure-reason filter** — you cannot list "everything declined by risk" |
| Payment detail | `Order Failure Code` and `Order Failure Description` for that order |
| Payments Analytics → failure analysis | Declines **grouped and counted** by category and description |

So the workflow is: find the volume in failure analysis, then use your own records to pull the
individual orders. The filters the payment list does offer are in
[Portal List Filters & Date Ranges](/docs/en/support/portal-list-filters-and-date-ranges-faq).

## What does the failure analysis tell me, and can I drill down to the orders?

It tells you **how many** and **why**, grouped as: failures attributable to the payment method side,
to the customer side, and to your side, each expanding to individual failure descriptions with their
counts and share.

It does **not** drill down to orders. The request behind the panel is parameterised by product,
subscription stage, countries, payment methods and the interval — never by order, and the response
carries counts rather than order identifiers. There is no link that carries a failure reason into the
payment list as a filter.

What the panel counts, and why analytics figures do not tie exactly to a list count, is in
[Portal Dashboards & Analytics](/docs/en/support/portal-analytics-faq).

## Do declined transactions count against my success rate?

Yes. The success rate is **successful orders divided by orders created**, so every order that was
created and then declined — for any reason, risk included — stays in the denominator. The Portal
shows this alongside two narrower ratios: how many created orders reached the payment step, and how
many of those succeeded.

Because a risk decline happens at the payment step, it lands in the denominator of the narrower
ratio too, not only in the overall one. Practical consequence: pre-qualifying traffic before you
create an order improves the ratio; declining it after you create one does not.

## Can I see my own chargeback rate and fraud-report rate?

Yes, on Chargeback Analytics, where they are reported **per card scheme** rather than as one blended
number, because the schemes define them differently:

| Indicator | How it is composed |
| - | - |
| Chargeback rate (one scheme) | Fraud reports plus chargebacks received in the period, over successful payments in the period |
| Fraud rate (that scheme) | Fraud reports **plus chargebacks carrying that scheme's fraud reason code**, received in the period, over successful payments in the period |
| Chargeback rate (the other scheme) | First chargebacks in the period, over successful payments in the period |
| Fraud rate (that scheme) | Fraud reports over successful payments |

Note both numerators are **received in the period**, not "belonging to payments made in the period" —
a chargeback lands months after the payment, so the ratio is a period measure, not a cohort measure.
The screen also gives chargeback counts and amounts, a trend, a reason-code distribution, and cases
approaching their deadline. Data is a day behind, and the period you can select is bounded.

What each ratio card measures and which period it reports on is also in
[Portal Dashboards & Analytics](/docs/en/support/portal-analytics-faq). What to do about a case is in
[Chargebacks](/docs/en/support/chargeback-faq) and
[Chargeback Evidence](/docs/en/support/chargeback-evidence-guide).

## Are fraud reports the same thing as chargebacks, and can I query them separately?

They are not the same thing: a fraud report is the issuer telling the scheme a transaction was
fraudulent, and it may arrive without a chargeback ever following — which is why it appears in its
own ratio above.

Individual fraud reports **can** be queried separately, through the Reported Fraud list API
(`/api/v1/fraud-report/list`, filtered by report date, scheme or a single order). There is no
Portal list page for them — the Portal shows only the aggregate inside Chargeback Analytics. Field
by field, see [Reason codes & Reported Fraud](/docs/en/support/chargeback-reason-codes-and-reported-fraud).

## A payment was declined and I think it should not have been. Can I appeal, or have a card allowlisted?

There is **no self-serve mechanism** for either — no appeal endpoint, no allowlist or exemption for a
card, a shopper or a range, and no Portal screen for it. The only merchant-operated risk control is
the limit rule API, and it only adds restriction.

So the route is your support contact, with the order identifiers. Do not rebuild the payment as a
retry loop: repeated attempts on a declined instrument are themselves a risk signal.

The IP allowlist is a different feature and *is* self-serve — it restricts which of your servers may
call the API, and has nothing to do with screening shoppers. See
[Portal Self-Service Security Configuration](/docs/en/support/portal-security-config-faq).

## Can I test a risk decline or a 3D Secure challenge in the sandbox?

Not directly. The sandbox simulator drives outcomes by **order amount**, and the outcomes it can
produce are payment failure, system error and unknown status on create, cancel and refund paths —
listed in
[Sandbox simulator](/docs/en/developer-docs/tools-and-references/developer-tools/sandbox-simulator). None
of them produces a risk-decline code, and none forces an authentication challenge.

What you can and should do instead:

* **Exercise your handling, not the trigger.** Your code paths for a declined order and for an
  authorization-required order are the same regardless of which reason produced them, so simulate a
  failure and an authorization step and prove your handling.
* **Handle the risk codes defensively in production from day one**, since you cannot rehearse them.

If you need a specific branch reproduced before going live, ask integration support rather than
hunting for an amount that triggers it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.