Step-up integration
step_up means: require an additional control before continuing. PayInference decides that a control is needed; you choose and run the control, then report how it went.
What the control is depends on your business: 3DS or SCA for cards, KYC for a new account, manual review, human approval, or agent approval in agent-driven systems.
When step_up is returned#
step_up is returned when a policy rule with a step_up action matched: reason code STEP_UP_REQUIRED_BY_POLICY. Rule conditions can combine amount, region, payment method, customer type, and the risk_score or risk_level you supply in the transaction, so your existing fraud tooling drives step-ups through a rule like when: { risk_score_gte: 60 }, then: { type: "step_up" }.
The deterministic risk object in the response is advisory: when the band is high its recommended_action is step_up and the top-level reason codes include RISK_ELEVATED, but the assessment by itself does not change the instruction. If you want it enforced, express the threshold as a policy rule.
{
"decision_id": "dec_03c9e4",
"action": "step_up",
"route": { "primary_provider": "adyen", "fallback_provider": null },
"risk": {
"score": 74,
"band": "high",
"recommended_action": "step_up",
"reason_codes": ["ELEVATED_AMOUNT", "NEW_CUSTOMER_HIGH_AMOUNT", "HIGH_VELOCITY_10M"]
},
"reason_codes": ["STEP_UP_REQUIRED_BY_POLICY", "RISK_ELEVATED"]
}
route is still populated: it tells you where to execute once the control passes.
The flow with 3DS as the control#
const decision = await payinference.decide(params);
if (decision.action === 'step_up') {
// 1. Run your control. For cards this is typically a 3DS challenge
// through your PSP's own SDK.
const challenge = await startThreeDSChallenge(order, decision.route?.primary_provider);
if (challenge.completed) {
// 2a. Report the control result.
await payinference.recordOutcome({
decision_id: decision.decision_id,
order_id: order.id,
provider_used: decision.route!.primary_provider,
outcome: 'step_up_completed',
provider_latency_ms: challenge.durationMs,
amount: order.amount,
currency: order.currency,
});
// 3a. Execute the payment on the routed provider, with the 3DS proof
// attached the way your PSP expects.
return chargeWithProvider(decision.route!.primary_provider, order, challenge.proof);
}
// 2b. The customer failed or abandoned the challenge.
await payinference.recordOutcome({
decision_id: decision.decision_id,
order_id: order.id,
provider_used: decision.route!.primary_provider,
outcome: 'step_up_failed',
provider_latency_ms: challenge.durationMs,
amount: order.amount,
currency: order.currency,
});
return rejectPayment(order, decision.decision_id);
}
For asynchronous pauses (manual review, human approval, compliance checks) prefer the dedicated hold instruction, which policy can demand with a hold rule; see Hold and review integration. step_up is for interactive controls the customer or agent completes in the flow.
Defining step-up rules#
{
"rule_id": "step_up_high_value_new",
"name": "Step up new customers above 500",
"priority": 10,
"enabled": true,
"when": { "amount_gte": 50000, "customer_type_in": ["new", "guest"] },
"then": { "type": "step_up" }
}
Conditions can combine amount, risk score, region, payment method, and more. See Policy Manager.
Why report step-up outcomes#
step_up_completed and step_up_failed are first-class outcome values. Reporting them tells you (and PayInference's analytics) how much friction your controls add and how often stepped-up payments convert, which is exactly the data you need to tune the thresholds in your policy.