[{"data":1,"prerenderedAt":1355},["ShallowReactive",2],{"docsv3-nav":3,"\u002Fdocs\u002Fv3\u002Fchallenge-flow":198},[4],{"title":5,"path":6,"stem":7,"children":8,"page":188},"V3","\u002Fdocs\u002Fv3","1.docs\u002Fv3",[9,13,17,21,38,87,189],{"title":10,"path":11,"stem":12},"Introduction","\u002Fdocs\u002Fv3\u002Fintroduction","1.docs\u002Fv3\u002F1.Introduction",{"title":14,"path":15,"stem":16},"Quick start","\u002Fdocs\u002Fv3\u002Fquick-start","1.docs\u002Fv3\u002F2.Quick start",{"title":18,"path":19,"stem":20},"Challenge flow","\u002Fdocs\u002Fv3\u002Fchallenge-flow","1.docs\u002Fv3\u002F3.Challenge flow",{"title":22,"path":23,"stem":24,"children":25},"Fundamentals","\u002Fdocs\u002Fv3\u002Ffundamentals","1.docs\u002Fv3\u002F4.fundamentals",[26,30,34],{"title":27,"path":28,"stem":29},"Signup protection","\u002Fdocs\u002Fv3\u002Ffundamentals\u002Fsignup-protection","1.docs\u002Fv3\u002F4.fundamentals\u002F00.Signup protection",{"title":31,"path":32,"stem":33},"Login protection","\u002Fdocs\u002Fv3\u002Ffundamentals\u002Flogin-protection","1.docs\u002Fv3\u002F4.fundamentals\u002F01.Login protection",{"title":35,"path":36,"stem":37},"Access protection","\u002Fdocs\u002Fv3\u002Ffundamentals\u002Faccess-protection","1.docs\u002Fv3\u002F4.fundamentals\u002F02.Access protection",{"title":39,"path":40,"stem":41,"children":42},"Guides","\u002Fdocs\u002Fv3\u002Fguides","1.docs\u002Fv3\u002F5.guides",[43,47,51,55,59,63,67,71,75,79,83],{"title":44,"path":45,"stem":46},"Account sharing prevention","\u002Fdocs\u002Fv3\u002Fguides\u002Faccount-sharing-prevention","1.docs\u002Fv3\u002F5.guides\u002F1.Account sharing prevention",{"title":48,"path":49,"stem":50},"Web scraping prevention","\u002Fdocs\u002Fv3\u002Fguides\u002Fweb-scraping-prevention","1.docs\u002Fv3\u002F5.guides\u002F13.Web scraping prevention",{"title":52,"path":53,"stem":54},"Ban enforcement","\u002Fdocs\u002Fv3\u002Fguides\u002Fban-enforcement","1.docs\u002Fv3\u002F5.guides\u002F14.Ban enforcement",{"title":56,"path":57,"stem":58},"Chargeback dispute","\u002Fdocs\u002Fv3\u002Fguides\u002Fchargeback-dispute","1.docs\u002Fv3\u002F5.guides\u002F15.Chargeback dispute",{"title":60,"path":61,"stem":62},"Multi-accounting prevention","\u002Fdocs\u002Fv3\u002Fguides\u002Fmulti-accounting-prevention","1.docs\u002Fv3\u002F5.guides\u002F16.Multi-accounting prevention",{"title":64,"path":65,"stem":66},"Account takeover prevention","\u002Fdocs\u002Fv3\u002Fguides\u002Faccount-takeover-prevention","1.docs\u002Fv3\u002F5.guides\u002F2.Account takeover prevention",{"title":68,"path":69,"stem":70},"Risky transaction prevention","\u002Fdocs\u002Fv3\u002Fguides\u002Frisky-transaction-prevention","1.docs\u002Fv3\u002F5.guides\u002F20.Risky transaction prevention",{"title":72,"path":73,"stem":74},"Fake account detection","\u002Fdocs\u002Fv3\u002Fguides\u002Ffake-account-detection","1.docs\u002Fv3\u002F5.guides\u002F3.Fake account detection",{"title":76,"path":77,"stem":78},"Bot detection","\u002Fdocs\u002Fv3\u002Fguides\u002Fbot-detection","1.docs\u002Fv3\u002F5.guides\u002F4.Bot detection",{"title":80,"path":81,"stem":82},"Card testing prevention","\u002Fdocs\u002Fv3\u002Fguides\u002Fcard-testing-prevention","1.docs\u002Fv3\u002F5.guides\u002F5.Card testing prevention",{"title":84,"path":85,"stem":86},"Incentive abuse prevention","\u002Fdocs\u002Fv3\u002Fguides\u002Fincentive-abuse-prevention","1.docs\u002Fv3\u002F5.guides\u002F9.Incentive abuse prevention",{"title":88,"path":89,"stem":90,"children":91,"page":188},"Concepts","\u002Fdocs\u002Fv3\u002Fconcepts","1.docs\u002Fv3\u002F6.concepts",[92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168,172,176,180,184],{"title":93,"path":94,"stem":95},"Evaluations","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fevaluations","1.docs\u002Fv3\u002F6.concepts\u002F01.evaluations",{"title":97,"path":98,"stem":99},"Actions","\u002Fdocs\u002Fv3\u002Fconcepts\u002Factions","1.docs\u002Fv3\u002F6.concepts\u002F02.actions",{"title":101,"path":102,"stem":103},"Signals","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fsignals","1.docs\u002Fv3\u002F6.concepts\u002F03.signals",{"title":105,"path":106,"stem":107},"Checks","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks","1.docs\u002Fv3\u002F6.concepts\u002F04.checks",{"title":109,"path":110,"stem":111},"Risks","\u002Fdocs\u002Fv3\u002Fconcepts\u002Frisks","1.docs\u002Fv3\u002F6.concepts\u002F05.risks",{"title":113,"path":114,"stem":115},"Verdicts","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fverdicts","1.docs\u002Fv3\u002F6.concepts\u002F06.verdicts",{"title":117,"path":118,"stem":119},"Policies","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpolicies","1.docs\u002Fv3\u002F6.concepts\u002F07.policies",{"title":121,"path":122,"stem":123},"Challenges","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchallenges","1.docs\u002Fv3\u002F6.concepts\u002F08.challenges",{"title":125,"path":126,"stem":127},"Concurrency","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fconcurrency","1.docs\u002Fv3\u002F6.concepts\u002F09.concurrency",{"title":129,"path":130,"stem":131},"Impossible travel","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fimpossible-travel","1.docs\u002Fv3\u002F6.concepts\u002F10.impossible-travel",{"title":133,"path":134,"stem":135},"Bots","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fbots","1.docs\u002Fv3\u002F6.concepts\u002F11.bots",{"title":137,"path":138,"stem":139},"Devices","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fdevices","1.docs\u002Fv3\u002F6.concepts\u002F12.devices",{"title":141,"path":142,"stem":143},"Fingerprints","\u002Fdocs\u002Fv3\u002Fconcepts\u002Ffingerprints","1.docs\u002Fv3\u002F6.concepts\u002F13.fingerprints",{"title":145,"path":146,"stem":147},"People","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpeople","1.docs\u002Fv3\u002F6.concepts\u002F14.people",{"title":149,"path":150,"stem":151},"Lists","\u002Fdocs\u002Fv3\u002Fconcepts\u002Flists","1.docs\u002Fv3\u002F6.concepts\u002F15.lists",{"title":153,"path":154,"stem":155},"Account takeover","\u002Fdocs\u002Fv3\u002Fconcepts\u002Faccount-takeover","1.docs\u002Fv3\u002F6.concepts\u002F16.account-takeover",{"title":157,"path":158,"stem":159},"Account sharing","\u002Fdocs\u002Fv3\u002Fconcepts\u002Faccount-sharing","1.docs\u002Fv3\u002F6.concepts\u002F17.account-sharing",{"title":161,"path":162,"stem":163},"Fake account","\u002Fdocs\u002Fv3\u002Fconcepts\u002Ffake-account","1.docs\u002Fv3\u002F6.concepts\u002F18.fake-account",{"title":165,"path":166,"stem":167},"Scraping","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fscraping","1.docs\u002Fv3\u002F6.concepts\u002F19.scraping",{"title":169,"path":170,"stem":171},"Linked accounts","\u002Fdocs\u002Fv3\u002Fconcepts\u002Flinked-accounts","1.docs\u002Fv3\u002F6.concepts\u002F20.linked-accounts",{"title":173,"path":174,"stem":175},"New IP","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fip","1.docs\u002Fv3\u002F6.concepts\u002F21.ip",{"title":177,"path":178,"stem":179},"Anonymizing network","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fanonymizing-network","1.docs\u002Fv3\u002F6.concepts\u002F22.anonymizing-network",{"title":181,"path":182,"stem":183},"Email quality","\u002Fdocs\u002Fv3\u002Fconcepts\u002Femail","1.docs\u002Fv3\u002F6.concepts\u002F23.email",{"title":185,"path":186,"stem":187},"Velocity","\u002Fdocs\u002Fv3\u002Fconcepts\u002Fvelocity","1.docs\u002Fv3\u002F6.concepts\u002F24.velocity",false,{"title":190,"path":191,"stem":192,"children":193,"page":188},"Advanced","\u002Fdocs\u002Fv3\u002Fadvanced","1.docs\u002Fv3\u002F7.Advanced",[194],{"title":195,"path":196,"stem":197},"Proxy setup","\u002Fdocs\u002Fv3\u002Fadvanced\u002Fproxy-setup","1.docs\u002Fv3\u002F7.Advanced\u002F1.Proxy-setup",{"id":199,"title":18,"body":200,"description":1349,"extension":1350,"meta":1351,"navigation":391,"path":19,"rawbody":1352,"seo":1353,"stem":20,"__hash__":1354},"docsv3\u002F1.docs\u002Fv3\u002F3.Challenge flow.md",{"type":201,"value":202,"toc":1340},"minimark",[203,207,236,239,244,248,252,285,292,308,311,315,340,666,683,687,696,699,707,711,718,721,865,869,872,1297,1306,1310,1336],[204,205,18],"h1",{"id":206},"challenge-flow",[208,209,210,211,215,216,220,221,224,225,228,229,232,233,235],"p",{},"This picks up where the ",[212,213,214],"a",{"href":15},"quick start"," leaves off. You've wired ",[217,218,219],"code",{},"evaluate"," on the client and the server confirmation. Now a ",[212,222,223],{"href":118},"policy"," returns a ",[217,226,227],{},"challenge"," ",[212,230,231],{"href":114},"verdict"," and Rupt runs an interactive ",[212,234,227],{"href":122},": it sends the user to a hosted verification page, and when they pass, sends them back to you. This guide is the generic wiring for that round trip, and every other guide that challenges a user reuses it.",[208,237,238],{},"The shape never changes: evaluate on the client, let Rupt run the challenge, and confirm the outcome on your server before you honor the action. The final server step consumes the evaluation, so a passed challenge is single-use and can't be replayed. Nothing trusts the client.",[240,241,243],"h2",{"id":242},"the-flow","The flow",[245,246],"mermaid-diagram",{"code":247},"sequenceDiagram\n  actor U as User\n  participant C as Client app\n  participant S as Your server\n  participant R as Rupt\n  participant X as Challenge UI\n\n  U->>C: Takes the action\n  C->>R: evaluate(action, identifiers)\n  R-->>C: { evaluation_id, redirect? }\n  alt verdict needs a challenge\n    C->>X: Redirect to the challenge UI\n    U->>X: Completes verification\n    X->>C: Return to success_url?evaluation=…\n  end\n  C->>S: Send evaluation_id with the action request\n  S->>R: Consume the evaluation (single-use)\n  R-->>S: { challenge.status, … } or 409 if already used\n  Note over S: Confirm status + integrity, then honor once\n  S-->>U: Honor or block the action\n",[240,249,251],{"id":250},"before-you-start-the-policy-and-its-challenge-config","Before you start: the policy and its challenge config",[208,253,254,255,257,258,264,265,268,269,272,273,276,277,280,281,284],{},"A challenge only happens because a ",[212,256,223],{"href":118}," told it to. In the dashboard, create a policy whose ",[259,260,261,262],"strong",{},"action is ",[217,263,227],{}," for the ",[212,266,267],{"href":98},"action"," you're protecting (",[217,270,271],{},"login",", ",[217,274,275],{},"signup",", or ",[217,278,279],{},"access",") and the ",[212,282,283],{"href":106},"checks"," you want to gate on.",[208,286,287,288,291],{},"Every challenge policy points at a ",[259,289,290],{},"challenge config",", which holds the verification channels and the URLs Rupt uses for the round trip:",[293,294,295,302],"ul",{},[296,297,298,301],"li",{},[259,299,300],{},"Success URL",": where Rupt sends the user after they pass. This is the page on your site that finishes the action.",[296,303,304,307],{},[259,305,306],{},"Primary, secondary, and logout URLs",": the links shown inside the challenge UI (for example, \"back to app\" or \"log out\").",[208,309,310],{},"You set these once on the challenge config, and they apply to every challenge that policy issues.",[240,312,314],{"id":313},"step-1-evaluate-on-the-client","Step 1: Evaluate on the client",[208,316,317,318,320,321,324,325,272,328,331,332,335,336,339],{},"Call ",[217,319,219],{}," at the moment the user takes the action. Pass whatever identifiers you have: a ",[217,322,323],{},"user"," id, ",[217,326,327],{},"email",[217,329,330],{},"phone",". The response carries an ",[217,333,334],{},"evaluation_id"," and, when a challenge is required, a ",[217,337,338],{},"redirect",".",[341,342,343,522,604],"client-platform",{},[344,345,347],"template",{"v-slot:web":346},"",[348,349,353],"pre",{"className":350,"code":351,"language":352,"meta":346,"style":346},"language-js shiki shiki-themes material-theme-lighter one-dark-pro monokai","import Rupt from \"@ruptjs\u002Fclient\";\n\nconst rupt = new Rupt({ clientId: \"your_client_id\" });\n\nconst evaluation = await rupt.evaluate.login({\n  user: \"USER_ID\",\n  email: form.email,\n});\n","js",[217,354,355,386,393,444,449,477,495,512],{"__ignoreMap":346},[356,357,360,364,368,371,375,379,382],"span",{"class":358,"line":359},"line",1,[356,361,363],{"class":362},"sAPXc","import",[356,365,367],{"class":366},"seeE2"," Rupt",[356,369,370],{"class":362}," from",[356,372,374],{"class":373},"s9QZx"," \"",[356,376,378],{"class":377},"siibJ","@ruptjs\u002Fclient",[356,380,381],{"class":373},"\"",[356,383,385],{"class":384},"shEKG",";\n",[356,387,389],{"class":358,"line":388},2,[356,390,392],{"emptyLinePlaceholder":391},true,"\n",[356,394,396,400,404,408,412,415,419,422,426,429,431,434,436,439,442],{"class":358,"line":395},3,[356,397,399],{"class":398},"sHm3x","const",[356,401,403],{"class":402},"sZ9uN"," rupt",[356,405,407],{"class":406},"sut_7"," =",[356,409,411],{"class":410},"srTuz"," new",[356,413,367],{"class":414},"sjp9t",[356,416,418],{"class":417},"sJCYa","(",[356,420,421],{"class":384},"{",[356,423,425],{"class":424},"sUwfj"," clientId",[356,427,428],{"class":384},":",[356,430,374],{"class":373},[356,432,433],{"class":377},"your_client_id",[356,435,381],{"class":373},[356,437,438],{"class":384}," }",[356,440,441],{"class":417},")",[356,443,385],{"class":384},[356,445,447],{"class":358,"line":446},4,[356,448,392],{"emptyLinePlaceholder":391},[356,450,452,454,457,459,462,464,466,468,470,472,474],{"class":358,"line":451},5,[356,453,399],{"class":398},[356,455,456],{"class":402}," evaluation",[356,458,407],{"class":406},[356,460,461],{"class":362}," await",[356,463,403],{"class":402},[356,465,339],{"class":384},[356,467,219],{"class":402},[356,469,339],{"class":384},[356,471,271],{"class":414},[356,473,418],{"class":417},[356,475,476],{"class":384},"{\n",[356,478,480,483,485,487,490,492],{"class":358,"line":479},6,[356,481,482],{"class":424},"  user",[356,484,428],{"class":384},[356,486,374],{"class":373},[356,488,489],{"class":377},"USER_ID",[356,491,381],{"class":373},[356,493,494],{"class":384},",\n",[356,496,498,501,503,506,508,510],{"class":358,"line":497},7,[356,499,500],{"class":424},"  email",[356,502,428],{"class":384},[356,504,505],{"class":402}," form",[356,507,339],{"class":384},[356,509,327],{"class":366},[356,511,494],{"class":384},[356,513,515,518,520],{"class":358,"line":514},8,[356,516,517],{"class":384},"}",[356,519,441],{"class":417},[356,521,385],{"class":384},[344,523,524],{"v-slot:ios":346},[348,525,529],{"className":526,"code":527,"language":528,"meta":346,"style":346},"language-swift shiki shiki-themes material-theme-lighter one-dark-pro monokai","let response = try await rupt.evaluate(\n  action: \"login\",\n  user: \"USER_ID\",\n  email: form.email\n)\n","swift",[217,530,531,558,573,587,599],{"__ignoreMap":346},[356,532,533,537,540,544,547,549,552,555],{"class":358,"line":359},[356,534,536],{"class":535},"s2NTT","let",[356,538,539],{"class":417}," response ",[356,541,543],{"class":542},"sKfv_","=",[356,545,546],{"class":362}," try",[356,548,461],{"class":362},[356,550,551],{"class":417}," rupt.",[356,553,219],{"class":554},"sh6BQ",[356,556,557],{"class":384},"(\n",[356,559,560,563,565,567,569,571],{"class":358,"line":388},[356,561,562],{"class":554},"  action",[356,564,428],{"class":384},[356,566,374],{"class":373},[356,568,271],{"class":377},[356,570,381],{"class":373},[356,572,494],{"class":417},[356,574,575,577,579,581,583,585],{"class":358,"line":395},[356,576,482],{"class":554},[356,578,428],{"class":384},[356,580,374],{"class":373},[356,582,489],{"class":377},[356,584,381],{"class":373},[356,586,494],{"class":417},[356,588,589,591,593,596],{"class":358,"line":446},[356,590,500],{"class":554},[356,592,428],{"class":384},[356,594,595],{"class":417}," form.",[356,597,598],{"class":366},"email\n",[356,600,601],{"class":358,"line":451},[356,602,603],{"class":384},")\n",[344,605,606],{"v-slot:android":346},[348,607,611],{"className":608,"code":609,"language":610,"meta":346,"style":346},"language-kotlin shiki shiki-themes material-theme-lighter one-dark-pro monokai","val response = rupt.evaluate(\n  action = \"login\",\n  user = \"USER_ID\",\n  email = form.email,\n)\n","kotlin",[217,612,613,628,640,652,662],{"__ignoreMap":346},[356,614,615,618,620,622,624,626],{"class":358,"line":359},[356,616,617],{"class":410},"val",[356,619,539],{"class":417},[356,621,543],{"class":406},[356,623,551],{"class":417},[356,625,219],{"class":414},[356,627,557],{"class":417},[356,629,630,633,635,638],{"class":358,"line":388},[356,631,632],{"class":417},"  action ",[356,634,543],{"class":406},[356,636,637],{"class":377}," \"login\"",[356,639,494],{"class":417},[356,641,642,645,647,650],{"class":358,"line":395},[356,643,644],{"class":417},"  user ",[356,646,543],{"class":406},[356,648,649],{"class":377}," \"USER_ID\"",[356,651,494],{"class":417},[356,653,654,657,659],{"class":358,"line":446},[356,655,656],{"class":417},"  email ",[356,658,543],{"class":406},[356,660,661],{"class":417}," form.email,\n",[356,663,664],{"class":358,"line":451},[356,665,603],{"class":417},[208,667,668,669,671,672,272,674,676,677,679,680,339],{},"For ",[217,670,279],{},", the SDK navigates to the challenge UI automatically when one is required. Every other action (",[217,673,271],{},[217,675,275],{},", and custom events) doesn't auto-navigate by default, so the form your user is filling isn't abandoned mid-submit. The redirect URL comes back in the response and your server decides what to do with it. See ",[212,678,27],{"href":28},". You can force either behavior per call with ",[217,681,682],{},"auto_challenge: true | false",[240,684,686],{"id":685},"step-2-set-the-success-url","Step 2: Set the success URL",[208,688,689,690,692,693,339],{},"In the dashboard, on the challenge config your policy uses, set ",[259,691,300],{}," to the page that completes the action, for example ",[217,694,695],{},"https:\u002F\u002Fyourapp.com\u002Fverified",[208,697,698],{},"When the user passes, Rupt redirects there with the evaluation ID appended:",[348,700,705],{"className":701,"code":703,"language":704},[702],"language-text","https:\u002F\u002Fyourapp.com\u002Fverified?evaluation=68f…\n","text",[217,706,703],{"__ignoreMap":346},[240,708,710],{"id":709},"step-3-hand-the-evaluation-id-to-your-server","Step 3: Hand the evaluation ID to your server",[712,713,715],"alert",{"type":714},"info",[208,716,717],{},"This step is optional for account-sharing prevention.",[208,719,720],{},"Your success page reads the evaluation ID off the URL and posts it to your backend. The client never decides the outcome. It only carries the evaluation ID across.",[348,722,724],{"className":350,"code":723,"language":352,"meta":346,"style":346},"const params = new URLSearchParams(window.location.search);\n\nawait fetch(\"\u002Fverify-challenge\", {\n  method: \"POST\",\n  body: JSON.stringify({\n    evaluation_id: params.get(\"evaluation\"),\n  }),\n});\n",[217,725,726,759,763,786,802,821,848,857],{"__ignoreMap":346},[356,727,728,730,733,735,737,740,742,745,747,750,752,755,757],{"class":358,"line":359},[356,729,399],{"class":398},[356,731,732],{"class":402}," params",[356,734,407],{"class":406},[356,736,411],{"class":410},[356,738,739],{"class":414}," URLSearchParams",[356,741,418],{"class":417},[356,743,744],{"class":402},"window",[356,746,339],{"class":384},[356,748,749],{"class":402},"location",[356,751,339],{"class":384},[356,753,754],{"class":366},"search",[356,756,441],{"class":417},[356,758,385],{"class":384},[356,760,761],{"class":358,"line":388},[356,762,392],{"emptyLinePlaceholder":391},[356,764,765,768,771,773,775,778,780,783],{"class":358,"line":395},[356,766,767],{"class":362},"await",[356,769,770],{"class":414}," fetch",[356,772,418],{"class":417},[356,774,381],{"class":373},[356,776,777],{"class":377},"\u002Fverify-challenge",[356,779,381],{"class":373},[356,781,782],{"class":384},",",[356,784,785],{"class":384}," {\n",[356,787,788,791,793,795,798,800],{"class":358,"line":446},[356,789,790],{"class":424},"  method",[356,792,428],{"class":384},[356,794,374],{"class":373},[356,796,797],{"class":377},"POST",[356,799,381],{"class":373},[356,801,494],{"class":384},[356,803,804,807,809,812,814,817,819],{"class":358,"line":451},[356,805,806],{"class":424},"  body",[356,808,428],{"class":384},[356,810,811],{"class":402}," JSON",[356,813,339],{"class":384},[356,815,816],{"class":414},"stringify",[356,818,418],{"class":417},[356,820,476],{"class":384},[356,822,823,826,828,830,832,835,837,839,842,844,846],{"class":358,"line":479},[356,824,825],{"class":424},"    evaluation_id",[356,827,428],{"class":384},[356,829,732],{"class":402},[356,831,339],{"class":384},[356,833,834],{"class":414},"get",[356,836,418],{"class":417},[356,838,381],{"class":373},[356,840,841],{"class":377},"evaluation",[356,843,381],{"class":373},[356,845,441],{"class":417},[356,847,494],{"class":384},[356,849,850,853,855],{"class":358,"line":497},[356,851,852],{"class":384},"  }",[356,854,441],{"class":417},[356,856,494],{"class":384},[356,858,859,861,863],{"class":358,"line":514},[356,860,517],{"class":384},[356,862,441],{"class":417},[356,864,385],{"class":384},[240,866,868],{"id":867},"step-4-consume-the-evaluation-on-your-server","Step 4: Consume the evaluation on your server",[208,870,871],{},"Make the final step a consumption. Consuming reads the evaluation and claims it in one atomic, single-use step, so a passed challenge can't be replayed into a second honored action. Confirm the consume succeeded, check that the challenge completed and the evaluation's action and identifiers match what you expected, and only then honor the action. (Flows with no server step, like self-managed account sharing, skip this.)",[348,873,875],{"className":350,"code":874,"language":352,"meta":346,"style":346},"import { RuptAPI } from \"@ruptjs\u002Fapi\";\n\nconst rupt = new RuptAPI(\"API_SECRET\");\n\nlet evaluation;\ntry {\n  \u002F\u002F Consume reads and claims the evaluation in one shot. A replay of the\n  \u002F\u002F success URL gets 409 because the evaluation was already used.\n  evaluation = await rupt.consumeEvaluation(evaluation_id);\n} catch (err) {\n  if (err.status === 409) {\n    \u002F\u002F Already used. Reject and have the user start the action again.\n    return reject(\"This challenge was already used. Start the action again.\");\n  }\n  return reject(\"Could not verify the challenge\");\n}\n\nif (evaluation.challenge?.status !== \"completed\") {\n  \u002F\u002F Challenge not completed. Send the user back to the challenge UI.\n  return redirect(evaluation.redirect);\n}\nif (\n  evaluation.user?.email !== expectedEmail || \u002F\u002F Identity mismatch\n  evaluation.action !== \"YOUR_EXPECTED_ACTION\" || \u002F\u002F Action mismatch\n  evaluation.user?.metadata !== YOUR_EXPECTED_METADATA \u002F\u002F Metadata mismatch\n) {\n  \u002F\u002F Integrity mismatch. Block the action.\n  return reject(\"Integrity mismatch\");\n}\n\n\u002F\u002F Honor the action\nhonorTheAction();\n",[217,876,877,900,904,929,933,941,948,954,959,985,1004,1030,1036,1058,1064,1085,1091,1096,1129,1135,1155,1160,1168,1192,1215,1237,1244,1250,1270,1275,1280,1286],{"__ignoreMap":346},[356,878,879,881,884,887,889,891,893,896,898],{"class":358,"line":359},[356,880,363],{"class":362},[356,882,883],{"class":384}," {",[356,885,886],{"class":366}," RuptAPI",[356,888,438],{"class":384},[356,890,370],{"class":362},[356,892,374],{"class":373},[356,894,895],{"class":377},"@ruptjs\u002Fapi",[356,897,381],{"class":373},[356,899,385],{"class":384},[356,901,902],{"class":358,"line":388},[356,903,392],{"emptyLinePlaceholder":391},[356,905,906,908,910,912,914,916,918,920,923,925,927],{"class":358,"line":395},[356,907,399],{"class":398},[356,909,403],{"class":402},[356,911,407],{"class":406},[356,913,411],{"class":410},[356,915,886],{"class":414},[356,917,418],{"class":417},[356,919,381],{"class":373},[356,921,922],{"class":377},"API_SECRET",[356,924,381],{"class":373},[356,926,441],{"class":417},[356,928,385],{"class":384},[356,930,931],{"class":358,"line":446},[356,932,392],{"emptyLinePlaceholder":391},[356,934,935,937,939],{"class":358,"line":451},[356,936,536],{"class":398},[356,938,456],{"class":366},[356,940,385],{"class":384},[356,942,943,946],{"class":358,"line":479},[356,944,945],{"class":362},"try",[356,947,785],{"class":384},[356,949,950],{"class":358,"line":497},[356,951,953],{"class":952},"s42Qa","  \u002F\u002F Consume reads and claims the evaluation in one shot. A replay of the\n",[356,955,956],{"class":358,"line":514},[356,957,958],{"class":952},"  \u002F\u002F success URL gets 409 because the evaluation was already used.\n",[356,960,962,965,967,969,971,973,976,979,981,983],{"class":358,"line":961},9,[356,963,964],{"class":366},"  evaluation",[356,966,407],{"class":406},[356,968,461],{"class":362},[356,970,403],{"class":402},[356,972,339],{"class":384},[356,974,975],{"class":414},"consumeEvaluation",[356,977,418],{"class":978},"s2Cpd",[356,980,334],{"class":366},[356,982,441],{"class":978},[356,984,385],{"class":384},[356,986,988,990,993,996,999,1002],{"class":358,"line":987},10,[356,989,517],{"class":384},[356,991,992],{"class":362}," catch",[356,994,995],{"class":417}," (",[356,997,998],{"class":366},"err",[356,1000,1001],{"class":417},") ",[356,1003,476],{"class":384},[356,1005,1007,1010,1012,1014,1016,1019,1022,1026,1028],{"class":358,"line":1006},11,[356,1008,1009],{"class":362},"  if",[356,1011,995],{"class":978},[356,1013,998],{"class":402},[356,1015,339],{"class":384},[356,1017,1018],{"class":366},"status",[356,1020,1021],{"class":406}," ===",[356,1023,1025],{"class":1024},"s4ofd"," 409",[356,1027,1001],{"class":978},[356,1029,476],{"class":384},[356,1031,1033],{"class":358,"line":1032},12,[356,1034,1035],{"class":952},"    \u002F\u002F Already used. Reject and have the user start the action again.\n",[356,1037,1039,1042,1045,1047,1049,1052,1054,1056],{"class":358,"line":1038},13,[356,1040,1041],{"class":362},"    return",[356,1043,1044],{"class":414}," reject",[356,1046,418],{"class":978},[356,1048,381],{"class":373},[356,1050,1051],{"class":377},"This challenge was already used. Start the action again.",[356,1053,381],{"class":373},[356,1055,441],{"class":978},[356,1057,385],{"class":384},[356,1059,1061],{"class":358,"line":1060},14,[356,1062,1063],{"class":384},"  }\n",[356,1065,1067,1070,1072,1074,1076,1079,1081,1083],{"class":358,"line":1066},15,[356,1068,1069],{"class":362},"  return",[356,1071,1044],{"class":414},[356,1073,418],{"class":978},[356,1075,381],{"class":373},[356,1077,1078],{"class":377},"Could not verify the challenge",[356,1080,381],{"class":373},[356,1082,441],{"class":978},[356,1084,385],{"class":384},[356,1086,1088],{"class":358,"line":1087},16,[356,1089,1090],{"class":384},"}\n",[356,1092,1094],{"class":358,"line":1093},17,[356,1095,392],{"emptyLinePlaceholder":391},[356,1097,1099,1102,1104,1106,1108,1110,1113,1115,1118,1120,1123,1125,1127],{"class":358,"line":1098},18,[356,1100,1101],{"class":362},"if",[356,1103,995],{"class":417},[356,1105,841],{"class":402},[356,1107,339],{"class":384},[356,1109,227],{"class":402},[356,1111,1112],{"class":384},"?.",[356,1114,1018],{"class":366},[356,1116,1117],{"class":406}," !==",[356,1119,374],{"class":373},[356,1121,1122],{"class":377},"completed",[356,1124,381],{"class":373},[356,1126,1001],{"class":417},[356,1128,476],{"class":384},[356,1130,1132],{"class":358,"line":1131},19,[356,1133,1134],{"class":952},"  \u002F\u002F Challenge not completed. Send the user back to the challenge UI.\n",[356,1136,1138,1140,1143,1145,1147,1149,1151,1153],{"class":358,"line":1137},20,[356,1139,1069],{"class":362},[356,1141,1142],{"class":414}," redirect",[356,1144,418],{"class":978},[356,1146,841],{"class":402},[356,1148,339],{"class":384},[356,1150,338],{"class":366},[356,1152,441],{"class":978},[356,1154,385],{"class":384},[356,1156,1158],{"class":358,"line":1157},21,[356,1159,1090],{"class":384},[356,1161,1163,1165],{"class":358,"line":1162},22,[356,1164,1101],{"class":362},[356,1166,1167],{"class":417}," (\n",[356,1169,1171,1173,1175,1177,1179,1181,1183,1186,1189],{"class":358,"line":1170},23,[356,1172,964],{"class":402},[356,1174,339],{"class":384},[356,1176,323],{"class":402},[356,1178,1112],{"class":384},[356,1180,327],{"class":366},[356,1182,1117],{"class":406},[356,1184,1185],{"class":366}," expectedEmail",[356,1187,1188],{"class":406}," ||",[356,1190,1191],{"class":952}," \u002F\u002F Identity mismatch\n",[356,1193,1195,1197,1199,1201,1203,1205,1208,1210,1212],{"class":358,"line":1194},24,[356,1196,964],{"class":402},[356,1198,339],{"class":384},[356,1200,267],{"class":366},[356,1202,1117],{"class":406},[356,1204,374],{"class":373},[356,1206,1207],{"class":377},"YOUR_EXPECTED_ACTION",[356,1209,381],{"class":373},[356,1211,1188],{"class":406},[356,1213,1214],{"class":952}," \u002F\u002F Action mismatch\n",[356,1216,1218,1220,1222,1224,1226,1229,1231,1234],{"class":358,"line":1217},25,[356,1219,964],{"class":402},[356,1221,339],{"class":384},[356,1223,323],{"class":402},[356,1225,1112],{"class":384},[356,1227,1228],{"class":366},"metadata",[356,1230,1117],{"class":406},[356,1232,1233],{"class":402}," YOUR_EXPECTED_METADATA",[356,1235,1236],{"class":952}," \u002F\u002F Metadata mismatch\n",[356,1238,1240,1242],{"class":358,"line":1239},26,[356,1241,1001],{"class":417},[356,1243,476],{"class":384},[356,1245,1247],{"class":358,"line":1246},27,[356,1248,1249],{"class":952},"  \u002F\u002F Integrity mismatch. Block the action.\n",[356,1251,1253,1255,1257,1259,1261,1264,1266,1268],{"class":358,"line":1252},28,[356,1254,1069],{"class":362},[356,1256,1044],{"class":414},[356,1258,418],{"class":978},[356,1260,381],{"class":373},[356,1262,1263],{"class":377},"Integrity mismatch",[356,1265,381],{"class":373},[356,1267,441],{"class":978},[356,1269,385],{"class":384},[356,1271,1273],{"class":358,"line":1272},29,[356,1274,1090],{"class":384},[356,1276,1278],{"class":358,"line":1277},30,[356,1279,392],{"emptyLinePlaceholder":391},[356,1281,1283],{"class":358,"line":1282},31,[356,1284,1285],{"class":952},"\u002F\u002F Honor the action\n",[356,1287,1289,1292,1295],{"class":358,"line":1288},32,[356,1290,1291],{"class":414},"honorTheAction",[356,1293,1294],{"class":417},"()",[356,1296,385],{"class":384},[208,1298,1299,1300,1302,1303,1305],{},"Treat any challenge ",[217,1301,1018],{}," other than ",[217,1304,1122],{}," as a block.",[240,1307,1309],{"id":1308},"where-to-go-next","Where to go next",[293,1311,1312,1319,1326],{},[296,1313,1314,1318],{},[259,1315,1316],{},[212,1317,27],{"href":28},": the signup variant. The user is new with no ID yet, so you store a little state to bind the pending signup to its challenge.",[296,1320,1321,1325],{},[259,1322,1323],{},[212,1324,31],{"href":32},": the login variant. Nothing to store, just hold off issuing the session until the challenge completes.",[296,1327,1328,1332,1333,1335],{},[259,1329,1330],{},[212,1331,44],{"href":45},": a self-managed variant on ",[217,1334,279],{},", with no server step.",[1337,1338,1339],"style",{},"html pre.shiki code .sAPXc, html code.shiki .sAPXc{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#C678DD;--shiki-default-font-style:inherit;--shiki-dark:#F92672;--shiki-dark-font-style:inherit}html pre.shiki code .seeE2, html code.shiki .seeE2{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .s9QZx, html code.shiki .s9QZx{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#E6DB74}html pre.shiki code .siibJ, html code.shiki .siibJ{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#E6DB74}html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .sHm3x, html code.shiki .sHm3x{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#C678DD;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sZ9uN, html code.shiki .sZ9uN{--shiki-light:#90A4AE;--shiki-default:#E5C07B;--shiki-dark:#F8F8F2}html pre.shiki code .sut_7, html code.shiki .sut_7{--shiki-light:#39ADB5;--shiki-default:#56B6C2;--shiki-dark:#F92672}html pre.shiki code .srTuz, html code.shiki .srTuz{--shiki-light:#39ADB5;--shiki-default:#C678DD;--shiki-dark:#F92672}html pre.shiki code .sjp9t, html code.shiki .sjp9t{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#A6E22E}html pre.shiki code .sJCYa, html code.shiki .sJCYa{--shiki-light:#90A4AE;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .sUwfj, html code.shiki .sUwfj{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s2NTT, html code.shiki .s2NTT{--shiki-light:#F76D47;--shiki-default:#C678DD;--shiki-dark:#F92672}html pre.shiki code .sKfv_, html code.shiki .sKfv_{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F92672}html pre.shiki code .sh6BQ, html code.shiki .sh6BQ{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#66D9EF}html pre.shiki code .s42Qa, html code.shiki .s42Qa{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#88846F;--shiki-dark-font-style:inherit}html pre.shiki code .s2Cpd, html code.shiki .s2Cpd{--shiki-light:#E53935;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .s4ofd, html code.shiki .s4ofd{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#AE81FF}",{"title":346,"searchDepth":388,"depth":388,"links":1341},[1342,1343,1344,1345,1346,1347,1348],{"id":242,"depth":388,"text":243},{"id":250,"depth":388,"text":251},{"id":313,"depth":388,"text":314},{"id":685,"depth":388,"text":686},{"id":709,"depth":388,"text":710},{"id":867,"depth":388,"text":868},{"id":1308,"depth":388,"text":1309},"This picks up where the quick start leaves off. You've wired evaluate on the client and the server confirmation. Now a policy returns a challenge verdict and Rupt runs an interactive challenge: it sends the user to a hosted verification page, and when they pass, sends them back to you. This guide is the generic wiring for that round trip, and every other guide that challenges a user reuses it.","md",{},"---\ntitle: Challenge flow\n---\n\n# Challenge flow\n\nThis picks up where the [quick start](\u002Fdocs\u002Fv3\u002Fquick-start) leaves off. You've wired `evaluate` on the client and the server confirmation. Now a [policy](\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpolicies) returns a `challenge` [verdict](\u002Fdocs\u002Fv3\u002Fconcepts\u002Fverdicts) and Rupt runs an interactive [challenge](\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchallenges): it sends the user to a hosted verification page, and when they pass, sends them back to you. This guide is the generic wiring for that round trip, and every other guide that challenges a user reuses it.\n\nThe shape never changes: evaluate on the client, let Rupt run the challenge, and confirm the outcome on your server before you honor the action. The final server step consumes the evaluation, so a passed challenge is single-use and can't be replayed. Nothing trusts the client.\n\n## The flow\n\n\u003C!-- prettier-ignore-start -->\n::MermaidDiagram\n---\ncode: |\n  sequenceDiagram\n    actor U as User\n    participant C as Client app\n    participant S as Your server\n    participant R as Rupt\n    participant X as Challenge UI\n\n    U->>C: Takes the action\n    C->>R: evaluate(action, identifiers)\n    R-->>C: { evaluation_id, redirect? }\n    alt verdict needs a challenge\n      C->>X: Redirect to the challenge UI\n      U->>X: Completes verification\n      X->>C: Return to success_url?evaluation=…\n    end\n    C->>S: Send evaluation_id with the action request\n    S->>R: Consume the evaluation (single-use)\n    R-->>S: { challenge.status, … } or 409 if already used\n    Note over S: Confirm status + integrity, then honor once\n    S-->>U: Honor or block the action\n---\n::\n\u003C!-- prettier-ignore-end -->\n\n## Before you start: the policy and its challenge config\n\nA challenge only happens because a [policy](\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpolicies) told it to. In the dashboard, create a policy whose **action is `challenge`** for the [action](\u002Fdocs\u002Fv3\u002Fconcepts\u002Factions) you're protecting (`login`, `signup`, or `access`) and the [checks](\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks) you want to gate on.\n\nEvery challenge policy points at a **challenge config**, which holds the verification channels and the URLs Rupt uses for the round trip:\n\n- **Success URL**: where Rupt sends the user after they pass. This is the page on your site that finishes the action.\n- **Primary, secondary, and logout URLs**: the links shown inside the challenge UI (for example, \"back to app\" or \"log out\").\n\nYou set these once on the challenge config, and they apply to every challenge that policy issues.\n\n## Step 1: Evaluate on the client\n\nCall `evaluate` at the moment the user takes the action. Pass whatever identifiers you have: a `user` id, `email`, `phone`. The response carries an `evaluation_id` and, when a challenge is required, a `redirect`.\n\n::ClientPlatform\n\n#web\n\n```js\nimport Rupt from \"@ruptjs\u002Fclient\";\n\nconst rupt = new Rupt({ clientId: \"your_client_id\" });\n\nconst evaluation = await rupt.evaluate.login({\n  user: \"USER_ID\",\n  email: form.email,\n});\n```\n\n#ios\n\n```swift\nlet response = try await rupt.evaluate(\n  action: \"login\",\n  user: \"USER_ID\",\n  email: form.email\n)\n```\n\n#android\n\n```kotlin\nval response = rupt.evaluate(\n  action = \"login\",\n  user = \"USER_ID\",\n  email = form.email,\n)\n```\n\n::\n\nFor `access`, the SDK navigates to the challenge UI automatically when one is required. Every other action (`login`, `signup`, and custom events) doesn't auto-navigate by default, so the form your user is filling isn't abandoned mid-submit. The redirect URL comes back in the response and your server decides what to do with it. See [Signup protection](\u002Fdocs\u002Fv3\u002Ffundamentals\u002Fsignup-protection). You can force either behavior per call with `auto_challenge: true | false`.\n\n## Step 2: Set the success URL\n\nIn the dashboard, on the challenge config your policy uses, set **Success URL** to the page that completes the action, for example `https:\u002F\u002Fyourapp.com\u002Fverified`.\n\nWhen the user passes, Rupt redirects there with the evaluation ID appended:\n\n```\nhttps:\u002F\u002Fyourapp.com\u002Fverified?evaluation=68f…\n```\n\n## Step 3: Hand the evaluation ID to your server\n\n::alert{type=\"info\"}\nThis step is optional for account-sharing prevention.\n::\n\nYour success page reads the evaluation ID off the URL and posts it to your backend. The client never decides the outcome. It only carries the evaluation ID across.\n\n```js\nconst params = new URLSearchParams(window.location.search);\n\nawait fetch(\"\u002Fverify-challenge\", {\n  method: \"POST\",\n  body: JSON.stringify({\n    evaluation_id: params.get(\"evaluation\"),\n  }),\n});\n```\n\n## Step 4: Consume the evaluation on your server\n\nMake the final step a consumption. Consuming reads the evaluation and claims it in one atomic, single-use step, so a passed challenge can't be replayed into a second honored action. Confirm the consume succeeded, check that the challenge completed and the evaluation's action and identifiers match what you expected, and only then honor the action. (Flows with no server step, like self-managed account sharing, skip this.)\n\n```js\nimport { RuptAPI } from \"@ruptjs\u002Fapi\";\n\nconst rupt = new RuptAPI(\"API_SECRET\");\n\nlet evaluation;\ntry {\n  \u002F\u002F Consume reads and claims the evaluation in one shot. A replay of the\n  \u002F\u002F success URL gets 409 because the evaluation was already used.\n  evaluation = await rupt.consumeEvaluation(evaluation_id);\n} catch (err) {\n  if (err.status === 409) {\n    \u002F\u002F Already used. Reject and have the user start the action again.\n    return reject(\"This challenge was already used. Start the action again.\");\n  }\n  return reject(\"Could not verify the challenge\");\n}\n\nif (evaluation.challenge?.status !== \"completed\") {\n  \u002F\u002F Challenge not completed. Send the user back to the challenge UI.\n  return redirect(evaluation.redirect);\n}\nif (\n  evaluation.user?.email !== expectedEmail || \u002F\u002F Identity mismatch\n  evaluation.action !== \"YOUR_EXPECTED_ACTION\" || \u002F\u002F Action mismatch\n  evaluation.user?.metadata !== YOUR_EXPECTED_METADATA \u002F\u002F Metadata mismatch\n) {\n  \u002F\u002F Integrity mismatch. Block the action.\n  return reject(\"Integrity mismatch\");\n}\n\n\u002F\u002F Honor the action\nhonorTheAction();\n```\n\nTreat any challenge `status` other than `completed` as a block.\n\n## Where to go next\n\n- **[Signup protection](\u002Fdocs\u002Fv3\u002Ffundamentals\u002Fsignup-protection)**: the signup variant. The user is new with no ID yet, so you store a little state to bind the pending signup to its challenge.\n- **[Login protection](\u002Fdocs\u002Fv3\u002Ffundamentals\u002Flogin-protection)**: the login variant. Nothing to store, just hold off issuing the session until the challenge completes.\n- **[Account sharing prevention](\u002Fdocs\u002Fv3\u002Fguides\u002Faccount-sharing-prevention)**: a self-managed variant on `access`, with no server step.\n",{"title":18,"description":1349},"kUuNzLy82NaZ2wZoWnKB4L-hDOtJoRzVykO2LN5OOcA",1780344892810]