[{"data":1,"prerenderedAt":1976},["ShallowReactive",2],{"docsv3-nav":3,"\u002Fdocs\u002Fv3\u002Fmigration\u002Foverview":236,"docs-search-nav":450,"docs-search-sections":611},[4],{"title":5,"path":6,"stem":7,"children":8,"page":188},"V3","\u002Fdocs\u002Fv3","1.docs\u002Fv3",[9,13,17,21,38,87,189,198,219],{"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",{"title":199,"path":200,"stem":201,"children":202},"Integrations","\u002Fdocs\u002Fv3\u002Fintegrations","1.docs\u002Fv3\u002F8.integrations",[203,207,211,215],{"title":204,"path":205,"stem":206},"Kajabi","\u002Fdocs\u002Fv3\u002Fintegrations\u002Fkajabi-account-sharing-prevention","1.docs\u002Fv3\u002F8.integrations\u002F1.Kajabi account sharing prevention",{"title":208,"path":209,"stem":210},"Teachable","\u002Fdocs\u002Fv3\u002Fintegrations\u002Fteachable-account-sharing-prevention","1.docs\u002Fv3\u002F8.integrations\u002F2.Teachable account sharing prevention",{"title":212,"path":213,"stem":214},"Thinkific","\u002Fdocs\u002Fv3\u002Fintegrations\u002Fthinkific-account-sharing-prevention","1.docs\u002Fv3\u002F8.integrations\u002F3.Thinkific account sharing prevention",{"title":216,"path":217,"stem":218},"LearnWorlds","\u002Fdocs\u002Fv3\u002Fintegrations\u002Flearnworlds-account-sharing-prevention","1.docs\u002Fv3\u002F8.integrations\u002F4.LearnWorlds account sharing prevention",{"title":220,"path":221,"stem":222,"children":223,"page":188},"Migration","\u002Fdocs\u002Fv3\u002Fmigration","1.docs\u002Fv3\u002F9.migration",[224,228,232],{"title":225,"path":226,"stem":227},"Overview","\u002Fdocs\u002Fv3\u002Fmigration\u002Foverview","1.docs\u002Fv3\u002F9.migration\u002F1.Overview",{"title":229,"path":230,"stem":231},"Account sharing on web","\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-web","1.docs\u002Fv3\u002F9.migration\u002F2.Account sharing on web",{"title":233,"path":234,"stem":235},"Account sharing on mobile","\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile","1.docs\u002Fv3\u002F9.migration\u002F3.Account sharing on mobile",{"id":237,"title":225,"body":238,"description":443,"extension":444,"meta":445,"navigation":446,"path":226,"rawbody":447,"seo":448,"stem":227,"__hash__":449},"docsv3\u002F1.docs\u002Fv3\u002F9.migration\u002F1.Overview.md",{"type":239,"value":240,"toc":436},"minimark",[241,246,258,269,274,277,350,367,371,382,407,414,420,424],[242,243,245],"h1",{"id":244},"migrating-from-v2-to-v3","Migrating from v2 to v3",[247,248,249,250,257],"p",{},"v3 is a new API with a new SDK on every platform. The clients are not drop-in compatible with v2, so plan for a real upgrade. The upside is that the v3 client is smaller: most of the wiring you did by hand now lives on a ",[251,252,256],"a",{"href":253,"rel":254},"https:\u002F\u002Fapp.rupt.dev\u002Fpolicies",[255],"nofollow","policy"," in the dashboard, and the SDK handles the rest.",[247,259,260,261,264,265,268],{},"This section walks the migration one product at a time. It starts with account sharing, the simplest one, ",[251,262,263],{"href":230},"on web"," and ",[251,266,267],{"href":234},"on iOS and Android",".",[270,271,273],"h2",{"id":272},"what-changed-at-a-glance","What changed at a glance",[247,275,276],{},"v2 and v3 refer to the Rupt API. Each platform ships its own client library on top of it, and every library has its own version number. This table maps each API version to the library that talks to it:",[278,279,280,296],"table",{},[281,282,283],"thead",{},[284,285,286,290,293],"tr",{},[287,288,289],"th",{},"Platform",[287,291,292],{},"API v2 library",[287,294,295],{},"API v3 library",[297,298,299,318,334],"tbody",{},[284,300,301,305,312],{},[302,303,304],"td",{},"Web",[302,306,307,311],{},[308,309,310],"code",{},"rupt"," 2.x",[302,313,314,317],{},[308,315,316],{},"@ruptjs\u002Fclient"," 3.x",[284,319,320,323,329],{},[302,321,322],{},"iOS",[302,324,325,328],{},[308,326,327],{},"RuptClient"," 3.8.1",[302,330,331,333],{},[308,332,327],{}," 4.0.0",[284,335,336,339,345],{},[302,337,338],{},"Android",[302,340,341,344],{},[308,342,343],{},"com.github.getrupt:kotlin"," 2.1.0",[302,346,347,333],{},[308,348,349],{},"dev.rupt.android:rupt-android",[351,352,354],"alert",{"type":353},"info",[247,355,356,357,359,360,362,363,366],{},"The library version and the API version are two different numbers. The v3 clients are ",[308,358,316],{}," 3.x, ",[308,361,327],{}," 4.0.0, and ",[308,364,365],{},"rupt-android"," 4.0.0. They all talk to the same v3 API. When you see a version on a package, that's the library's version, not the API's.",[270,368,370],{"id":369},"the-one-idea-to-hold-onto","The one idea to hold onto",[247,372,373,374,377,378,381],{},"v2 gave each product its own method. Account sharing was ",[308,375,376],{},"attach",". v3 has a single entry point, ",[308,379,380],{},"evaluate",", and the action names the product:",[383,384,385,395,401],"ul",{},[386,387,388,391,392,394],"li",{},[308,389,390],{},"evaluate.access"," runs the account-sharing check (this was ",[308,393,376],{},").",[386,396,397,400],{},[308,398,399],{},"evaluate.login"," runs the login check.",[386,402,403,406],{},[308,404,405],{},"evaluate.signup"," runs the signup check.",[247,408,409,410,413],{},"Challenges are self-managed now. In v2 you passed redirect URLs and challenge callbacks into the client. In v3 the challenge is configured on a policy in the dashboard, gated on the ",[251,411,412],{"href":106},"checks"," you choose, and the SDK surfaces it for you. Your client code shrinks to one call.",[247,415,416,417,419],{},"Account sharing is fully client-side: you call ",[308,418,390],{}," and Rupt handles detection, the challenge, owner verification, and device capping. There is no server step and no evaluation to consume. Login and signup do add a server-side verification, and they will get their own migration pages here.",[270,421,423],{"id":422},"related","Related",[383,425,426,431],{},[386,427,428,430],{},[251,429,14],{"href":15},": the shape of a fresh v3 integration.",[386,432,433,435],{},[251,434,35],{"href":36},": the account-sharing fundamental in full.",{"title":437,"searchDepth":438,"depth":438,"links":439},"",2,[440,441,442],{"id":272,"depth":438,"text":273},{"id":369,"depth":438,"text":370},{"id":422,"depth":438,"text":423},"How to move an existing v2 integration onto the Rupt v3 API and SDKs, one product at a time. Start with account sharing.","md",{},true,"---\ntitle: Overview\ndescription: How to move an existing v2 integration onto the Rupt v3 API and SDKs, one product at a time. Start with account sharing.\n---\n\n# Migrating from v2 to v3\n\nv3 is a new API with a new SDK on every platform. The clients are not drop-in compatible with v2, so plan for a real upgrade. The upside is that the v3 client is smaller: most of the wiring you did by hand now lives on a [policy](https:\u002F\u002Fapp.rupt.dev\u002Fpolicies) in the dashboard, and the SDK handles the rest.\n\nThis section walks the migration one product at a time. It starts with account sharing, the simplest one, [on web](\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-web) and [on iOS and Android](\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile).\n\n## What changed at a glance\n\nv2 and v3 refer to the Rupt API. Each platform ships its own client library on top of it, and every library has its own version number. This table maps each API version to the library that talks to it:\n\n| Platform | API v2 library                    | API v3 library                        |\n| -------- | --------------------------------- | ------------------------------------- |\n| Web      | `rupt` 2.x                        | `@ruptjs\u002Fclient` 3.x                  |\n| iOS      | `RuptClient` 3.8.1                | `RuptClient` 4.0.0                    |\n| Android  | `com.github.getrupt:kotlin` 2.1.0 | `dev.rupt.android:rupt-android` 4.0.0 |\n\n::alert{type=\"info\"}\nThe library version and the API version are two different numbers. The v3 clients are `@ruptjs\u002Fclient` 3.x, `RuptClient` 4.0.0, and `rupt-android` 4.0.0. They all talk to the same v3 API. When you see a version on a package, that's the library's version, not the API's.\n::\n\n## The one idea to hold onto\n\nv2 gave each product its own method. Account sharing was `attach`. v3 has a single entry point, `evaluate`, and the action names the product:\n\n- `evaluate.access` runs the account-sharing check (this was `attach`).\n- `evaluate.login` runs the login check.\n- `evaluate.signup` runs the signup check.\n\nChallenges are self-managed now. In v2 you passed redirect URLs and challenge callbacks into the client. In v3 the challenge is configured on a policy in the dashboard, gated on the [checks](\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks) you choose, and the SDK surfaces it for you. Your client code shrinks to one call.\n\nAccount sharing is fully client-side: you call `evaluate.access` and Rupt handles detection, the challenge, owner verification, and device capping. There is no server step and no evaluation to consume. Login and signup do add a server-side verification, and they will get their own migration pages here.\n\n## Related\n\n- [Quick start](\u002Fdocs\u002Fv3\u002Fquick-start): the shape of a fresh v3 integration.\n- [Access protection](\u002Fdocs\u002Fv3\u002Ffundamentals\u002Faccess-protection): the account-sharing fundamental in full.\n",{"title":225,"description":443},"_TLIx3EMHhqjtgKO01T5fZZqswXcqmmiSQODOJe1SqA",[451,514],{"title":5,"path":6,"stem":7,"children":452,"page":188},[453,454,455,456,461,474,500,503,509],{"title":10,"path":11,"stem":12},{"title":14,"path":15,"stem":16},{"title":18,"path":19,"stem":20},{"title":22,"path":23,"stem":24,"children":457},[458,459,460],{"title":27,"path":28,"stem":29},{"title":31,"path":32,"stem":33},{"title":35,"path":36,"stem":37},{"title":39,"path":40,"stem":41,"children":462},[463,464,465,466,467,468,469,470,471,472,473],{"title":44,"path":45,"stem":46},{"title":48,"path":49,"stem":50},{"title":52,"path":53,"stem":54},{"title":56,"path":57,"stem":58},{"title":60,"path":61,"stem":62},{"title":64,"path":65,"stem":66},{"title":68,"path":69,"stem":70},{"title":72,"path":73,"stem":74},{"title":76,"path":77,"stem":78},{"title":80,"path":81,"stem":82},{"title":84,"path":85,"stem":86},{"title":88,"path":89,"stem":90,"children":475,"page":188},[476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499],{"title":93,"path":94,"stem":95},{"title":97,"path":98,"stem":99},{"title":101,"path":102,"stem":103},{"title":105,"path":106,"stem":107},{"title":109,"path":110,"stem":111},{"title":113,"path":114,"stem":115},{"title":117,"path":118,"stem":119},{"title":121,"path":122,"stem":123},{"title":125,"path":126,"stem":127},{"title":129,"path":130,"stem":131},{"title":133,"path":134,"stem":135},{"title":137,"path":138,"stem":139},{"title":141,"path":142,"stem":143},{"title":145,"path":146,"stem":147},{"title":149,"path":150,"stem":151},{"title":153,"path":154,"stem":155},{"title":157,"path":158,"stem":159},{"title":161,"path":162,"stem":163},{"title":165,"path":166,"stem":167},{"title":169,"path":170,"stem":171},{"title":173,"path":174,"stem":175},{"title":177,"path":178,"stem":179},{"title":181,"path":182,"stem":183},{"title":185,"path":186,"stem":187},{"title":190,"path":191,"stem":192,"children":501,"page":188},[502],{"title":195,"path":196,"stem":197},{"title":199,"path":200,"stem":201,"children":504},[505,506,507,508],{"title":204,"path":205,"stem":206},{"title":208,"path":209,"stem":210},{"title":212,"path":213,"stem":214},{"title":216,"path":217,"stem":218},{"title":220,"path":221,"stem":222,"children":510,"page":188},[511,512,513],{"title":225,"path":226,"stem":227},{"title":229,"path":230,"stem":231},{"title":233,"path":234,"stem":235},{"title":5,"path":515,"stem":516,"children":517,"page":188},"\u002Fapi\u002Fv3","2.api\u002Fv3",[518,521,525,537,545,562,578],{"title":10,"path":519,"stem":520},"\u002Fapi\u002Fv3\u002Fintroduction","2.api\u002Fv3\u002F1.Introduction",{"title":522,"path":523,"stem":524},"Errors","\u002Fapi\u002Fv3\u002Ferrors","2.api\u002Fv3\u002F2.Errors",{"title":121,"path":526,"stem":527,"children":528},"\u002Fapi\u002Fv3\u002Fchallenges","2.api\u002Fv3\u002F4.Challenges",[529,533],{"title":530,"path":531,"stem":532},"The challenge object","\u002Fapi\u002Fv3\u002Fchallenges\u002Fthe-challenge-object","2.api\u002Fv3\u002F4.Challenges\u002F1.The challenge object",{"title":534,"path":535,"stem":536},"Retrieve a challenge","\u002Fapi\u002Fv3\u002Fchallenges\u002Fretrieve-a-challenge","2.api\u002Fv3\u002F4.Challenges\u002F2.Retrieve a challenge",{"title":137,"path":538,"stem":539,"children":540},"\u002Fapi\u002Fv3\u002Fdevices","2.api\u002Fv3\u002F5.Devices",[541],{"title":542,"path":543,"stem":544},"The device object","\u002Fapi\u002Fv3\u002Fdevices\u002Fthe-device-object","2.api\u002Fv3\u002F5.Devices\u002F1.The device object",{"title":546,"path":547,"stem":548,"children":549},"Users","\u002Fapi\u002Fv3\u002Fusers","2.api\u002Fv3\u002F6.Users",[550,554,558],{"title":551,"path":552,"stem":553},"The user object","\u002Fapi\u002Fv3\u002Fusers\u002Fthe-user-object","2.api\u002Fv3\u002F6.Users\u002F1.The user object",{"title":555,"path":556,"stem":557},"Update a user","\u002Fapi\u002Fv3\u002Fusers\u002Fupdate-a-user","2.api\u002Fv3\u002F6.Users\u002F2.Update a user",{"title":559,"path":560,"stem":561},"Retrieve user devices","\u002Fapi\u002Fv3\u002Fusers\u002Fretrieve-user-devices","2.api\u002Fv3\u002F6.Users\u002F3.Retrieve user devices",{"title":93,"path":563,"stem":564,"children":565},"\u002Fapi\u002Fv3\u002Fevaluations","2.api\u002Fv3\u002F7.Evaluations",[566,570,574],{"title":567,"path":568,"stem":569},"The evaluation object","\u002Fapi\u002Fv3\u002Fevaluations\u002Fthe-evaluation-object","2.api\u002Fv3\u002F7.Evaluations\u002F1.The evaluation object",{"title":571,"path":572,"stem":573},"Retrieve an evaluation","\u002Fapi\u002Fv3\u002Fevaluations\u002Fretrieve-an-evaluation","2.api\u002Fv3\u002F7.Evaluations\u002F2.Retrieve an evaluation",{"title":575,"path":576,"stem":577},"Consume an evaluation","\u002Fapi\u002Fv3\u002Fevaluations\u002Fconsume-an-evaluation","2.api\u002Fv3\u002F7.Evaluations\u002F3.Consume an evaluation",{"title":579,"path":580,"stem":581,"children":582,"page":188},"Webhooks","\u002Fapi\u002Fv3\u002Fwebhooks","2.api\u002Fv3\u002F98.Webhooks",[583,587,591,595,599,603,607],{"title":584,"path":585,"stem":586},"Challenge initiated","\u002Fapi\u002Fv3\u002Fwebhooks\u002Fchallenge-initiated","2.api\u002Fv3\u002F98.Webhooks\u002F1.Challenge initiated",{"title":588,"path":589,"stem":590},"Challenge pending","\u002Fapi\u002Fv3\u002Fwebhooks\u002Fchallenge-pending","2.api\u002Fv3\u002F98.Webhooks\u002F2.Challenge pending",{"title":592,"path":593,"stem":594},"Challenge skipped","\u002Fapi\u002Fv3\u002Fwebhooks\u002Fchallenge-skipped","2.api\u002Fv3\u002F98.Webhooks\u002F3.Challenge skipped",{"title":596,"path":597,"stem":598},"Challenge completed","\u002Fapi\u002Fv3\u002Fwebhooks\u002Fchallenge-completed","2.api\u002Fv3\u002F98.Webhooks\u002F4.Challenge completed",{"title":600,"path":601,"stem":602},"Account sharing signal","\u002Fapi\u002Fv3\u002Fwebhooks\u002Faccount-sharing-signal","2.api\u002Fv3\u002F98.Webhooks\u002F5.Account sharing signal",{"title":604,"path":605,"stem":606},"Device detached","\u002Fapi\u002Fv3\u002Fwebhooks\u002Fdevice-detached","2.api\u002Fv3\u002F98.Webhooks\u002F6.Device detached",{"title":608,"path":609,"stem":610},"User converted","\u002Fapi\u002Fv3\u002Fwebhooks\u002Fuser-converted","2.api\u002Fv3\u002F98.Webhooks\u002F7.User converted",[612,616,620,625,628,632,637,642,647,652,657,660,664,668,673,678,683,688,693,697,700,704,707,711,716,720,725,730,735,740,745,748,752,756,759,764,768,772,777,780,784,789,794,798,801,805,810,813,816,819,824,829,833,836,839,844,848,852,855,858,863,867,871,874,877,882,887,891,894,897,901,905,909,912,915,919,923,927,930,933,938,942,946,949,952,956,960,964,967,970,974,978,982,985,988,993,997,1001,1004,1007,1011,1015,1019,1022,1026,1031,1036,1038,1042,1047,1053,1058,1063,1068,1073,1075,1079,1084,1089,1094,1099,1104,1109,1114,1116,1120,1125,1130,1135,1140,1144,1149,1154,1159,1164,1169,1174,1179,1184,1189,1192,1196,1201,1206,1211,1216,1221,1226,1229,1233,1238,1243,1248,1250,1254,1259,1264,1269,1274,1277,1281,1286,1291,1296,1301,1304,1308,1313,1318,1323,1326,1330,1335,1340,1344,1349,1352,1356,1361,1366,1370,1373,1377,1382,1387,1392,1395,1399,1404,1409,1414,1417,1421,1426,1431,1436,1439,1443,1448,1453,1458,1463,1466,1470,1475,1480,1482,1486,1490,1494,1497,1501,1505,1509,1512,1516,1520,1524,1527,1531,1535,1539,1542,1546,1551,1555,1557,1561,1566,1570,1572,1576,1581,1585,1588,1592,1597,1601,1604,1608,1611,1614,1618,1623,1628,1633,1638,1643,1647,1651,1656,1661,1665,1669,1672,1676,1681,1686,1690,1694,1697,1701,1706,1711,1715,1719,1722,1724,1728,1732,1736,1740,1743,1747,1752,1757,1762,1767,1772,1776,1779,1784,1788,1793,1798,1803,1808,1812,1817,1821,1825,1830,1835,1840,1844,1846,1850,1852,1856,1858,1862,1864,1868,1870,1874,1876,1880,1882,1886,1888,1892,1894,1898,1900,1904,1906,1910,1912,1916,1918,1922,1924,1928,1930,1934,1936,1940,1942,1946,1948,1952,1954,1958,1960,1964,1966,1970,1972],{"id":11,"title":10,"titles":613,"content":614,"level":615},[],"Rupt is the unified user trust and fraud prevention platform. Start here if you're new; jump to the guides if you're already integrated.",1,{"id":617,"title":10,"titles":618,"content":619,"level":615},"\u002Fdocs\u002Fv3\u002Fintroduction#introduction",[],"Rupt is the unified user trust and fraud prevention platform — one evaluation covers account takeover, fake accounts, account sharing, scraping, bots, and more.",{"id":621,"title":622,"titles":623,"content":624,"level":438},"\u002Fdocs\u002Fv3\u002Fintroduction#where-to-go-next","Where to go next",[10],"New to Rupt? Start with the Quick start — three steps to wire up your first evaluation.Already integrated? Head to the guides for recommendations on specific fraud-prevention scenarios.Want the mental model first? Read the Concepts — what evaluations, signals, checks, risks, verdicts, policies, and challenges actually mean.",{"id":15,"title":14,"titles":626,"content":627,"level":615},[],"Rupt works the same on every platform: run an evaluation when the user takes an action, then confirm it on your server before you honor it. Here's the flow:",{"id":629,"title":14,"titles":630,"content":631,"level":615},"\u002Fdocs\u002Fv3\u002Fquick-start#quick-start",[],"Rupt works the same on every platform: run an evaluation when the user takes an action, then confirm it on your server before you honor it. Here's the flow: The integrity check is where your server confirms the evaluation matches what your client app sent and hasn't been tampered with.",{"id":633,"title":634,"titles":635,"content":636,"level":438},"\u002Fdocs\u002Fv3\u002Fquick-start#_1-install-the-rupt-sdk","1. Install the Rupt SDK",[14],"npm install @ruptjs\u002Fclient\nyarn add @ruptjs\u002Fclient\nbun add @ruptjs\u002Fclient\n\u003Cscript src=\"https:\u002F\u002Fcdn.rupt.dev\u002Frupt\u002Fjs\u002Fv3\u002Fclient.js\">\u003C\u002Fscript>\nDownload the SDK binary from RuptClient.xcframework.zip.Unzip and drag RuptClient.xcframework into your Xcode project.Select Copy items if needed and click Finish.Make sure mavenCentral() and jitpack are listed under dependencyResolutionManagement in settings.gradle.Add Rupt to your dependencies: implementation(\"dev.rupt.android:rupt-android:4.0.0\").",{"id":638,"title":639,"titles":640,"content":641,"level":438},"\u002Fdocs\u002Fv3\u002Fquick-start#_2-run-an-evaluation-client-side","2. Run an evaluation (client-side)",[14],"Call evaluate() at the moment the user takes the action (e.g. right after they submit the login, sign up, or checkout). import Rupt from \"@ruptjs\u002Fclient\";\n\nconst rupt = new Rupt({ clientId: \"your_client_id\" });\n\n\u002F\u002F Call rupt.evaluate.login \u002F .signup \u002F .access for the action you're protecting.\nconst response = await rupt.evaluate.login({\n  user: \"USER_ID\",\n  email: \"EMAIL\",\n  phone: \"PHONE\",\n  metadata: { key: \"value\" },\n});\nimport Rupt\n\nlet rupt = Rupt(clientID: \"your_client_id\")\n\nlet response = try await rupt.evaluate(\n  action: \"login\", \u002F\u002F \"login\", \"signup\", \"access\", or any custom action\n  user: \"USER_ID\",\n  email: \"EMAIL\",\n  phone: \"PHONE\",\n  metadata: [\"key\": \"value\"]\n)\nimport dev.rupt.Rupt\n\nval rupt = Rupt(context, clientId = \"your_client_id\")\n\nval response = rupt.evaluate(\n  action = \"login\", \u002F\u002F \"login\", \"signup\", \"access\", or any custom action\n  user = \"USER_ID\",\n  email = \"EMAIL\",\n  phone = \"PHONE\",\n  metadata = mapOf(\"key\" to \"value\"),\n) The response includes the evaluation_id that you need to send to your server in step 3.",{"id":643,"title":644,"titles":645,"content":646,"level":438},"\u002Fdocs\u002Fv3\u002Fquick-start#_3-confirm-the-evaluation-server-side","3. Confirm the evaluation (server-side)",[14],"This step takes place on your server. Your server should take the evaluation_id from the client and get that evaluation details from Rupt. import { RuptAPI } from \"@ruptjs\u002Fapi\";\n\nconst rupt = new RuptAPI(\"API_SECRET\");\nconst evaluation = await rupt.getEvaluation(evaluation_id);\nusing Rupt.Api;\n\nvar rupt = new RuptApi(\"API_SECRET\");\nvar evaluation = await rupt.GetEvaluationAsync(evaluationId);\ncurl \"https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fevaluations\u002F$EVALUATION_ID\" \\\n  -H \"Authorization: Bearer $API_SECRET\" The response includes the verdict, the action, the user details Rupt received, the policy that matched, the risks detected, and the challenge if one was issued, and more: {\n  \"id\": \"...\",\n  \"action\": \"login\",\n  \"verdict\": \"allow\",\n  \"user\": {\n    \"rupt_id\": \"...\",\n    \"id\": \"USER_ID\", \u002F\u002F The user ID you provided to Rupt\n    \"email\": \"EMAIL\", \u002F\u002F The email you provided to Rupt\n    \"phone\": \"PHONE\" \u002F\u002F The phone you provided to Rupt\n  },\n  \"metadata\": { \"key\": \"value\" }, \u002F\u002F The metadata you provided to Rupt\n  \"policy\": { \"id\": \"...\", \"name\": \"...\", \"action\": { \"type\": \"allow\" } }, \u002F\u002F The policy that matched\n  \"challenge\": null, \u002F\u002F The challenge details if one was issued\n  \"redirect\": null, \u002F\u002F The redirect URL if one was issued\n  \"risks\": [\n    \u002F\u002F The risks detected\n    { \"id\": \"...\", \"definition\": \"...\", \"name\": \"ato\", \"severity\": \"low\", \"score\": 3, \"indicators\": [\"new_fingerprint\", \"new_ip\"] }\n  ],\n  \"createdAt\": \"...\",\n  \"updatedAt\": \"...\"\n} Confirm that the evaluation matches the action you expected, the user you expected, and the metadata you expected. Treat any mismatch as a bad actor and block the action.",{"id":648,"title":649,"titles":650,"content":651,"level":438},"\u002Fdocs\u002Fv3\u002Fquick-start#_4-handle-challenges","4. Handle challenges",[14],"Out of the box you have no policies, so every evaluation comes back allow and no challenge fires. That's expected; the steps above give you the plumbing and all the data you need to start writing policies that challenge, deny, or gate an action. Policies that issue a deny verdict are easy; you should refuse the action immediately. Challenges are a little more When a policy does issue a challenge verdict, the evaluation comes back with a redirect. You should send the user to that redirect URL. It contains a hosted challenge. When the user completes the challenge, you should confirm it on your server before honoring the action. The Challenge flow walks through it end to end. Start with the two foundations; every other use case builds on them: Signup protection — at signup the user is new and has no ID yet, so you store a little state to bind the pending signup to its challenge.Login protection — at login you don't store anything; you just hold off issuing the session until the challenge completes.",{"id":653,"title":654,"titles":655,"content":656,"level":438},"\u002Fdocs\u002Fv3\u002Fquick-start#next-steps","Next steps",[14],"Concepts — what evaluations, signals, checks, risks, verdicts, policies, and challenges actually mean.Guides — recommendations on specific fraud-prevention scenarios.Proxy setup — route Rupt traffic through your own domain to bypass adblockers and JS-domain blocking. html pre.shiki code .sHrIR, html code.shiki .sHrIR{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#A6E22E}html pre.shiki code .siibJ, html code.shiki .siibJ{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#E6DB74}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 .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 .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 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 .s2NTT, html code.shiki .s2NTT{--shiki-light:#F76D47;--shiki-default:#C678DD;--shiki-dark:#F92672}html pre.shiki code .sdgkD, html code.shiki .sdgkD{--shiki-light:#90A4AE;--shiki-light-text-decoration:inherit;--shiki-default:#E5C07B;--shiki-default-text-decoration:inherit;--shiki-dark:#A6E22E;--shiki-dark-text-decoration:underline}html pre.shiki code .sUO3M, html code.shiki .sUO3M{--shiki-light:#E2931D;--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 .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .sKfv_, html code.shiki .sKfv_{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F92672}html pre.shiki code .sX0Ul, html code.shiki .sX0Ul{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#E5C07B;--shiki-default-text-decoration:inherit;--shiki-dark:#A6E22E;--shiki-dark-text-decoration:underline}html pre.shiki code .sIaD8, html code.shiki .sIaD8{--shiki-light:#90A4AE;--shiki-default:#56B6C2;--shiki-dark:#AE81FF}html pre.shiki code .spvyc, html code.shiki .spvyc{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .s32IW, html code.shiki .s32IW{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .s49Q_, html code.shiki .s49Q_{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sw10c, html code.shiki .sw10c{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s9uTm, html code.shiki .s9uTm{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s4VVQ, html code.shiki .s4VVQ{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sfLoi, html code.shiki .sfLoi{--shiki-light:#F76D47;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .stE5w, html code.shiki .stE5w{--shiki-light:#39ADB5;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .s4ofd, html code.shiki .s4ofd{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .slwgX, html code.shiki .slwgX{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#F92672}html pre.shiki code .sXIpk, html code.shiki .sXIpk{--shiki-light:#9C3EDA;--shiki-default:#D19A66;--shiki-dark:#A6E22E}html pre.shiki code .sh6BQ, html code.shiki .sh6BQ{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#66D9EF}html pre.shiki code .s3gOb, html code.shiki .s3gOb{--shiki-light:#E2931D;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}",{"id":19,"title":18,"titles":658,"content":659,"level":615},[],"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.",{"id":661,"title":18,"titles":662,"content":663,"level":615},"\u002Fdocs\u002Fv3\u002Fchallenge-flow#challenge-flow",[],"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. 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. Account sharing prevention is a special case and doesn't follow this flow. See Access protection instead!.",{"id":665,"title":666,"titles":667,"content":437,"level":438},"\u002Fdocs\u002Fv3\u002Fchallenge-flow#the-flow","The flow",[18],{"id":669,"title":670,"titles":671,"content":672,"level":438},"\u002Fdocs\u002Fv3\u002Fchallenge-flow#before-you-start-the-policy-and-its-challenge-config","Before you start: the policy and its challenge config",[18],"A challenge only happens because a policy told it to. In the dashboard, create a policy whose action is challenge for the action you're protecting (login, signup, or access) and the checks you want to gate on. Every challenge policy points at a challenge config, which holds the verification channels and the URLs Rupt uses for the round trip: Success URL: where Rupt sends the user after they pass. This is the page on your site that finishes the action.Primary, secondary, and logout URLs: the links shown inside the challenge UI (for example, \"back to app\" or \"log out\"). You set these once on the challenge config, and they apply to every challenge that policy issues.",{"id":674,"title":675,"titles":676,"content":677,"level":438},"\u002Fdocs\u002Fv3\u002Fchallenge-flow#step-1-evaluate-on-the-client","Step 1: Evaluate on the client",[18],"Call 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. 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});\nlet response = try await rupt.evaluate(\n  action: \"login\",\n  user: \"USER_ID\",\n  email: form.email\n)\nval response = rupt.evaluate(\n  action = \"login\",\n  user = \"USER_ID\",\n  email = form.email,\n) You will then hand the evaluation ID to your server. The server will get the evaluation details (including the verdict and redirect URL) and hand them back to the client if needed.",{"id":679,"title":680,"titles":681,"content":682,"level":438},"\u002Fdocs\u002Fv3\u002Fchallenge-flow#step-2-set-the-success-url","Step 2: Set the success URL",[18],"In 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. When the user passes, Rupt redirects there with the evaluation ID appended: https:\u002F\u002Fyourapp.com\u002Fverified?evaluation=68f…",{"id":684,"title":685,"titles":686,"content":687,"level":438},"\u002Fdocs\u002Fv3\u002Fchallenge-flow#step-3-hand-the-evaluation-id-to-your-server","Step 3: Hand the evaluation ID to your server",[18],"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. 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});",{"id":689,"title":690,"titles":691,"content":692,"level":438},"\u002Fdocs\u002Fv3\u002Fchallenge-flow#step-4-consume-the-evaluation-on-your-server","Step 4: Consume the evaluation on your server",[18],"When a client in your success page hands you an evaluation ID, you should consume it. This gives you the evaluation state and marks it as consumed. This is done to prevent someone from taking a successful challenge and using it again to honor other actions. Check that the challenge completed and the evaluation's action and identifiers match what you expected, and only then honor the action. 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.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();\nusing Rupt.Api;\n\nvar rupt = new RuptApi(\"API_SECRET\");\n\nEvaluation evaluation;\ntry\n{\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.ConsumeEvaluationAsync(evaluationId);\n}\ncatch (RuptApiException ex) when (ex.StatusCode == 409)\n{\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}\ncatch (RuptException)\n{\n    return Reject(\"Could not verify the challenge\");\n}\n\nif (evaluation.Challenge?.Status != \"completed\")\n{\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)\n{\n    \u002F\u002F Integrity mismatch. Block the action.\n    return Reject(\"Integrity mismatch\");\n}\n\n\u002F\u002F Honor the action\nHonorTheAction();\n# Consume reads and claims the evaluation in one shot. A replay of the\n# success URL gets 409 because the evaluation was already used.\ncurl -X POST \"https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fevaluations\u002F$EVALUATION_ID\u002Fconsume\" \\\n  -H \"Authorization: Bearer $API_SECRET\" If you consume via the raw API, check challenge.status, action, and the user identifiers in the response before you honor the action, exactly as the SDK examples do. Treat any challenge status other than completed as a block.",{"id":694,"title":622,"titles":695,"content":696,"level":438},"\u002Fdocs\u002Fv3\u002Fchallenge-flow#where-to-go-next",[18],"Signup 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.Login protection: the login variant. Nothing to store, just hold off issuing the session until the challenge completes.Account sharing prevention: a self-managed variant on access, with no server step. 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 .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}html pre.shiki code .s2NTT, html code.shiki .s2NTT{--shiki-light:#F76D47;--shiki-default:#C678DD;--shiki-dark:#F92672}html pre.shiki code .sdgkD, html code.shiki .sdgkD{--shiki-light:#90A4AE;--shiki-light-text-decoration:inherit;--shiki-default:#E5C07B;--shiki-default-text-decoration:inherit;--shiki-dark:#A6E22E;--shiki-dark-text-decoration:underline}html pre.shiki code .sUO3M, html code.shiki .sUO3M{--shiki-light:#E2931D;--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 .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .sKfv_, html code.shiki .sKfv_{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F92672}html pre.shiki code .sX0Ul, html code.shiki .sX0Ul{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#E5C07B;--shiki-default-text-decoration:inherit;--shiki-dark:#A6E22E;--shiki-dark-text-decoration:underline}html pre.shiki code .sHrIR, html code.shiki .sHrIR{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#A6E22E}html pre.shiki code .spvyc, html code.shiki .spvyc{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .sIaD8, html code.shiki .sIaD8{--shiki-light:#90A4AE;--shiki-default:#56B6C2;--shiki-dark:#AE81FF}html pre.shiki code .sh6BQ, html code.shiki .sh6BQ{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#66D9EF}",{"id":23,"title":22,"titles":698,"content":699,"level":615},[],"These three patterns sit under every Rupt integration. Get them down first; every guide that follows is a variation on one of them with a different policy and different checks.",{"id":701,"title":22,"titles":702,"content":703,"level":615},"\u002Fdocs\u002Fv3\u002Ffundamentals#fundamentals",[],"These three patterns sit under every Rupt integration. Get them down first; every guide that follows is a variation on one of them with a different policy and different checks. Login protection — don't issue a session while a challenge is outstanding.Signup protection — bind the new, ID-less user to its challenge so it can't be bypassed.Access protection — stop account sharing on everything in between, tuned conservatively since a false sharing accusation is costly. Login and signup share the same challenge flow and differ only in what you hold onto — a session at login, a new user at signup. Access protection is the special case: it's account sharing, runs fully client-side, and optimizes for converting extra users into their own accounts rather than blocking them.",{"id":28,"title":27,"titles":705,"content":706,"level":615},[],"Signup is the awkward action. The user doesn't have an ID yet, so the usual evaluate flow doesn't quite fit — you'd normally pass user to Rupt and your server would later check that user.id on the evaluation matches. On the first signup there's nothing to match against, and a naive integration ends up letting attackers slip past the challenge by closing the tab and reloading your site.",{"id":708,"title":27,"titles":709,"content":710,"level":615},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Fsignup-protection#signup-protection",[],"Signup is the awkward action. The user doesn't have an ID yet, so the usual evaluate flow doesn't quite fit — you'd normally pass user to Rupt and your server would later check that user.id on the evaluation matches. On the first signup there's nothing to match against, and a naive integration ends up letting attackers slip past the challenge by closing the tab and reloading your site. It builds on the generic Challenge flow and adds the binding that makes signup safe. Along with Login protection, it's one of the two foundations every other guide builds on. Fake account, multi-accounting, and the other signup-time guides point back here because the shape is the same.",{"id":712,"title":713,"titles":714,"content":715,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Fsignup-protection#what-this-protects-against","What this protects against",[27],"Skip this pattern and you'll see these problems. A user triggers a challenge, closes the tab, walks to \u002Flogin. Without server-side state they look like any other new account.The client sends one email to Rupt and a different one to your \u002Fregister. Your server sees verdict: allow and trusts it.Same email, several signup attempts, none challenge-completed, all of them landing in your DB.Someone grabs the signup success URL after the challenge completed and replays it, hoping to mint a second session off the same evaluation. The flow below closes them.",{"id":717,"title":666,"titles":718,"content":719,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Fsignup-protection#the-flow",[27],"Your server should keep a reference to the evaluation and the status of the signup on the User record. Something like signup_evaluation_id and signup_status on your User model do the trick. We'll fill in the details in the next steps.",{"id":721,"title":722,"titles":723,"content":724,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Fsignup-protection#step-1-call-evaluate-on-the-client","Step 1: Call evaluate on the client",[27],"On signup you normally don't have a user ID yet. Pass email (and phone if you collect it). import Rupt from \"@ruptjs\u002Fclient\";\n\nconst rupt = new Rupt({ clientId: \"your_client_id\" });\n\nconst evaluation = await rupt.evaluate.signup({\n  email: form.email,\n  \u002F\u002F phone: form.phone,\n});\n\n\u002F\u002F POST \u002Fsignup to your server with the evaluation ID\nawait fetch(\"YOUR_SIGNUP_ENDPOINT\u002Fsignup\", {\n  method: \"POST\",\n  body: JSON.stringify({\n    ...form,\n    evaluation_id: evaluation?.evaluation_id,\n  }),\n});\nlet response = try await rupt.evaluate(\n  action: \"signup\",\n  email: form.email\n)\n\n\u002F\u002F POST evaluation.evaluationId to your server with the form\nval response = rupt.evaluate(\n  action = \"signup\",\n  email = form.email,\n)\n\n\u002F\u002F POST response.evaluationId to your server with the form",{"id":726,"title":727,"titles":728,"content":729,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Fsignup-protection#step-2-handle-the-verdict-on-your-server","Step 2: Handle the verdict on your server",[27],"Your server fetches the evaluation, runs the integrity check (the action and email match what you expected), and then handles the verdict. \u002F\u002F POST \u002Fsignup\nconst evaluation = await rupt.getEvaluation(evaluation_id);\n\n\u002F\u002F Integrity check — block tampering before anything else\nif (evaluation.action !== \"signup\") return reject(\"Action mismatch\");\nif (evaluation.user?.email !== form.email) return reject(\"Identity mismatch\");\n\nif (evaluation.verdict === \"deny\") {\n  return reject(\"Signup denied\");\n}\n\nif (evaluation.verdict === \"allow\") {\n  const user = await User.create({\n    ...form,\n    signup_status: \"complete\",\n  });\n  return { token: issueToken(user), user };\n}\n\nif (evaluation.verdict === \"challenge\") {\n  const user = await User.create({\n    ...form,\n    signup_status: \"await_challenge_completion\",\n    signup_evaluation_id: evaluation.id,\n  });\n  return { redirect: evaluation.redirect };\n} The await_challenge_completion user exists in your DB but can't authenticate yet. Two fields cover the binding: FieldSet whenReset whensignup_evaluation_idOn signup attemptNever!signup_statusOn signup attempt: complete or await_challenge_completionFlipped to complete after challenge completes",{"id":731,"title":732,"titles":733,"content":734,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Fsignup-protection#step-3-configure-the-challenge-success-url","Step 3: Configure the challenge success URL",[27],"In the Rupt dashboard, on the relevant policy's Challenge Config (Policies -> Edit -> Challenge Config), set Success URL to the page on your site that handles signup completion. For example: https:\u002F\u002Fyourapp.com\u002Fsignup\u002Fcomplete. When the user passes the challenge, Rupt redirects to that URL with the evaluation ID appended: https:\u002F\u002Fyourapp.com\u002Fsignup\u002Fcomplete?evaluation=68f… Configure it once per project. The same URL serves every signup that requires a challenge.",{"id":736,"title":737,"titles":738,"content":739,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Fsignup-protection#step-4-complete-the-signup-server-side","Step 4: Complete the signup server-side",[27],"Your \u002Fsignup\u002Fcomplete page (or route) takes the evaluation ID from the URL and POSTs it to your server. The server is the only thing that should flip the user's status. Consume the evaluation in one shot — consuming is the read and the single-use claim at once, so there's no separate fetch. The first call returns the evaluation; a replay throws 409. \u002F\u002F POST \u002Fsignup\u002Fcomplete\nconst { evaluation_id } = req.body;\n\n\u002F\u002F Cross-check against the user we created at \u002Fsignup\nconst user = await User.findOne({\n  signup_evaluation_id: evaluation_id,\n  signup_status: \"await_challenge_completion\",\n});\nif (!user) return reject(\"No pending signup matches this evaluation\");\n\n\u002F\u002F One shot: consume reads and claims the evaluation atomically. The first call\n\u002F\u002F wins; a replay throws 409.\nlet evaluation;\ntry {\n  evaluation = await rupt.consumeEvaluation(evaluation_id);\n} catch (err) {\n  if (err.status === 409)\n    return reject(\"This signup link was already used. Please log in.\");\n  \u002F\u002F Rupt unreachable: fail open. The status flip below still blocks replays.\n}\n\nif (evaluation && evaluation.challenge?.status !== \"completed\") {\n  return reject(\"Challenge not completed\");\n}\n\nuser.signup_status = \"complete\";\nawait user.save();\nreturn { token: issueToken(user), user }; The lookup is by signup_evaluation_id, not by trusting the ID in the query string. A user URL-tampering with someone else's evaluation= won't find a pending row bound to themselves. And because the flip from await_challenge_completion to complete is itself single-use, consuming is a second gate: even if Rupt is down when you try to consume, a replay still finds no pending row.",{"id":741,"title":742,"titles":743,"content":744,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Fsignup-protection#step-5-refuse-pending-users-at-login","Step 5: Refuse pending users at login",[27],"This is the bypass-prevention check. A user still in await_challenge_completion who skips \u002Fsignup\u002Fcomplete and goes straight to \u002Flogin gets rejected. \u002F** PSEUDO CODE **\u002F\n\u002F\u002F POST \u002Flogin\nconst user = await User.findOne({ email: form.email });\nif (!user) return reject(\"Invalid credentials\");\n\nif (user.signup_status === \"await_challenge_completion\") {\n  const evaluation = await rupt.getEvaluation(user.signup_evaluation_id);\n  return reject(\"Complete your signup challenge first.\", {\n    redirect: evaluation.redirect,\n  });\n}\n\u002F\u002F …password check, evaluate.login integrity check, etc. 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 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 .sKfv_, html code.shiki .sKfv_{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F92672}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 .sh6BQ, html code.shiki .sh6BQ{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#66D9EF}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}",{"id":32,"title":31,"titles":746,"content":747,"level":615},[],"The login evaluation has three verdicts: allow, deny, and challenge. The allow and deny verdicts are simple cases.",{"id":749,"title":31,"titles":750,"content":751,"level":615},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Flogin-protection#login-protection",[],"The login evaluation has three verdicts: allow, deny, and challenge. The allow and deny verdicts are simple cases. The challenge verdict requires a bit more work to ensure it's not bypassed. The one rule is: don't issue a session or token while a challenge is outstanding. If the verdict is challenge, the user gets nothing until your server confirms the challenge completed. Login and signup protection are the foundation of every other guide. Once you have those two down, you can build on them with different policies and checks for any other use case.",{"id":753,"title":713,"titles":754,"content":755,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Flogin-protection#what-this-protects-against",[31],"A user triggers a login challenge, closes the tab, and hits \u002Flogin again. If a challenge never blocks the session, the second attempt just works.The client sends one user to Rupt and authenticates as a different one. Your server sees verdict: allow and trusts it.A stolen-credential login that should have been challenged sails through because the server never confirmed the outcome.An attacker replays the post-challenge success URL — or guesses an evaluation_id whose challenge already completed — to mint a session without passing a challenge of their own.",{"id":757,"title":666,"titles":758,"content":437,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Flogin-protection#the-flow",[31],{"id":760,"title":761,"titles":762,"content":763,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Flogin-protection#step-1-call-evaluate-at-login","Step 1: Call evaluate at login",[31],"Pass the user id and email (and phone if you have it). import Rupt from \"@ruptjs\u002Fclient\";\n\nconst rupt = new Rupt({ clientId: \"your_client_id\" });\n\nconst loginEval = await rupt.evaluate.login({\n  user: user.id,\n  email: form.email,\n});\n\n\u002F\u002F POST \u002Flogin to your server with the credentials and the evaluation ID\nawait fetch(\"\u002Flogin\", {\n  method: \"POST\",\n  body: JSON.stringify({\n    ...credentials,\n    evaluation_id: loginEval?.evaluation_id,\n  }),\n});\nlet response = try await rupt.evaluate(\n  action: \"login\",\n  user: user.id,\n  email: form.email\n)\n\n\u002F\u002F POST evaluation.evaluationId to your server with the credentials\nval response = rupt.evaluate(\n  action = \"login\",\n  user = user.id,\n  email = form.email,\n)\n\n\u002F\u002F POST response.evaluationId to your server with the credentials",{"id":765,"title":727,"titles":766,"content":767,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Flogin-protection#step-2-handle-the-verdict-on-your-server",[31],"Your server checks the password, fetches the evaluation, runs the integrity check (the action and user match what you expected), then branches on the verdict. On a challenge it issues nothing and hands back the redirect. \u002F\u002F POST \u002Flogin\nif (!checkPassword(credentials)) return reject(\"Invalid credentials\");\n\nconst evaluation = await rupt.getEvaluation(evaluation_id);\n\n\u002F\u002F Integrity check — block tampering before anything else\nif (evaluation.action !== \"login\") return reject(\"Action mismatch\");\nif (evaluation.user?.id !== user.id) return reject(\"Identity mismatch\");\n\nif (evaluation.verdict === \"deny\") {\n  return reject(\"Login denied\");\n}\n\nif (evaluation.verdict === \"allow\") {\n  return { session: startSession(user) };\n}\n\nif (evaluation.verdict === \"challenge\") {\n  \u002F\u002F Don't start a session. Send the user to the challenge first.\n  return { redirect: evaluation.redirect };\n}",{"id":769,"title":732,"titles":770,"content":771,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Flogin-protection#step-3-configure-the-challenge-success-url",[31],"In the Rupt dashboard, on the relevant Challenge Config (Policies -> Edit -> Challenge Config), set Success URL to the page that finishes login. For example: https:\u002F\u002Fyourapp.com\u002Flogin\u002Fcomplete. When the user passes, Rupt redirects there with the evaluation ID appended: https:\u002F\u002Fyourapp.com\u002Flogin\u002Fcomplete?evaluation=68f…",{"id":773,"title":774,"titles":775,"content":776,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Flogin-protection#step-4-consume-the-evaluation-and-start-the-session","Step 4: Consume the evaluation and start the session",[31],"Your \u002Flogin\u002Fcomplete route takes the evaluation ID from the URL and consumes it. Consuming is a single-use, atomic claim: Rupt marks the evaluation spent and returns it in one step, so the same success URL can never start a second session. The first call wins; a replay throws 409. Start the session only if the consume succeeds, the challenge completed, and the evaluation is still fresh. \u002F\u002F POST \u002Flogin\u002Fcomplete\nconst { evaluation_id } = req.body;\n\nlet evaluation;\ntry {\n  \u002F\u002F Single-use: the first call wins, a replay throws 409.\n  evaluation = await rupt.consumeEvaluation(evaluation_id);\n} catch (err) {\n  if (err.status === 409) return reject(\"This login link was already used\");\n  \u002F\u002F Network or 5xx — fail open, allow the login to proceed. Worth logging.\n  return { session: startSession(evaluation.user) };\n}\n\nif (evaluation.action !== \"login\") return reject(\"Action mismatch\");\n\n\u002F\u002F Cap time on challenge to 10 mins (as an example).\nif (Date.now() - new Date(evaluation.createdAt).getTime() >= 10 * 60 * 1000) {\n  return reject(\"Login session expired\");\n}\n\nif (evaluation.challenge?.status !== \"completed\") {\n  return reject(\"Challenge not completed\");\n}\n\nreturn { session: startSession(evaluation.user) }; The session starts here, never at \u002Flogin when a challenge was issued. Consuming rather than just reading is what makes it safe: an attacker who captures the success URL — or guesses an evaluation_id — finds it already spent, never completed, or expired. Pair this with Signup protection and you've covered both ends of authentication. Every other guide builds on one of the two. 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 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 .sKfv_, html code.shiki .sKfv_{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F92672}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 .sh6BQ, html code.shiki .sh6BQ{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#66D9EF}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}",{"id":36,"title":35,"titles":778,"content":779,"level":615},[],"Access protection is how Rupt defends against account sharing: one subscription quietly used by several people.",{"id":781,"title":35,"titles":782,"content":783,"level":615},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Faccess-protection#access-protection",[],"Access protection is how Rupt defends against account sharing: one subscription quietly used by several people. Account sharing is a special case, and it's handled differently from login and signup.It leans on devices, not fingerprints, and the goal is growth, not blocking. A shared account is a paying customer with extra people attached. The win is converting those extra people into their own accounts, not locking anyone out.False positives are worse than false negatives. Users accept tight login security — you're protecting their credentials. But challenging someone for sharing when they aren't is infuriating. That's why Rupt errs on the side of caution. Like the other fundamentals, this builds on the challenge flow, but it's fully self-managed. You call evaluate.access and Rupt handles detection, the challenge, owner verification, and device capping client-side. There's no server step and no evaluation to consume.",{"id":785,"title":786,"titles":787,"content":788,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Faccess-protection#step-1-evaluate-on-access","Step 1: Evaluate on access",[35],"Call evaluate.access once on every page view (do it only for paid content and paying users -- you don't care if free users are sharing). Pass user, and include email and\u002For phone so the verification challenge can reach the owner. Set up the on_logout callback at the same time. The device-limit challenge can log a device out, and the callback is how your app clears its own session when that happens. import Rupt from \"@ruptjs\u002Fclient\";\n\nconst rupt = new Rupt({\n  clientId: \"your_client_id\",\n  on_logout: () => {\n    \u002F\u002F Clear your session and sign the user out locally.\n  },\n});\n\n\u002F\u002F Call this on every protected page.\nawait rupt.evaluate.access({\n  user: user.id,\n  email: user.email,\n  phone: user.phone,\n});\n\u002F\u002F Set on_logout when you create the client, then call evaluate on every screen.\ntry await rupt.evaluate(\n  action: \"access\",\n  user: user.id,\n  email: user.email,\n  phone: user.phone\n)\n\u002F\u002F Set on_logout when you create the client, then call evaluate on every screen.\nrupt.evaluate(\n  action = \"access\",\n  user = user.id,\n  email = user.email,\n  phone = user.phone,\n) When Rupt detects sharing, the SDK redirects to the challenge, runs verification or the device cap, and brings the user back, all client-side. There's nothing to fetch or confirm on your server.",{"id":790,"title":791,"titles":792,"content":793,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Faccess-protection#step-2-create-the-policy-and-its-urls","Step 2: Create the policy and its URLs",[35],"This is the last piece, and it's what turns detection on. Until a policy exists, evaluate.access just gathers signals and never challenges anyone. In the dashboard, create a policy on the access event with action challenge. Gate it on the sharing checks: concurrent_sessions: the account is live in two places at once.impossible_travel: back-to-back activity from locations too far apart to bridge.device_count, or the per-type computer_device_count \u002F tablet_device_count \u002F mobile_device_count: more devices than one person uses. There are two kinds of account-sharing challenge: Verification challenge — for concurrent_sessions and impossible_travel. Rupt asks the suspected owner to verify with an email or SMS code.Device-limit challenge — for the device counts. Rupt asks the user to log devices out until the account is back under your limit. A common setup is two policies: one that verifies on concurrency or impossible travel, and one that caps devices on count. Keep the thresholds generous — given the false-positive cost above, start loose and tighten only if you need to. On the policy's challenge config, set two URLs: Success URL — where a blocked user goes to create their own account (your signup page). Someone who hits a sharing challenge is high-intent, so this converts the extra user instead of turning them away. For high-value B2B, pointing them to book a meeting works just as well.Logout URL — where the user lands when they're logged out of the shared account. This is the redirect that pairs with your on_logout callback; log your existing user out here first, so they can create their own account cleanly.",{"id":795,"title":423,"titles":796,"content":797,"level":438},"\u002Fdocs\u002Fv3\u002Ffundamentals\u002Faccess-protection#related",[35],"Account sharing: the risk and the checks behind it.Account sharing prevention: the same pattern in the guides section.Concurrency and Devices: what the policy matches on. 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 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 .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 .sh6BQ, html code.shiki .sh6BQ{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#66D9EF}",{"id":40,"title":39,"titles":799,"content":800,"level":615},[],"Guides are a collection of tutorials that address specific use cases and best practices for using Rupt.",{"id":802,"title":39,"titles":803,"content":804,"level":615},"\u002Fdocs\u002Fv3\u002Fguides#guides",[],"Guides are a collection of tutorials that address specific use cases and best practices for using Rupt. These guides are intentionally specific, and they overlap on purpose. Many fraud problems share the same checks and the same challenge flow, so you'll see the same building blocks recur. Each guide is here to get you up and running on one use case quickly, with the exact policy and code for it. Pick the one that matches what you're building.",{"id":806,"title":807,"titles":808,"content":809,"level":438},"\u002Fdocs\u002Fv3\u002Fguides#prerequisite","Prerequisite",[39],"You have a working Rupt integration. If not, see the Quick start guide.",{"id":811,"title":39,"titles":812,"content":437,"level":438},"\u002Fdocs\u002Fv3\u002Fguides#guides-1",[39],{"id":45,"title":44,"titles":814,"content":815,"level":615},[],"Account sharing is one subscription used by several people, and it's the use-case framing of the Access protection fundamental.",{"id":817,"title":44,"titles":818,"content":815,"level":615},"\u002Fdocs\u002Fv3\u002Fguides\u002Faccount-sharing-prevention#account-sharing-prevention",[],{"id":820,"title":821,"titles":822,"content":823,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Faccount-sharing-prevention#step-1-set-up-access-protection","Step 1: Set up access protection",[44],"Set up Access protection first. It's self-managed: you call evaluate.access on the client and Rupt handles detection, the challenge, owner verification, and device capping client-side. There's no server step. With that in place, the policies below are what actually catch sharing.",{"id":825,"title":826,"titles":827,"content":828,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Faccount-sharing-prevention#step-2-add-the-policies","Step 2: Add the policies",[44],"A policy has a trigger (the event it runs on) and a verdict. Add these in your policies dashboard: PolicyTriggerConditionsVerdictVerify the owneraccessconcurrent_sessions, or impossible_travelChallengeCap devicesaccessdevice_count over your limit (or per-type computer_device_count \u002F tablet_device_count \u002F mobile_device_count)Challenge Account sharing leans on devices, not fingerprints, and the goal is to convert the extra user, so point the challenge Success URL at signup. The device-cap challenge logs the extra devices out. Tune conservatively: a false sharing accusation is costly, so start loose and tighten only if you need to.",{"id":830,"title":423,"titles":831,"content":832,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Faccount-sharing-prevention#related",[44],"Access protectionConcurrencyDevices",{"id":49,"title":48,"titles":834,"content":835,"level":615},[],"Scraping is automated extraction of your content: bots and crawlers pulling pages far faster than a person would.",{"id":837,"title":48,"titles":838,"content":835,"level":615},"\u002Fdocs\u002Fv3\u002Fguides\u002Fweb-scraping-prevention#web-scraping-prevention",[],{"id":840,"title":841,"titles":842,"content":843,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fweb-scraping-prevention#step-1-wire-a-working-integration","Step 1: Wire a working integration",[48],"Wire Rupt and run an access evaluation first (see the Access protection). The policies below trigger on access, so they only fire once that evaluation is in place.",{"id":845,"title":826,"titles":846,"content":847,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fweb-scraping-prevention#step-2-add-the-policies",[48],"A policy has a trigger (the event it runs on) and a verdict. Add these in your policies dashboard: PolicyTriggerConditionsVerdictBlock datacenter scrapersaccessip_is_hosting, or ip_is_proxyDenyThrottle high velocityaccesshigh event_countChallenge Datacenter IP alone catches a lot, since real users almost never browse from hosting infrastructure. Velocity separates a heavy reader from an extractor pulling pages at machine speed.",{"id":849,"title":423,"titles":850,"content":851,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fweb-scraping-prevention#related",[48],"ScrapingAnonymizing networkVelocity",{"id":53,"title":52,"titles":853,"content":854,"level":615},[],"A ban is only as good as your ability to keep the person out when they come back with a new email, a new IP, or a fresh account.",{"id":856,"title":52,"titles":857,"content":854,"level":615},"\u002Fdocs\u002Fv3\u002Fguides\u002Fban-enforcement#ban-enforcement",[],{"id":859,"title":860,"titles":861,"content":862,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fban-enforcement#step-1-set-up-login-and-signup-protection","Step 1: Set up login and signup protection",[52],"Before anything else here, set up Signup protection and Login protection. They're the basis this builds on, and without them the policies below can be bypassed. With those in place, the rest of this guide covers the policies that enforce a ban and catch the evasion attempts.",{"id":864,"title":826,"titles":865,"content":866,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fban-enforcement#step-2-add-the-policies",[52],"To ban someone, add their identifier to a list in your policies dashboard, by hand or with the add_to_list action. Lists don't expire on their own, so the ban sticks until you remove it, and Rupt checks the list on every evaluation for you. Then add policies that deny anyone on a block list, run at high priority so a banned identifier is stopped before any other rule: PolicyTriggerConditionsVerdictBlock banned userslogin, signup, accessin_list your user block listDenyBlock banned deviceslogin, signup, accessin_list your fingerprint block listDenyBlock banned IPs (optional)login, signup, accessin_list your IP block listDeny When you ban a user, add their device fingerprint to the fingerprint list too. Otherwise they just open a new account on the same device and start over; banning the fingerprint closes that door. Deny outright, or use the suspend action if you'd rather mark the account suspended. The IP list is optional and risky: IPs are shared (offices, schools, carrier NAT), so banning one can lock out innocent people behind it. Use it only for an IP you're sure belongs to a single bad actor.",{"id":868,"title":423,"titles":869,"content":870,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fban-enforcement#related",[52],"ListsVerdictsMulti-accounting prevention",{"id":57,"title":56,"titles":872,"content":873,"level":615},[],"Most chargebacks trace back to fraud you let in earlier, so the cheapest way to cut disputes is to stop the fraud before and at checkout.",{"id":875,"title":56,"titles":876,"content":873,"level":615},"\u002Fdocs\u002Fv3\u002Fguides\u002Fchargeback-dispute#chargeback-dispute",[],{"id":878,"title":879,"titles":880,"content":881,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fchargeback-dispute#step-1-block-fake-accounts-first","Step 1: Block fake accounts first",[56],"Fraudulent purchases come from fraudulent accounts, so start by keeping those out. Set up Fake account detection, and keep your block lists ready. When you see a flood of accounts created from one IP (hundreds or thousands), add that IP to a block list and deny it on the spot. Lists are how you react fast in the middle of an attack, so set them up before you need them.",{"id":883,"title":884,"titles":885,"content":886,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fchargeback-dispute#step-2-gate-the-checkout-coming-soon","Step 2: Gate the checkout (coming soon)",[56],"A dedicated checkout action is coming soon. When it ships, you'll evaluate the purchase itself and challenge when it looks risky. A real cardholder clears the challenge; a card tester can't. Challenge whenCheckStatusThe buyer looks automatedbot riskComing soon (read the score now, can't gate yet)Purchase rate spikes well above the buyer's normalper-buyer velocityComing soon (event_count gives the raw rate today)The purchase is from a new, untrusted deviceis_new_fingerprintAvailableThe email isn't verifiedis_email_verifiedAvailable The fingerprint and email checks are live today, but the checkout trigger and the bot\u002Fvelocity gating land with the checkout action, so this whole step arrives together. When a chargeback does come through, the evaluation from that purchase is also your evidence: it holds the device, location, and risk at the moment of sale.",{"id":888,"title":423,"titles":889,"content":890,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fchargeback-dispute#related",[56],"Fake account detection and Ban enforcement: keep fraudulent accounts out.Card testing prevention: the same checkout signals, framed for stolen-card runs.",{"id":61,"title":60,"titles":892,"content":893,"level":615},[],"Multi-accounting is one person running many accounts: trial farming, incentive abuse, ban evasion, or padding out fake leads.",{"id":895,"title":60,"titles":896,"content":893,"level":615},"\u002Fdocs\u002Fv3\u002Fguides\u002Fmulti-accounting-prevention#multi-accounting-prevention",[],{"id":898,"title":860,"titles":899,"content":900,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fmulti-accounting-prevention#step-1-set-up-login-and-signup-protection",[60],"Before anything else here, set up Signup protection and Login protection. They're the basis this builds on, and without them the policies below can be bypassed. With those in place, the rest of this guide covers the policies that stop one person from spinning up account after account.",{"id":902,"title":826,"titles":903,"content":904,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fmulti-accounting-prevention#step-2-add-the-policies",[60],"A policy has a trigger (the event it runs on) and a verdict. Add these in your policies dashboard: PolicyTriggerConditionsVerdictLimit accounts per devicesignupfingerprint_user_count is over your limit (2 is a fair start)ChallengeLimit accounts per phonesignupmore than 2 accounts on one verified phone (coming soon)Deny The two policies stack. Allow a couple of accounts per device, then challenge anyone past that, with the challenge set to require a phone number so the extra account has to verify a real phone before it continues. Then cap how many accounts a single verified phone can back, and deny beyond it. A real phone is far harder to mass-produce than an email, so this is what actually slows multi-accounting down. Keep the device limit generous: a shared computer, a family, or an office is normal, so 2 (or higher) avoids punishing real users. Per-phone account limits are coming soon; until then the device limit plus required phone verification does most of the work.",{"id":906,"title":423,"titles":907,"content":908,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fmulti-accounting-prevention#related",[60],"Linked accounts: the risk and the fingerprint_user_count check behind it.Coupon abuse prevention and Ban enforcement: two common multi-accounting payoffs.Fingerprints: how Rupt sees the shared device.",{"id":65,"title":64,"titles":910,"content":911,"level":615},[],"Account takeover is someone signing in with credentials that aren't theirs: bought from a breach, phished, or stuffed from a leak.",{"id":913,"title":64,"titles":914,"content":911,"level":615},"\u002Fdocs\u002Fv3\u002Fguides\u002Faccount-takeover-prevention#account-takeover-prevention",[],{"id":916,"title":860,"titles":917,"content":918,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Faccount-takeover-prevention#step-1-set-up-login-and-signup-protection",[64],"Before anything else here, set up Signup protection and especially Login protection. This is a login threat, so login protection is the basis it builds on, and without it the policy below can be bypassed. With those in place, the rest of this guide covers the policy that catches stolen sign-ins.",{"id":920,"title":826,"titles":921,"content":922,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Faccount-takeover-prevention#step-2-add-the-policies",[64],"A policy has a trigger (the event it runs on) and a verdict. Add these in your policies dashboard: PolicyTriggerConditionsVerdictChallenge unfamiliar loginsloginimpossible_travel, or is_new_fingerprint and is_new_ip, or ip_is_vpnChallenge This is a simple and solid policy to verify the user identity via 2FA if there's anything unfamiliar about the login and should take care of the vast majority of the cases of account takeover.",{"id":924,"title":423,"titles":925,"content":926,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Faccount-takeover-prevention#related",[64],"Login protectionAccount takeover",{"id":69,"title":68,"titles":928,"content":929,"level":615},[],"Add friction to high-risk checkouts before the charge goes through.",{"id":931,"title":68,"titles":932,"content":929,"level":615},"\u002Fdocs\u002Fv3\u002Fguides\u002Frisky-transaction-prevention#risky-transaction-prevention",[],{"id":934,"title":935,"titles":936,"content":937,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Frisky-transaction-prevention#step-1-protect-the-checkout","Step 1: Protect the checkout",[68],"Before anything else, make sure you have a working integration by following the Quick start. A dedicated transaction action is coming soon. Until then, protect the checkout surface as an access evaluation: evaluate when the user reaches checkout.",{"id":939,"title":826,"titles":940,"content":941,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Frisky-transaction-prevention#step-2-add-the-policies",[68],"A policy has a trigger (the event it runs on) and a verdict. Add these in your policies dashboard: PolicyTriggerConditionsVerdictChallenge risky checkoutsaccessis_new_fingerprint, ip_is_vpn, impossible_travel, or is_new_ipChallenge These are the same signals as an account takeover, applied to a purchase instead of a login: a device you've never seen, a VPN, a location the account couldn't have reached, a network it has never used. When they line up, challenge the checkout so a real buyer can clear it before the charge clears.",{"id":943,"title":423,"titles":944,"content":945,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Frisky-transaction-prevention#related",[68],"Account takeover prevention: the same signals on the login action.Anonymizing network: the VPN, proxy, and Tor checks behind this policy.",{"id":73,"title":72,"titles":947,"content":948,"level":615},[],"A fake account is one that isn't a real person who intends to use your product, usually a bot creating accounts in bulk.",{"id":950,"title":72,"titles":951,"content":948,"level":615},"\u002Fdocs\u002Fv3\u002Fguides\u002Ffake-account-detection#fake-account-detection",[],{"id":953,"title":860,"titles":954,"content":955,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Ffake-account-detection#step-1-set-up-login-and-signup-protection",[72],"Before anything else here, set up Signup protection and Login protection. They're the basis this builds on, and without them the policies below can be bypassed. With those in place, the rest of this guide covers the policies that actually stop fake accounts.",{"id":957,"title":826,"titles":958,"content":959,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Ffake-account-detection#step-2-add-the-policies",[72],"Add these in your policies dashboard: PolicyTriggerConditionsVerdictBlock fake contactssignupemail_is_invalid, email_is_disposable, or a VoIP \u002F bought phone number (coming soon)DenyVerify new accountssignupis_email_verified is falseChallengeChallenge bot sign-upssignupbot_risk_severity is high+ (coming soon)Challenge Give the block policy higher priority than the challenge, so a clear-cut fake is denied outright instead of just challenged. Everything else falls through to the verify challenge: a real person clears the email or SMS code, a bot or throwaway can't. From here you can stack more onboarding protections on top, like multi-accounting prevention.",{"id":961,"title":423,"titles":962,"content":963,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Ffake-account-detection#related",[72],"Multi-accounting preventionFake accountEmail quality",{"id":77,"title":76,"titles":965,"content":966,"level":615},[],"A bot is automated, non-human traffic behind signup farms, click fraud, and ad-budget siphoning.",{"id":968,"title":76,"titles":969,"content":966,"level":615},"\u002Fdocs\u002Fv3\u002Fguides\u002Fbot-detection#bot-detection",[],{"id":971,"title":860,"titles":972,"content":973,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fbot-detection#step-1-set-up-login-and-signup-protection",[76],"Before anything else here, set up Signup protection and Login protection. They're the basis this builds on, and without them the policies below can be bypassed. With those in place, the rest of this guide covers the policies that keep automated traffic out.",{"id":975,"title":826,"titles":976,"content":977,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fbot-detection#step-2-add-the-policies",[76],"A policy has a trigger (the event it runs on) and a verdict. Add these in your policies dashboard: PolicyTriggerConditionsVerdictBlock datacenter trafficlogin, signup, accessip_is_hosting, or ip_is_proxyDenyBlock fake deviceslogin, signup, accessis_simulator or is_emulator (native apps)DenyChallenge high bot risklogin, signup, accessbot risk is high (coming soon)Challenge The bot-risk challenge lands with bot risk summaries (coming soon): when a request's bot risk is high, the policy issues a 2FA challenge that a real person clears and a bot can't. Until then, read the bot score off the evaluation in your own logic; the datacenter and device policies above work today.",{"id":979,"title":423,"titles":980,"content":981,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fbot-detection#related",[76],"BotsWeb scraping preventionAnonymizing network",{"id":81,"title":80,"titles":983,"content":984,"level":615},[],"Card testing is a bot running stolen card numbers through your checkout in rapid small charges to find which ones approve.",{"id":986,"title":80,"titles":987,"content":984,"level":615},"\u002Fdocs\u002Fv3\u002Fguides\u002Fcard-testing-prevention#card-testing-prevention",[],{"id":989,"title":990,"titles":991,"content":992,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fcard-testing-prevention#step-1-evaluate-the-payment-action","Step 1: Evaluate the payment action",[80],"Card testing happens at the charge, so protect the payment action. payment is a custom action: you evaluate it like any other, you just name it payment. Evaluate it when the user submits a charge and confirm the verdict on your server before you run the payment. If you haven't wired Rupt yet, start with the Quick start.",{"id":994,"title":826,"titles":995,"content":996,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fcard-testing-prevention#step-2-add-the-policies",[80],"A policy has a trigger (the event it runs on) and a verdict. Add these in your policies dashboard: PolicyTriggerConditionsVerdictVerify a risky checkoutpaymentis_new_ip, is_new_fingerprint, or event_count of payment over the last 10 min above 5ChallengeBlock card testingpaymentis_simulator, is_emulator, a headless browser, the card on your card block list, or event_count of payment over the last 10 min above 10Deny Protect the real cardholder while stopping the tester. An unfamiliar IP or device might just be your customer on a new phone, so verify them with a challenge before the charge goes through. A simulator, emulator, headless browser, or a card you've already flagged has no business at checkout, so deny it. Card testing also gives itself away by pace: a handful of attempts in ten minutes earns a challenge, a flood gets blocked. When you catch a tester, add the card to your card block list (a list of type card) so the next attempt with it is denied on sight.",{"id":998,"title":423,"titles":999,"content":1000,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fcard-testing-prevention#related",[80],"Velocity: the event_count spike behind the rate checks.Anonymizing network: the IP and device checks behind this policy.Lists: the card block list.",{"id":85,"title":84,"titles":1002,"content":1003,"level":615},[],"Incentive abuse is claiming a reward more times than you allow: repeat free trials, reusing a one-time coupon, a fresh account for every promo, free credits, or completing a paid survey again and again for the payout.",{"id":1005,"title":84,"titles":1006,"content":1003,"level":615},"\u002Fdocs\u002Fv3\u002Fguides\u002Fincentive-abuse-prevention#incentive-abuse-prevention",[],{"id":1008,"title":860,"titles":1009,"content":1010,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fincentive-abuse-prevention#step-1-set-up-login-and-signup-protection",[84],"Before anything else here, set up Signup protection and Login protection. They're the basis this builds on, and without them the policies below can be bypassed. With those in place, the rest of this guide covers the policies that cap how often one person can claim.",{"id":1012,"title":826,"titles":1013,"content":1014,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fincentive-abuse-prevention#step-2-add-the-policies",[84],"A policy has a trigger (the event it runs on) and a verdict. Add these in your policies dashboard: PolicyTriggerConditionsVerdictLimit claims per devicesignupfingerprint_user_count over your limitChallengeBlock repeat offenderssignup, accessin_list your block listDeny This is multi-accounting prevention pointed at a reward: one person can't keep making accounts to claim again, because the shared device gives them away. When someone does abuse an offer, add their identifier to a block list and deny it. That's the same machinery as ban enforcement, and adding their fingerprint stops them coming back for the next promo.",{"id":1016,"title":423,"titles":1017,"content":1018,"level":438},"\u002Fdocs\u002Fv3\u002Fguides\u002Fincentive-abuse-prevention#related",[84],"Multi-accounting prevention: the core pattern behind this.Ban enforcement: block repeat offenders for good.Linked accounts: the fingerprint_user_count check.",{"id":94,"title":93,"titles":1020,"content":1021,"level":615},[],"[object Object]",{"id":1023,"title":93,"titles":1024,"content":1025,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fevaluations#evaluations",[],"An evaluation is Rupt's assessment of a single action by a single user from a single device at a single moment. It is the entry point to everything Rupt does: every fingerprinting probe, every risk score, every challenge starts with an evaluation. When you call evaluate() from the SDK, Rupt collects signals from the user's environment, derives checks from those signals plus the user's history, computes risks from the checks, and matches the result against your active policies to produce a verdict. If the verdict is challenge, Rupt also creates a challenge record.",{"id":1027,"title":1028,"titles":1029,"content":1030,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fevaluations#what-an-evaluation-contains","What an evaluation contains",[93],"The action the user is trying to take (login, signup, access, or any custom action).The user: a unique identifier when you have one, optional email and phone, and any metadata you want linked to the user.The device and a browser fingerprint the user is using.The geolocation (IP, latitude\u002Flongitude, city, country, and anonymity flags for VPN, proxy, Tor, and hosting).The checks derived from the signals and history.The risks that were classified, each with a severity of low, medium, high, or maximum.The policy that matched and the verdict it produced.The challenge details, if one was issued.",{"id":1032,"title":1033,"titles":1034,"content":1035,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fevaluations#where-this-fits","Where this fits",[93],"This page explains what an evaluation is. The mechanics of wiring evaluate() into a login, signup, access, or custom action flow (and confirming the result server-side) live in the Quick start.",{"id":98,"title":97,"titles":1037,"content":1021,"level":615},[],{"id":1039,"title":97,"titles":1040,"content":1041,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Factions#actions",[],"An action is the user-initiated event you ask Rupt to judge. Every evaluation is bound to exactly one action, and Rupt tunes its checks and risks to the threats most relevant to that flow.",{"id":1043,"title":1044,"titles":1045,"content":1046,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Factions#the-reserved-actions","The reserved actions",[97],"V3 supports the following reserved actions:",{"id":1048,"title":1049,"titles":1050,"content":1051,"level":1052},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Factions#login","login",[97,1044],"A user authenticates to an existing account. Rupt prioritizes the following risks for this action: account takeoveraccount sharingbot activityscrapinglinked accounts Rupt infers those risks from the following checks: new fingerprintnew IPimpossible travelanonymizing networkconcurrent sessionsdevice countvelocityshared fingerprint",3,{"id":1054,"title":1055,"titles":1056,"content":1057,"level":1052},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Factions#signup","signup",[97,1044],"A new user is created. Rupt prioritizes the following risks for this action: fake accountbot activitylinked accounts Rupt infers those risks from the following checks: disposable emailinvalid emailunverified emailwebmail emailshared fingerprint",{"id":1059,"title":1060,"titles":1061,"content":1062,"level":1052},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Factions#access","access",[97,1044],"A page view or app open. Rupt prioritizes the following risks for this action: account sharingbot activityscrapinglinked accounts Rupt infers those risks from the following checks: concurrent sessionsimpossible traveldevice countvelocityanonymizing networkshared fingerprint",{"id":1064,"title":1065,"titles":1066,"content":1067,"level":1052},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Factions#custom-actions","custom actions",[97,1044],"A custom action is one that you define. Pass any action string to evaluate(). Use this to protect any user-facing flow that doesn't fit into the reserved actions. Each risk contains checks that are derived from signals. In the dashboard, you can create a custom risk, define the checks and signals that contribute to it as well as the weight of each check based on the outcome as well as the level of severity based on the score.",{"id":1069,"title":1070,"titles":1071,"content":1072,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Factions#choosing-the-right-action","Choosing the right action",[97],"Pick the action that matches the user-facing flow you are protecting. The underlying signals and checks are shared across all actions; the difference is which combinations Rupt weights most heavily and which risks get scored. V3 grew out of the v2 account-sharing focus (access) but now covers more of the fraud surface: ATO via login, fake accounts via signup, and sharing via access. There will be more actions in the future.",{"id":102,"title":101,"titles":1074,"content":1021,"level":615},[],{"id":1076,"title":101,"titles":1077,"content":1078,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fsignals#signals",[],"Signals are the raw measurements Rupt collects from a user's environment when an evaluation runs. They aren't decisions on their own. They're the inputs that feed fingerprinting and, more broadly, our ability to spot risk. Here are some of the kinds of signals we look at. This is by no means the full list, just a sense of what goes in and how we use it.",{"id":1080,"title":1081,"titles":1082,"content":1083,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fsignals#network","Network",[101],"The IP address the connection is coming from.Rough location (city, region, country) derived from that IP.Whether the IP looks like a VPN, proxy, Tor exit, or hosting provider. That's a useful tell for things like scraping and account takeover.",{"id":1085,"title":1086,"titles":1087,"content":1088,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fsignals#device","Device",[101],"Type: phone, tablet, or computer.Hardware details like CPU, memory, and touch support.Screen: resolution and window size.The operating system and platform.",{"id":1090,"title":1091,"titles":1092,"content":1093,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fsignals#browser","Browser",[101],"User agent and the related client hints.Language, timezone, and locale.Fonts and plugins that are installed.Smaller permission and configuration details. On their own they're nothing, but together they add up to a fairly specific setup.",{"id":1095,"title":1096,"titles":1097,"content":1098,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fsignals#behavior","Behavior",[101],"Whether interactions look real or synthetic.Automation indicators: signs that a script or testing framework is driving the page rather than a person.",{"id":1100,"title":1101,"titles":1102,"content":1103,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fsignals#how-the-browser-renders-things","How the browser renders things",[101],"Browsers draw and compute certain things in tiny, consistent ways. The same device usually produces the same result every time. When it doesn't, that's often a sign something is being faked or masked.",{"id":1105,"title":1106,"titles":1107,"content":1108,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fsignals#native-apps-ios-and-android","Native apps (iOS and Android)",[101],"When you ship our mobile SDK, we can also tell whether a device is jailbroken or rooted, running in a simulator or emulator, or has a debugger attached.",{"id":1110,"title":1111,"titles":1112,"content":1113,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fsignals#how-it-comes-together","How it comes together",[101],"No single signal tells you much by itself. IPs change, browsers update, people travel. The value is in the mix: we weigh all of it together, and the combination is what actually points to whether something's risky.",{"id":106,"title":105,"titles":1115,"content":1021,"level":615},[],{"id":1117,"title":105,"titles":1118,"content":1119,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#checks",[],"Checks are the boolean and numeric facts Rupt derives from signals plus a user's history. They turn raw measurements into the language Rupt reasons about: is this fingerprint new for this user, is this IP a VPN, has this user moved impossibly far since we last saw them. A check is deliberately narrow. Each one captures a single fact at a single moment, and on its own each is weak. A new IP isn't suspicious by itself, and neither is a webmail address. The power comes from how they combine into risks and how your policies match conditions across them. Here's most of what we check. We keep this in sync with the app as best we can, but it isn't a guarantee. The dashboard policy editor always has the live, complete list.",{"id":1121,"title":1122,"titles":1123,"content":1124,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#identity-newness","Identity newness",[105],"How established the user, IP, or fingerprint is for this account. is_new_useris_new_ip: see New IP.is_new_fingerprintuser_age_daysfingerprint_user_count: how many users share this fingerprint.",{"id":1126,"title":1127,"titles":1128,"content":1129,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#network-reputation","Network reputation",[105],"What kind of network the connection is coming from. See Anonymizing network. ip_is_vpnip_is_proxyip_is_torip_is_hostingip_country",{"id":1131,"title":1132,"titles":1133,"content":1134,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#velocity-and-geography","Velocity and geography",[105],"How the user moves through space and time. impossible_travel: see Impossible travel.concurrent_sessions: see Concurrency.has_high_velocity: an unusual rate of actions in a short window. See Velocity.",{"id":1136,"title":1137,"titles":1138,"content":1139,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#device-counts","Device counts",[105],"How many devices a user has piled up. Some of the strongest account-sharing signals there are. See Devices. device_countcomputer_device_counttablet_device_countmobile_device_count",{"id":1141,"title":181,"titles":1142,"content":1143,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#email-quality",[105],"What kind of email address was provided. See Email quality. email_is_disposableemail_is_webmailemail_is_invalidemail_is_accept_all",{"id":1145,"title":1146,"titles":1147,"content":1148,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#account-state","Account state",[105],"Verification and status flags on the user. is_email_verifiedis_phone_verifiedis_suspendedorigin_is_new: first time this request origin has been seen for your project in the last 90 days.",{"id":1150,"title":1151,"titles":1152,"content":1153,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#device-integrity","Device integrity",[105],"Only populated when you ship the iOS or Android SDK. Web flows leave these unset, and each platform reports only its own flags. jailbroken_iosrooted_androidis_simulatoris_emulatordebugger_attachedui_testing",{"id":1155,"title":1156,"titles":1157,"content":1158,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#fingerprint-observations","Fingerprint observations",[105],"A separate family of checks derived from the deeper signals we collect during fingerprinting. Unlike the checks above, these aren't conditions you match in policies directly. They roll up into the observation-only risks (bots, tampering, anti-fingerprinting, incognito, and replay attacks) so you can watch them without them forcing a verdict.",{"id":1160,"title":1161,"titles":1162,"content":1163,"level":1052},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#bot","Bot",[105,1156],"bot_framework_globalsnavigator_webdriver_trueevent_istrusted_syntheticwindow_process_presentmediadevices_absent_modern_uadevtools_open_during_flowwidevine_missing_on_chromewebgl_swiftshader_rendereruach_missing_on_chromespeech_voices_empty_on_desktop_chromescreen_frame_all_zero_desktopfonts_empty_on_desktop",{"id":1165,"title":1166,"titles":1167,"content":1168,"level":1052},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#tampering","Tampering",[105,1156],"prototype_integrity_failplugin_prototype_mismatchengine_probe_mismatchmath_random_patcheduach_vs_ua_mismatchnotifications_permission_spoofdeviceorientation_permission_non_ios",{"id":1170,"title":1171,"titles":1172,"content":1173,"level":1052},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#anti-fingerprinting","Anti-fingerprinting",[105,1156],"tor_browser_signaturesafari_itp_lockdownfirefox_etp_timerbrave_farbling_detectedfirefox_rfp_detected",{"id":1175,"title":1176,"titles":1177,"content":1178,"level":1052},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#incognito","Incognito",[105,1156],"languages_dedup_incognitostorage_quota_incognitopermissions_stuck_promptindexeddb_failuresnotifications_private_spoof",{"id":1180,"title":1181,"titles":1182,"content":1183,"level":1052},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#replay-attack","Replay attack",[105,1156],"consumed_noncenonce_session_mismatchunknown_noncemissing_noncemissing_cookiesession_expired",{"id":1185,"title":1186,"titles":1187,"content":1188,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchecks#how-checks-are-used","How checks are used",[105],"Checks feed two systems: Risks: Rupt aggregates the relevant checks into a weighted score per category: account takeover, fake account, account sharing, scraping, and linked accounts.Policies: your rules can match conditions over checks directly. A policy can say \"if impossible_travel and ip_is_vpn, challenge\" without ever going through a risk score. The complete, current list of checks and their conditions lives in the policy editor in the dashboard.",{"id":110,"title":109,"titles":1190,"content":1191,"level":615},[],"A risk is Rupt's read on what an evaluation looks like, each scored on its own and graded by severity. Risks summarize; they don't dictate the verdict. Your policies decide what to do.",{"id":1193,"title":109,"titles":1194,"content":1195,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Frisks#risks",[],"A risk is Rupt's read on what an evaluation looks like (this smells like account takeover, this looks like a fake account), each scored on its own and graded by severity. A risk never dictates the verdict on its own. It's a summary, and your policies decide what to do with it.",{"id":1197,"title":1198,"titles":1199,"content":1200,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Frisks#how-a-risk-is-scored","How a risk is scored",[109],"Risks are built from the ground up: Signals are the raw measurements Rupt collects from the user's environment.Checks turn those signals into specific facts: is this IP a VPN, has this user moved impossibly far since last seen.A risk takes the checks that predict it, weights each by how much it counts, and adds them up. That weighted total is the score, and each risk maps its score to one of four severities: low, medium, high, or maximum. The cutoffs aren't shared across risks. Each risk sets its own, because the same check can carry very different weight depending on what you're detecting: a score one risk treats as high might still be medium for another. Severity also depends on how the checks combine, not just how many fire: for account sharing, concurrent sessions and impossible travel together rank far higher than either alone, while a modest device count on its own stays low. The severities surface in the risks array on the evaluation. Read it in your own logic, or write a policy over the checks behind a risk. Matching a policy directly on a risk severity is coming soon.",{"id":1202,"title":1203,"titles":1204,"content":1205,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Frisks#standard-risks","Standard risks",[109],"Rupt ships with a standard set of risks out of the box, and the list grows as the fraud landscape shifts. You don't configure anything to get them. Most are scored for your policies to act on directly; a few Rupt only records for visibility.",{"id":1207,"title":1208,"titles":1209,"content":1210,"level":1052},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Frisks#acted-on-by-policies","Acted on by policies",[109,1203],"Each of these is scored so your policies can match it directly, weighting every check by how strongly it predicts the risk. Account takeover (ato): someone other than the owner is signing in. Leans on a new fingerprint, a new IP, impossible travel, and anonymizing networks.Fake account (fake_account): the signup probably isn't a real person. Driven by email quality: disposable, invalid, unverified, or webmail.Account sharing (account_sharing): one account, several people. Shows up as concurrent sessions, impossible travel, and a pile of devices on one account.Scraping (scraping): automated extraction rather than a human. Flagged by anonymizing networks and high velocity.Linked accounts (linked_accounts): separate accounts sharing the same fingerprint. Catches multi-accounting and ban evasion.",{"id":1212,"title":1213,"titles":1214,"content":1215,"level":1052},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Frisks#recorded-for-visibility","Recorded for visibility",[109,1203],"Rupt scores these on every evaluation but doesn't act on them by default. They surface in the dashboard so you can keep an eye on them. bot: automated, non-human traffic. See Bots.tampering: the client environment has been modified to lie about itself.anti_fingerprinting: the user is running tooling built to defeat fingerprinting, like Tor Browser, Brave farbling, or Firefox RFP.incognito: the session is in private browsing mode.replay_attack: a captured evaluation is being replayed instead of run fresh.",{"id":1217,"title":1218,"titles":1219,"content":1220,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Frisks#custom-risks","Custom risks",[109],"You can define your own risks in the dashboard, on the same machinery the built-in ones run on. Since a risk is a weighted set of indicators (the specific facts Rupt derives from signals), building one is a matter of naming it, picking when it should be evaluated, and choosing which indicators count and by how much. Open Risks in the dashboard and click New risk. You get a canvas with three parts: Trigger: the actions the risk is evaluated on. Pick the built-in login, signup, and access, type any custom action you send Rupt, or run it on every action.Indicators: the indicators that predict the risk, each with a point weight. You're choosing from the same indicators Rupt already collects, so a custom risk starts scoring the moment you save it. There's nothing new to wire up.Risk score: a preview of the most a matching evaluation can score, and the severity it maps to. Scores follow the same cutoffs as everywhere else: 3+ is medium, 5+ is high, 7+ is maximum. A custom risk scores on every matching evaluation and appears in the risks array like any other, so you can read it in your own logic or write a policy over the checks behind it. That lets you target whatever's specific to your business: fraudulent listings, low-intent leads, payout abuse, and the like. Custom risks are built from the indicators Rupt already ships. If you need a signal Rupt doesn't collect yet, let us know and we'll look at adding it.",{"id":1222,"title":1223,"titles":1224,"content":1225,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Frisks#where-risks-fit","Where risks fit",[109],"Risks summarize. Policies decide. The verdict acts.",{"id":114,"title":113,"titles":1227,"content":1228,"level":615},[],"A verdict is Rupt's decision on an action (allow, deny, challenge, suspend, or a list mutation). The matching policy chooses the verdict; your server enforces it.",{"id":1230,"title":113,"titles":1231,"content":1232,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fverdicts#verdicts",[],"A verdict is Rupt's decision on an evaluation. It's the output of the policy that matched, and it tells your server what to do with the user's action. The verdict is just the matched policy's action type, so the set below is exactly the set of actions a policy can take.",{"id":1234,"title":1235,"titles":1236,"content":1237,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fverdicts#the-verdicts","The verdicts",[113],"VerdictWhat it meansServer actionallowNothing matched, or the matched policy says let it through.Honor the action.denyA policy matched and blocks outright.Block the action.challengeIdentity needs to be verified before you trust the action.Block until the linked challenge reaches completed. Otherwise keep blocking.suspendThe user has been suspended on this project.Block. The suspension stays in place until you lift it.add_to_listThe matched value was added to a list.Honor the action. Rupt has already applied the list change.remove_from_listThe mirror of add_to_list.Honor the action. The list change is already done. The value behind add_to_list \u002F remove_from_list depends on the list: it can be the user, IP, email, fingerprint, or another field the list is keyed on.",{"id":1239,"title":1240,"titles":1241,"content":1242,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fverdicts#soft-vs-hard-verdicts","Soft vs hard verdicts",[113],"allow, deny, and suspend are final: the verdict is the answer. challenge is not. With a challenge, the real answer depends on whether the user passes, so treat challenge like deny until you've confirmed the challenge reached completed. Any other state (failed, skipped, or still in progress) should stay blocked. add_to_list and remove_from_list never block the action. They let a policy maintain state without interrupting the user.",{"id":1244,"title":1245,"titles":1246,"content":1247,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fverdicts#confirming-server-side","Confirming server-side",[113],"The verdict that reaches the client is advisory. A determined attacker can strip it before it gets back to your server, so don't trust the client copy for anything that matters. Confirm the verdict by fetching the evaluation directly from Rupt, then check that the action, user, email, phone, and metadata on it match what your server expected before you honor the action. The wiring is in Quick start step 3.",{"id":118,"title":117,"titles":1249,"content":1021,"level":615},[],{"id":1251,"title":117,"titles":1252,"content":1253,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpolicies#policies",[],"A policy is a rule that turns checks into a verdict. It's a tree of conditions plus an action: when the conditions match an evaluation, the action becomes the verdict. Policies are the v3 replacement for v2 environments. Where v2 leaned on fixed environment thresholds, v3 lets you write your own AND\u002FOR conditions over any check and pick the verdict to apply.",{"id":1255,"title":1256,"titles":1257,"content":1258,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpolicies#anatomy-of-a-policy","Anatomy of a policy",[117],"Name and description: for your own reference in the dashboard.Type: development or production. A development policy is evaluated only for traffic from a development API key, and a production policy only for production keys. That lets you test rules without touching live traffic.Enabled: turn a policy on or off without deleting it.Event types: which actions the policy applies to: login, signup, or access.Conditions: a nested AND\u002FOR tree of comparisons over checks, lists, and metadata.Action: the verdict to produce when the conditions match.Priority: decides which policy wins when more than one matches. The highest-priority match takes effect.",{"id":1260,"title":1261,"titles":1262,"content":1263,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpolicies#what-conditions-can-check","What conditions can check",[117],"Condition fields line up with the check inventory, grouped by category: Device: device_count, computer_device_count, tablet_device_count, mobile_device_count, device_id.Network: impossible_travel, is_new_ip, ip_country, ip_is_vpn, ip_is_proxy, ip_is_tor, ip_is_hosting, concurrent_sessions.User: is_new_user, is_email_verified, is_phone_verified, user_age_days, is_suspended, in_list (see Lists), user_external_id, user_email, metadata.Email: email_is_disposable, email_is_webmail, email_is_invalid, email_is_accept_all.Fingerprint: is_new_fingerprint, fingerprint_user_count.Velocity: event_count over a sliding window.Device integrity (native SDKs): jailbroken_ios, rooted_android, is_simulator, is_emulator, debugger_attached, ui_testing.Group: group, the user's assigned group (if you use groups). Conditions match on checks, not risks. Risks are scored separately and recorded on the evaluation for review. Matching a policy directly on a risk score or severity is coming soon.",{"id":1265,"title":1266,"titles":1267,"content":1268,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpolicies#how-matching-works","How matching works",[117],"When an evaluation runs, Rupt walks every enabled policy whose type matches the calling API key's environment and whose event type matches the action, in priority order, and tests each one's conditions against the derived checks. The highest-priority match wins; ties break toward the older policy. Order your rules so the most specific ones sit above the broad catch-alls. The winning policy's action becomes the evaluation's verdict. If nothing matches, the verdict is allow.",{"id":1270,"title":1271,"titles":1272,"content":1273,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpolicies#practical-guidance","Practical guidance",[117],"Start broad and narrow as you learn. A first login policy that challenges when impossible_travel is true catches a lot with very few false positives.Reach for specific checks when you want a surgical rule: deny on is_simulator in native flows, or challenge once device_count crosses a threshold.Keep development and production policies separate by type. The dashboard shows both side by side, and only the set matching the calling key is evaluated.",{"id":122,"title":121,"titles":1275,"content":1276,"level":615},[],"A challenge is the interactive verification step Rupt runs when a verdict is challenge. It walks the user through email or SMS verification and records whether they passed.",{"id":1278,"title":121,"titles":1279,"content":1280,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchallenges#challenges",[],"A challenge is the interactive verification step Rupt runs when an evaluation's verdict is challenge. It interrupts the user, asks them to prove they own the account through a channel you trust, and records the outcome on the challenge. Challenges fire automatically. When a policy whose action is challenge matches, Rupt creates the challenge and the SDK redirects the user to the challenge UI.",{"id":1282,"title":1283,"titles":1284,"content":1285,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchallenges#channels","Channels",[121],"A challenge verifies the user over the channels you've configured: Email: a code sent to the email on file.SMS: a code sent to the phone on file. You can require every configured channel, or accept any one of them.",{"id":1287,"title":1288,"titles":1289,"content":1290,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchallenges#status-lifecycle","Status lifecycle",[121],"Every challenge moves through a fixed set of statuses: created → presented → code_sent → verified → completed\n                          ↓\n                    skipped \u002F overridden created: the policy fired and the challenge record exists, but the user hasn't seen it yet.presented: the user has loaded the challenge UI.code_sent: a verification code has gone out.verified: the user entered the right code.completed: the challenge is fully resolved in the user's favor.skipped: the user chose to skip the challenge. Skipping is off by default; it's available only when you allow it, and only up to the skip limit you set.overridden: a newer challenge replaced this one. When Rupt issues a fresh challenge for the same user and device, it marks any still-pending earlier one as overridden. The verdict on the evaluation is a snapshot from the moment it ran. It stays challenge and doesn't flip to allow on its own. To decide whether to honor the action, read the challenge's status: honor it once the status is completed, and keep blocking for anything else, like skipped or a challenge that simply hasn't completed yet. See Quick start step 3.",{"id":1292,"title":1293,"titles":1294,"content":1295,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchallenges#categories","Categories",[121],"Each challenge carries a type that records why it fired: account_sharing: too many concurrent users or devices on the account.account_takeover: a login from an unfamiliar device or location.multi_accounting: the same fingerprint across accounts that should be independent.fake_account: the signup looks synthetic.repeat_trial: the same person re-creating throwaway accounts. The type comes back on the challenge object returned with the evaluation, and Rupt uses it to tailor the wording the user sees.",{"id":1297,"title":1298,"titles":1299,"content":1300,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fchallenges#the-challenge-ui","The challenge UI",[121],"The SDK redirects the user to Rupt's hosted challenge UI. It handles channel selection, code entry, retries, and rate-limiting, and applies the branding and language you've configured: English, Spanish, French, and Arabic out of the box. html pre.shiki code .sHrIR, html code.shiki .sHrIR{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#A6E22E}html pre.shiki code .siibJ, html code.shiki .siibJ{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#E6DB74}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);}",{"id":126,"title":125,"titles":1302,"content":1303,"level":615},[],"Concurrency is the same user accessing your service from multiple device-and-IP combinations at the same instant. One of the strongest account-sharing signals.",{"id":1305,"title":125,"titles":1306,"content":1307,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fconcurrency#concurrency",[],"Concurrency is the same user accessing your service from multiple device-and-IP combinations at the same instant. It's one of the strongest account sharing signals Rupt has: even careful sharers slip up and use the account from two places at once.",{"id":1309,"title":1310,"titles":1311,"content":1312,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fconcurrency#how-rupt-detects-it","How Rupt detects it",[125],"When an evaluation runs, Rupt checks for any other in-flight session for the same user that: Is on a different IP, ANDIs on the same device type (mobile, tablet, computer), ANDIs currently active. Active session presence is tracked in real time, so the detection window matches the user's actual session, not a coarse time bucket. If a second login lands while the first is still live, concurrent_sessions flips to true on the evaluation and you'll see it surface in the account sharing risk score.",{"id":1314,"title":1315,"titles":1316,"content":1317,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fconcurrency#using-it-in-policies","Using it in policies",[125],"concurrent_sessions is exposed directly as a check, so you can write a policy condition over it: If event_type = access AND concurrent_sessions = true → challenge. The built-in account_sharing risk already weights concurrent_sessions heavily, so it shows up in the risk score recorded on the evaluation. To act on it in a policy today, match the concurrent_sessions check directly, which also gives you surgical control: always blocking concurrent sessions on premium tiers but ignoring them on free ones, for example.",{"id":1319,"title":1320,"titles":1321,"content":1322,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fconcurrency#pairs-well-with","Pairs well with",[125],"Impossible travel: physical impossibility plus simultaneous access is near-conclusive sharing.High device counts: if the account has accumulated many devices over time and shows live concurrency, the case is stronger.",{"id":130,"title":129,"titles":1324,"content":1325,"level":615},[],"Impossible travel is two access events for the same user separated by a distance and time that no real human could bridge, a strong indicator of both account takeover and account sharing.",{"id":1327,"title":129,"titles":1328,"content":1329,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fimpossible-travel#impossible-travel",[],"Impossible travel is two access events for the same user separated by a distance and time that no real human could bridge. It's a strong indicator of both account takeover and account sharing: one person can't physically be in two places at once, so a session in São Paulo immediately followed by a session in Berlin is somebody else.",{"id":1331,"title":1332,"titles":1333,"content":1334,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fimpossible-travel#how-rupt-computes-it","How Rupt computes it",[129],"For each evaluation, Rupt compares the current geolocation against the user's most recent access and checks two conditions: Distance: ≥ 500 km between the two locations, ANDRequired speed: ≥ 1000 km\u002Fh to bridge them in the elapsed time. The window is 48 hours. Older sessions don't trigger the check, since enough time has passed that genuine travel becomes plausible. Both conditions must hold: a 600 km gap over a full day fails the speed check, and a fast turnaround across a small distance fails the distance check.",{"id":1336,"title":1337,"titles":1338,"content":1339,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fimpossible-travel#using-it","Using it",[129],"impossible_travel is exposed as a check and weights into both the account takeover and account sharing risk scores. To act on it in a policy, match the check directly: If event_type = login AND impossible_travel = true → challenge.",{"id":1341,"title":1320,"titles":1342,"content":1343,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fimpossible-travel#pairs-well-with",[129],"Concurrency: if both flip true on the same evaluation, sharing is essentially confirmed.VPN \u002F proxy \u002F Tor flags: impossible travel through an anonymizer is a near-certain ATO indicator.New fingerprint: the geographic shift is more meaningful when the device looks new for the account too.",{"id":1345,"title":1346,"titles":1347,"content":1348,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fimpossible-travel#edge-cases","Edge cases",[129],"Genuine air travel. Long-haul flights cover impossible-travel distances in plausible time and won't trigger the check (1000 km\u002Fh is faster than commercial aviation cruise speed). Short-haul or layover patterns also pass because the distance threshold isn't met.VPNs that flip locations. A user toggling a VPN can technically trigger impossible travel even when they aren't moving. The IP anonymity flags are factored in, so policies can decide whether to treat VPN-driven impossible travel as the same severity as physical impossible travel.",{"id":134,"title":133,"titles":1350,"content":1351,"level":615},[],"Bots are automated, non-human traffic. Rupt classifies bot risk from a battery of behavioral and cryptographic signals, but treats it as observation-only so you choose how to respond.",{"id":1353,"title":133,"titles":1354,"content":1355,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fbots#bots",[],"Bots are automated, non-human traffic: scripts driving a real browser, headless engines, or full automation frameworks pretending to be human. They show up across every fraud surface: signup farms, credential stuffing, scraping, payment testing.",{"id":1357,"title":1358,"titles":1359,"content":1360,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fbots#how-rupt-detects-bots","How Rupt detects bots",[133],"Bot detection runs entirely off signals: a mix of behavioral, cryptographic, and platform probes weighed together. No single tell is decisive; the score comes from how many line up at once. A few illustrative examples: A browser that openly reports it's being driven by automation.Automation frameworks that leave traces in the page environment.Interactions that were dispatched by code rather than a real person.Headless-browser giveaways: rendering and hardware details that don't match a real screen. That's a sample, not the full set. The complete list is deliberately unpublished, since a public checklist is just an evasion guide. Rupt weighs the indicators together into the bot risk.",{"id":1362,"title":1363,"titles":1364,"content":1365,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fbots#observation-only-by-default","Observation-only by default",[133],"The bot risk is observation-only: Rupt records the score on every evaluation but doesn't use it to choose a verdict. This is deliberate: bot policy is product-specific. A scraping API wants to block every bot; a search-engine-friendly site wants crawlers to pass. For now, read the bot score off the evaluation and act on it in your own logic. Matching a policy on the bot risk directly is coming soon. In the meantime, the checks that policies can match catch a lot of automated traffic on their own: ip_is_hosting in particular gates most cloud-run bots.",{"id":1367,"title":1320,"titles":1368,"content":1369,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fbots#pairs-well-with",[133],"tampering: bots that try to disguise themselves usually trip tampering checks too.Hosting IPs: the most aggressive bots run from cloud infrastructure, so ip_is_hosting tends to fire alongside a high bot score.anti_fingerprinting: bots that layer anti-fingerprinting tooling surface through the anti_fingerprinting risk.",{"id":138,"title":137,"titles":1371,"content":1372,"level":615},[],"A device is a stable identifier for a physical phone, tablet, or computer. Multiple fingerprints can map to one device (same Mac, two browsers), and Rupt tracks them as a single entity for sharing and ATO checks.",{"id":1374,"title":137,"titles":1375,"content":1376,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fdevices#devices",[],"A device is a stable identifier for a physical phone, tablet, or computer in Rupt's data model. It sits one level above a fingerprint: multiple browsers on the same laptop produce different fingerprints but the same device. That's important for account sharing: an account with twelve fingerprints across two devices reads very differently from an account with twelve fingerprints across twelve devices.",{"id":1378,"title":1379,"titles":1380,"content":1381,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fdevices#how-devices-are-identified","How devices are identified",[137],"The mechanism depends on how the SDK is shipped: Native iOS: uses the Identifier for Vendor (IDFV). Stable across reinstalls of the same vendor's apps on the device, resets if every app from the vendor is uninstalled. Apple's anti-fingerprinting policy forbids more durable cross-app identifiers.Native Android: uses platform-provided device identifiers per Google's guidelines.Web: derived from the fingerprint plus persistent storage hints. Not as stable as a native device ID; treat web devices as best-effort.",{"id":1383,"title":1384,"titles":1385,"content":1386,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fdevices#what-devices-feed","What devices feed",[137],"Device counts are core to account-sharing detection: device_count: devices currently attached to the user.computer_device_count, tablet_device_count, mobile_device_count: broken out by type, since the thresholds for \"too many\" differ (one phone is fine, six laptops is suspicious). These are exposed as checks so policies can match on them directly.",{"id":1388,"title":1389,"titles":1390,"content":1391,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fdevices#devices-vs-fingerprints-vs-users","Devices vs fingerprints vs users",[137],"Rupt deliberately tracks all three layers: Fingerprint: narrowest. A specific browser instance.Device: broader. The physical hardware behind one or more fingerprints.User: your account-level identity, independent of hardware. A single user may legitimately span multiple devices (laptop, phone, tablet). A single device may legitimately host multiple fingerprints (Chrome, Safari, work browser, personal browser). What's not legitimate is a single user with implausibly many devices. That's the sharing signal Rupt is built to catch, and the basis for the people estimate.",{"id":142,"title":141,"titles":1393,"content":1394,"level":615},[],"A fingerprint is a browser-level identity, a stable hash derived from a browser's signals. Narrower than a device, broader than a session.",{"id":1396,"title":141,"titles":1397,"content":1398,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Ffingerprints#fingerprints",[],"A fingerprint is a browser-level identity in Rupt's data model. It's a stable hash derived from the signals Rupt collects: the user agent, hardware probes, cryptographic measurements, storage state, and behavioral indicators. The same browser on the same machine, fingerprinted twice, produces the same fingerprint. A different browser on the same machine produces a different fingerprint. Fingerprints sit between sessions (which are ephemeral) and devices (which are physical). Multiple fingerprints can map to one device, Chrome and Safari on the same Mac, for example. One fingerprint maps to exactly one browser instance.",{"id":1400,"title":1401,"titles":1402,"content":1403,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Ffingerprints#what-fingerprints-are-good-for","What fingerprints are good for",[141],"Linked accounts: multiple user accounts sharing a single fingerprint usually means one person operating multiple accounts.Multi-accounting and ban evasion: clearing cookies doesn't clear a fingerprint. Someone trying to re-create an account after being blocked will trip on fingerprint reuse.Account takeover detection: a login from a brand-new fingerprint, when the user has been on the same one for months, is a meaningful shift. These are surfaced through checks: is_new_fingerprint: first time this fingerprint is seen for this user.fingerprint_user_count: how many users share this fingerprint.",{"id":1405,"title":1406,"titles":1407,"content":1408,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Ffingerprints#differentiating-within-a-browser-not-across","Differentiating within a browser, not across",[141],"Fingerprinting is intra-browser by design. The goal isn't to recognize the same person whether they switch from Chrome to Safari, which is privacy-violating and unreliable. The goal is within a single browser, to differentiate this instance from every other instance, even if they look superficially similar. What matters for accuracy: that the same browser produces the same fingerprint on every visit, and that two genuinely different browsers don't collide. Cross-browser uniformity isn't the goal.",{"id":1410,"title":1411,"titles":1412,"content":1413,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Ffingerprints#anti-fingerprinting-tooling","Anti-fingerprinting tooling",[141],"Some browsers and extensions actively try to defeat fingerprinting: Tor Browser, Brave's farbling, Firefox's RFP, Safari's ITP lockdown. Rupt detects this directly: the anti_fingerprinting risk surfaces when these tools are in play, regardless of whether they successfully obscured the underlying signals. That gives you policy options ranging from \"ignore\" (privacy-friendly product) to \"challenge\" (high-trust flow).",{"id":146,"title":145,"titles":1415,"content":1416,"level":615},[],"A person is Rupt's estimate of a distinct human behind an account, worked out from device signals. One account can resolve to several people, which is the account-sharing picture.",{"id":1418,"title":145,"titles":1419,"content":1420,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpeople#people",[],"A person is Rupt's best estimate of a single human behind an account. Most accounts are one person, so the usual picture is one person per account. When Rupt sees more than one, the account is being shared. That's the whole point of the concept. People is how you see, at a glance, whether an account belongs to one human or several. It exists for account sharing and nothing else.",{"id":1422,"title":1423,"titles":1424,"content":1425,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpeople#how-rupt-works-it-out","How Rupt works it out",[145],"People come entirely from device signals. When the devices on an account look like they can't all belong to the same human, Rupt treats them as separate people and counts them. So people don't map one-to-one to your accounts. A single account in your system can resolve to several people when several humans are clearly using it. That gap between one account and many people is exactly what sharing looks like.",{"id":1427,"title":1428,"titles":1429,"content":1430,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpeople#what-it-isnt","What it isn't",[145],"People has nothing to do with the identity you send Rupt. The user ID, email, phone, and metadata you pass during an evaluation identify the account. People runs the other direction: it's Rupt's read on how many humans are actually behind that account, inferred from devices, never from anything you told it.",{"id":1432,"title":1433,"titles":1434,"content":1435,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fpeople#all-time-vs-active","All-time vs active",[145],"You'll see two figures on an account: All-time people: every distinct person Rupt has ever seen on the account.Active people: the ones using it right now. A jump in either is the tell that an account has gone from one user to a shared one.",{"id":150,"title":149,"titles":1437,"content":1438,"level":615},[],"Lists are sets of values (user IDs, IPs, emails, fingerprints) that policies match against. Use them to encode allow, block, VIP, and known-fraud rules.",{"id":1440,"title":149,"titles":1441,"content":1442,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Flists#lists",[],"A list is a set of values you can match against in a policy condition. Lists are how you encode rules that depend on which user, IP, email, or fingerprint, not what kind of user, IP, email, or fingerprint. Allow lists, block lists, VIP lists, known-fraud lists: all the same primitive.",{"id":1444,"title":1445,"titles":1446,"content":1447,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Flists#what-goes-in-a-list","What goes in a list",[149],"A list holds values of a single kind: User external_ids.IP addresses.Email addresses or domains.Fingerprint IDs.Phone numbers.Country codes.Device IDs. You manage list contents from the dashboard or the API. Policies reference lists by name.",{"id":1449,"title":1450,"titles":1451,"content":1452,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Flists#how-policies-use-lists","How policies use lists",[149],"The in_list policy condition matches when the value on the evaluation appears in the list: If user_external_id in_list \"vip-customers\" → allow.If ip in_list \"known-fraud-ips\" → deny. Combine list checks with other conditions for finer rules, for example, \"challenge unless the user is on the VIP list.\"",{"id":1454,"title":1455,"titles":1456,"content":1457,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Flists#side-effect-verdicts","Side-effect verdicts",[149],"Two verdicts mutate lists as a side-effect: add_to_list: the matching policy adds the user (or IP, email, fingerprint) to a configured list.remove_from_list: the inverse. The list mutation is applied by Rupt before the evaluation returns, and the action itself is honored by your server. Use this pattern for \"first offense\" rules: record the user with add_to_list once, then a second policy with in_list denies on subsequent attempts.",{"id":1459,"title":1460,"titles":1461,"content":1462,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Flists#practical-patterns","Practical patterns",[149],"VIP allowlist. A high-priority policy that matches user_external_id in_list \"vip\" and returns allow short-circuits any later policy from challenging or denying these users.Known-fraud blocklist. A high-priority policy that matches ip in_list \"fraud-ips\" and returns deny cuts off repeat offenders before any other check runs.Progressive enforcement. First policy: add_to_list \"watching\" on suspicious signals. Second policy: in_list \"watching\" AND (any new offense) → challenge. Third policy: in_list \"watching\" AND (severe offense) → deny.",{"id":154,"title":153,"titles":1464,"content":1465,"level":615},[],"Account takeover (ATO) is when someone who isn't the account owner signs in, usually with stolen or guessed credentials. Rupt scores it at login from device, network, and location signals.",{"id":1467,"title":153,"titles":1468,"content":1469,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Faccount-takeover#account-takeover",[],"Account takeover (ato) is when someone other than the owner signs in to an existing account. The credentials are usually real (bought from a breach dump, phished, or guessed through credential stuffing), so a password check alone won't catch it. What gives the attacker away is the context around the login: a device, network, or location that doesn't fit the real owner. Rupt scores this risk on the login action.",{"id":1471,"title":1472,"titles":1473,"content":1474,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Faccount-takeover#what-rupt-looks-for","What Rupt looks for",[153],"The headline checks that feed the score: New fingerprint: the login comes from a browser or device Rupt hasn't seen on this account.New IP: an address the user hasn't connected from recently.Impossible travel: the account was active somewhere else too recently for the same person to have moved between the two locations.Anonymizing network: the connection is hiding behind a VPN, proxy, or Tor. No single check is damning. People buy new phones and travel. The score climbs when several line up at once: a new device on a new IP behind a VPN, far from where the account usually signs in, is a very different story from any one of those alone.",{"id":1476,"title":1477,"titles":1478,"content":1479,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Faccount-takeover#severity-and-response","Severity and response",[153],"Rupt rolls the triggered checks into an ato risk severity from low to maximum, recorded on the evaluation. To act on it today, your policies match the underlying checks. For example, challenge when a new device and impossible travel stack up, so a genuine owner on a new laptop gets a quick verification instead of a lockout while an attacker stacking signals hits a step they can't fake. Matching a policy on the ato severity directly is coming soon.",{"id":158,"title":157,"titles":1481,"content":1021,"level":615},[],{"id":1483,"title":157,"titles":1484,"content":1485,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Faccount-sharing#account-sharing",[],"Account sharing is when one account is used by more than one person: a login passed around a household, a team seat split between coworkers, a subscription resold to strangers. The credentials are correct every time, so nothing looks wrong at the auth layer. The tell is the pattern of use over time: more devices and more locations than one person racks up, plus activity in two places at once. This is the risk Rupt v2 was built around, and it's scored on the access action. It also stays in play on login so you can act at sign-in rather than waiting for the next page view.",{"id":1487,"title":1472,"titles":1488,"content":1489,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Faccount-sharing#what-rupt-looks-for",[157],"Device count: how many distinct computers, tablets, and phones have touched the account. Counts are tracked per device type, since five phones on one account reads very differently from five shared computers.Concurrent sessions: the account is active from two places at the same time, which one person can't be.Impossible travel: back-to-back activity from locations too far apart to bridge in the time elapsed.Velocity: an unusual rate of activity for a single user.",{"id":1491,"title":1477,"titles":1492,"content":1493,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Faccount-sharing#severity-and-response",[157],"The checks aggregate into an account_sharing risk severity. Sharing is rarely something you want to hard-block, since the account holder is often involved, so most teams challenge to re-verify the owner, or add them to a list for follow-up and treat repeat offenders as an upsell to a larger plan. Your policies decide which.",{"id":162,"title":161,"titles":1495,"content":1496,"level":615},[],"A fake account is a signup that doesn't belong to a real person, created in bulk for fraud, abuse, or farming free trials. Rupt scores it at signup, mostly from the quality of the email provided.",{"id":1498,"title":161,"titles":1499,"content":1500,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Ffake-account#fake-account",[],"A fake account is one created by someone who isn't a genuine new customer: bulk signups for fraud, trial farming, review manipulation, ban evasion, or seeding a botnet. The goal is volume, so the fastest tell is the identity attached to the signup. Real people sign up with an email address they actually keep. Bulk registration leans on throwaway inboxes and shortcuts no human bothers with. Rupt scores this risk on the signup action.",{"id":1502,"title":1472,"titles":1503,"content":1504,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Ffake-account#what-rupt-looks-for",[161],"The headline checks are all about the email provided: Disposable email: the address comes from a throwaway provider built to vanish after one use.Invalid email: the domain has no working mail server, so the address can't receive anything.Unverified email: the user never confirmed they control the inbox.Webmail email: a free consumer provider. Weak on its own (most real people use Gmail), but it adds up with the others. Rupt also scores linked accounts at signup: if the same fingerprint is registering account after account, that's a stronger fraud signal than any single email property.",{"id":1506,"title":1477,"titles":1507,"content":1508,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Ffake-account#severity-and-response",[161],"The checks combine into a fake_account risk severity. Because email quality is cheap to fake and easy to get wrong honestly, blocking outright catches real users too. Most teams challenge the borderline cases and reserve a hard deny for the unmistakable ones: a disposable address from a fingerprint that just made ten other accounts. Your policies draw the line.",{"id":166,"title":165,"titles":1510,"content":1511,"level":615},[],"Scraping is automated traffic that extracts data or hammers an endpoint rather than browsing like a person. Rupt scores it from network reputation and request velocity.",{"id":1513,"title":165,"titles":1514,"content":1515,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fscraping#scraping",[],"Scraping is traffic from an automated extractor rather than a human: a bot pulling your catalog, or a crawler hitting an endpoint far faster than anyone could click. It often rides legitimate accounts and valid sessions, so the request itself looks fine. What stands out is where it comes from and how fast it arrives. Rupt scores this risk on the access and login actions.",{"id":1517,"title":1472,"titles":1518,"content":1519,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fscraping#what-rupt-looks-for",[165],"Anonymizing network: automated traffic tends to run from hosting and datacenter IPs, proxies, or VPNs rather than a home connection. A request from a cloud provider's address range is a classic scraper tell.Velocity: the rate of requests from one IP or user. Humans pause, read, and click; scrapers don't.",{"id":1521,"title":1477,"titles":1522,"content":1523,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fscraping#severity-and-response",[165],"The checks aggregate into a scraping risk severity. Datacenter traffic alone catches a lot of bots, but it also catches corporate VPNs and privacy-conscious users, so velocity is what separates a heavy reader from an extractor. Most teams challenge or rate-limit as these signals stack up and deny only when both are unmistakable. Your policies set the threshold.",{"id":170,"title":169,"titles":1525,"content":1526,"level":615},[],"Linked accounts are separate accounts that share identifying state (most often the same fingerprint) when they should be independent. Useful for catching multi-accounting and ban evasion.",{"id":1528,"title":169,"titles":1529,"content":1530,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Flinked-accounts#linked-accounts",[],"Linked accounts are accounts that look independent but share identifying state underneath. The clearest case is one fingerprint showing up across accounts that should belong to different people. That's the signature of multi-accounting: one person running many accounts to farm referrals, dodge a ban, stack free trials, or stuff a vote. Rupt scores this risk on every action (login, signup, and access) because the same person can resurface at any of them.",{"id":1532,"title":1472,"titles":1533,"content":1534,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Flinked-accounts#what-rupt-looks-for",[169],"Shared fingerprint: how many distinct users a single fingerprint has been seen on. One device behind one account is normal. One device behind a dozen accounts is not.",{"id":1536,"title":1477,"titles":1537,"content":1538,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Flinked-accounts#severity-and-response",[169],"The check feeds a linked_accounts risk severity. Shared devices have innocent explanations, like a family computer or an office machine everyone logs into, so the count matters more than the sharing itself. A handful of accounts on one fingerprint is plausible; dozens is a ban-evasion ring. Most teams add the device to a list for review and escalate to a challenge or deny as the count climbs. Your policies decide where normal ends.",{"id":174,"title":173,"titles":1540,"content":1541,"level":615},[],"A new IP is an address Rupt hasn't seen for this user recently. Weak on its own, but a building block for account takeover and a useful policy condition.",{"id":1543,"title":173,"titles":1544,"content":1545,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fip#new-ip",[],"A new IP means the connecting address isn't one Rupt has recently associated with this user. Rupt keeps a short sliding window of each user's recent addresses; an evaluation from outside that set flips the is_new_ip check to true. On its own this is one of the weakest signals there is. IP addresses change constantly: mobile networks rotate them, people move between Wi-Fi and cellular, ISPs reassign them overnight. A new IP is normal for almost everyone almost all the time.",{"id":1547,"title":1548,"titles":1549,"content":1550,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fip#why-it-still-matters","Why it still matters",[173],"The value shows up in combination. A new IP next to a new fingerprint, or one that triggers impossible travel, or one that resolves to an anonymizing network, is part of the account takeover story. The address being unfamiliar is what makes the rest of the context meaningful.",{"id":1552,"title":1337,"titles":1553,"content":1554,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fip#using-it",[173],"is_new_ip is available as a policy condition, but rarely as a hard rule on its own, since too many real users would trip it. It's most useful weighted into a risk score alongside stronger checks. The raw IP also feeds geolocation and the anonymizing network flags. The address itself is internal context, not something Rupt exposes back to your users.",{"id":178,"title":177,"titles":1556,"content":1021,"level":615},[],{"id":1558,"title":177,"titles":1559,"content":1560,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fanonymizing-network#anonymizing-network",[],"An anonymizing network is anything that sits between the user and your service to hide where the connection really comes from. Rupt classifies every IP and exposes four checks for it: ip_is_vpn, ip_is_proxy, ip_is_tor, and ip_is_hosting. Using one isn't proof of bad intent. Plenty of ordinary people run a VPN for privacy or to reach their work network. But anonymizers are also the default tooling for account takeover, scraping, and fraud, because hiding the source IP is step one of not getting caught.",{"id":1562,"title":1563,"titles":1564,"content":1565,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fanonymizing-network#the-kinds-rupt-detects","The kinds Rupt detects",[177],"VPN: a tunnel that swaps the user's real IP for the VPN server's. Consumer VPNs (NordVPN, ExpressVPN, and the like) run from datacenter ranges Rupt recognizes. Residential or mobile VPNs route through real home and carrier IPs to look like ordinary users, which makes them harder to spot and a favorite of higher-effort fraud.Proxy: a relay that forwards requests on the user's behalf. Open and rotating proxies are the workhorses of scraping, since they spread traffic across many addresses to dodge rate limits.Tor: the onion network. Traffic exits through public Tor nodes that are easy to identify, so a Tor exit IP is unambiguous about wanting anonymity.Hosting \u002F datacenter: the IP belongs to a cloud provider, not a residential ISP. Real customers rarely browse from a server, so datacenter traffic is one of the strongest automation tells.",{"id":1567,"title":1337,"titles":1568,"content":1569,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fanonymizing-network#using-it",[177],"Each flag is its own policy condition, so you can treat them differently. A common pattern is to tolerate VPNs (real users have them) but challenge or block Tor and datacenter traffic on sensitive actions. The flags also weight into risk scores: an anonymizer turns an otherwise ordinary new IP or impossible travel event into a much sharper signal.",{"id":182,"title":181,"titles":1571,"content":1021,"level":615},[],{"id":1573,"title":181,"titles":1574,"content":1575,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Femail#email-quality",[],"When you pass an email to an evaluation, Rupt inspects the address and the domain behind it. None of these checks judges a person, they judge the address, but together they're the backbone of fake-account detection, because bulk registration almost always cuts corners on email.",{"id":1577,"title":1578,"titles":1579,"content":1580,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Femail#the-checks","The checks",[181],"email_is_disposable: the domain is a throwaway provider built to self-destruct (Mailinator, 10minutemail, and thousands like them). A real customer rarely signs up with one; a bot farm almost always does.email_is_invalid: the domain can't actually receive mail (no valid MX records) or the address is malformed. Often a typo, sometimes a fabricated address.email_is_webmail: a free consumer provider like Gmail or Outlook. This is the weakest of the set, since most real people use webmail. It only carries weight stacked with other signals.email_is_accept_all: the domain accepts mail to any address, so you can't tell a real mailbox from an invented one. Common with catch-all business domains, which makes it ambiguous rather than damning.is_email_verified: whether the user has confirmed they control the inbox. An unverified address is just a claim.",{"id":1582,"title":1337,"titles":1583,"content":1584,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Femail#using-it",[181],"Each check is a policy condition and weights into the fake-account risk score. Disposable and invalid are strong enough to act on almost by themselves; webmail and accept-all are softer signals you weigh in rather than block on. The usual approach is to weigh them together rather than block on any one. A disposable address from a fingerprint that just created ten accounts is a far cleaner signal than the email property alone. Rupt reads the email you send it. It never sends mail, and it doesn't verify the inbox for you: is_email_verified reflects what you've told Rupt about the user.",{"id":186,"title":185,"titles":1586,"content":1587,"level":615},[],"Velocity is the rate of activity for a single user or IP over a short window. A spike well past normal human pace points to scraping or account sharing.",{"id":1589,"title":185,"titles":1590,"content":1591,"level":615},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fvelocity#velocity",[],"Velocity is how fast activity is piling up for one user or IP. People act at human pace: they read, pause, navigate, come back later. Automated traffic and heavily shared accounts don't, so a burst of activity well past what one person produces is a useful tell. The has_high_velocity check flips true when the rate for an account or address crosses what's normal for a single human.",{"id":1593,"title":1594,"titles":1595,"content":1596,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fvelocity#what-it-catches","What it catches",[185],"Scraping: a script hitting an endpoint far faster than anyone could click is the textbook high-velocity case.Account sharing: when many people use one login, the account's combined activity runs hotter than any individual would.",{"id":1598,"title":1337,"titles":1599,"content":1600,"level":438},"\u002Fdocs\u002Fv3\u002Fconcepts\u002Fvelocity#using-it",[185],"has_high_velocity weights into both the scraping and account-sharing risk scores, and you can match it directly in a policy. It pairs naturally with the anonymizing network flags for scraping (fast traffic from a datacenter IP) and with concurrency for sharing (fast traffic from two places at once). On its own, treat it as a reason to add friction rather than to hard-block. A genuine power user can occasionally run hot.",{"id":196,"title":195,"titles":1602,"content":1603,"level":615},[],"Proxy setup lets you route Rupt traffic through your own domain, so ad blockers and JS-domain blocking can't cut it off.",{"id":1605,"title":195,"titles":1606,"content":1607,"level":615},"\u002Fdocs\u002Fv3\u002Fadvanced\u002Fproxy-setup#proxy-setup",[],"Proxy setup lets you route Rupt traffic through your own domain, so ad blockers and JS-domain blocking can't cut it off. This page is still being written by a human. Check back soon for the full walkthrough. In the meantime, the quick start covers the basic Rupt integration.",{"id":200,"title":199,"titles":1609,"content":1610,"level":615},[],"Drop-in setups for specific platforms. Each one wires Rupt into a host product, so you don't have to hand-roll the script, look up the user, or build the logout flow yourself. Pick the one that matches where your users sign in.",{"id":1612,"title":199,"titles":1613,"content":1610,"level":615},"\u002Fdocs\u002Fv3\u002Fintegrations#integrations",[],{"id":205,"title":1615,"titles":1616,"content":1617,"level":615},"Kajabi account sharing prevention",[],"Kajabi has no native account sharing protection. Add Rupt with one script tag to detect and stop account and password sharing on your courses.",{"id":1619,"title":1620,"titles":1621,"content":1622,"level":615},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fkajabi-account-sharing-prevention#kajabi-account-sharing-prevention-with-rupt","Kajabi account sharing prevention with Rupt",[],"Kajabi has no built-in account sharing or password sharing protection. Rupt adds it with a single script tag: paste it into Kajabi's header page scripts, add a policy in the Rupt dashboard, and Rupt detects concurrent sessions, challenges the extra viewer, and logs unauthorized devices out. No server code required. This guide assumes you've already chosen Rupt and want to wire it into Kajabi. For the background on why account sharing happens and how detection works, see Account sharing prevention.",{"id":1624,"title":1625,"titles":1626,"content":1627,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fkajabi-account-sharing-prevention#step-1-add-the-rupt-script-to-kajabi","Step 1: Add the Rupt script to Kajabi",[1620],"The script loads on every page, reads the signed-in Kajabi user on its own, and triggers a challenge when one of your policies fires. In Kajabi, go to Settings → Site Details → Page scripts, paste this into Header page scripts, and save: \u003Cscript\n  src=\"https:\u002F\u002Fcdn.rupt.dev\u002Fintegrations\u002Fkajabi\u002Fkajabi.js\"\n  data-client-id=\"xxxx-xxxx-xxxx-xxxx\"\n  async\n>\u003C\u002Fscript> Swap xxxx-xxxx-xxxx-xxxx for your client ID from the Rupt dashboard. There's nothing else to wire up. Rupt's Access protection handles detection, the challenge, owner verification, and device capping on the client. No extra code on your side.",{"id":1629,"title":1630,"titles":1631,"content":1632,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fkajabi-account-sharing-prevention#step-2-configure-your-account-sharing-policies","Step 2: Configure your account sharing policies",[1620],"Policies are what actually catch sharing. Without at least one policy on the access trigger, the script runs but never challenges anyone. A policy has a trigger (the event it runs on) and a verdict. In your policies dashboard, add a policy on the access trigger that challenges when it sees concurrent sessions or too many devices. The Account sharing prevention guide has the exact policies to start from. Set the challenge success URL to your signup or offer page so the extra viewer can start their own subscription.",{"id":1634,"title":1635,"titles":1636,"content":1637,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fkajabi-account-sharing-prevention#step-3-sync-kajabi-contacts-optional","Step 3: Sync Kajabi contacts (optional)",[1620],"Connect the Kajabi API and Rupt shows each user's name, email, and phone next to their devices. Open integration settings in the Rupt dashboard and enable Kajabi.Paste your Kajabi API key and secret (find them in Kajabi under Settings → Account).Click Test connection, then save. Once connected, each user's Kajabi name, email, and phone appear next to their device list in the Rupt dashboard. Rupt matches contacts by the Kajabi contact id the script already sends, so details fill in as your users connect. If no sessions show up after a few logins, check that the script is in Header page scripts (not the footer) and that the client ID matches the one in your Rupt dashboard.",{"id":1639,"title":1640,"titles":1641,"content":1642,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fkajabi-account-sharing-prevention#frequently-asked-questions","Frequently asked questions",[1620],"Does Kajabi prevent account sharing?\nKajabi has no native account sharing or password sharing protection. You add it by installing Rupt, which detects concurrent sessions and challenges unauthorized viewers through a script tag in Kajabi's header page scripts. How do I stop password sharing on Kajabi?\nAdd the Rupt script under Settings → Site Details → Page scripts, then create an access policy in the Rupt dashboard that triggers on concurrent sessions. Rupt handles detection, the challenge, and device logout automatically. Does Rupt work with Kajabi's existing login?\nYes. The Rupt script reads the signed-in Kajabi user from the page, so no changes to Kajabi's login or member portal are needed. It loads with async, so it doesn't block your pages.",{"id":1644,"title":423,"titles":1645,"content":1646,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fkajabi-account-sharing-prevention#related",[1620],"Account sharing preventionAccess protectionChallenge flow html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .slwgX, html code.shiki .slwgX{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#F92672}html pre.shiki code .sXIpk, html code.shiki .sXIpk{--shiki-light:#9C3EDA;--shiki-default:#D19A66;--shiki-dark:#A6E22E}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 .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);}",{"id":209,"title":1648,"titles":1649,"content":1650,"level":615},"Teachable account sharing prevention",[],"Teachable has no native account sharing protection. Add Rupt with one script tag to detect and stop account and password sharing on your courses.",{"id":1652,"title":1653,"titles":1654,"content":1655,"level":615},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fteachable-account-sharing-prevention#teachable-account-sharing-prevention-with-rupt","Teachable account sharing prevention with Rupt",[],"Teachable has no built-in account sharing or password sharing protection. Rupt adds it with a single script tag: paste it into Teachable's logged-in code snippets, add a policy in the Rupt dashboard, and Rupt detects concurrent sessions, challenges the extra viewer, and logs unauthorized devices out. No server code required. This guide assumes you've already chosen Rupt and want to wire it into Teachable. For the background on why account sharing happens and how detection works, see Account sharing prevention.",{"id":1657,"title":1658,"titles":1659,"content":1660,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fteachable-account-sharing-prevention#step-1-add-the-rupt-script-to-teachable","Step 1: Add the Rupt script to Teachable",[1653],"The script loads on every page, reads the signed-in Teachable user on its own, and triggers a challenge when one of your policies fires. In Teachable, go to Site → Code Snippets → Logged in snippets, paste this in, and save: \u003Cscript\n  src=\"https:\u002F\u002Fcdn.rupt.dev\u002Fintegrations\u002Fteachable\u002Fteachable.js\"\n  data-client-id=\"xxxx-xxxx-xxxx-xxxx\"\n  async\n>\u003C\u002Fscript> Swap xxxx-xxxx-xxxx-xxxx for your client ID from the Rupt dashboard. There's nothing else to wire up. Rupt's Access protection handles detection, the challenge, owner verification, and device capping on the client. No extra code on your side.",{"id":1662,"title":1630,"titles":1663,"content":1664,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fteachable-account-sharing-prevention#step-2-configure-your-account-sharing-policies",[1653],"Policies are what actually catch sharing. Without at least one policy on the access trigger, the script runs but never challenges anyone. A policy has a trigger (the event it runs on) and a verdict. In your policies dashboard, add a policy on the access trigger that challenges when it sees concurrent sessions or too many devices. The Account sharing prevention guide has the exact policies to start from. Set the challenge success URL to your signup or offer page so the extra viewer can start their own subscription. If no sessions show up in your dashboard after a few logins, check that the script is in Logged in snippets (not the logged-out snippet) and that the client ID matches the one in your Rupt dashboard.",{"id":1666,"title":1640,"titles":1667,"content":1668,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fteachable-account-sharing-prevention#frequently-asked-questions",[1653],"Does Teachable prevent account sharing?\nTeachable has no native account sharing or password sharing protection. You add it by installing Rupt, which detects concurrent sessions and challenges unauthorized viewers through a script in Teachable's logged-in code snippets. How do I stop password sharing on Teachable?\nAdd the Rupt script under Site → Code Snippets → Logged in snippets, then create an access policy in the Rupt dashboard that triggers on concurrent sessions. Rupt handles detection, the challenge, and device logout automatically. Does Rupt work with Teachable's existing login?\nYes. The Rupt script reads the signed-in Teachable user from the page, so no changes to Teachable's login or school setup are needed. It loads with async, so it doesn't block your pages.",{"id":1670,"title":423,"titles":1671,"content":1646,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fteachable-account-sharing-prevention#related",[1653],{"id":213,"title":1673,"titles":1674,"content":1675,"level":615},"Thinkific account sharing prevention",[],"Thinkific has no native account sharing protection. Add Rupt with one script tag to detect and stop account and password sharing on your courses.",{"id":1677,"title":1678,"titles":1679,"content":1680,"level":615},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fthinkific-account-sharing-prevention#thinkific-account-sharing-prevention-with-rupt","Thinkific account sharing prevention with Rupt",[],"Thinkific has no built-in account sharing or password sharing protection. Rupt adds it with a single script tag: paste it into Thinkific's site footer code, add a policy in the Rupt dashboard, and Rupt detects concurrent sessions, challenges the extra viewer, and logs unauthorized devices out. No server code required. This guide assumes you've already chosen Rupt and want to wire it into Thinkific. For the background on why account sharing happens and how detection works, see Account sharing prevention.",{"id":1682,"title":1683,"titles":1684,"content":1685,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fthinkific-account-sharing-prevention#step-1-add-the-rupt-script-to-thinkific","Step 1: Add the Rupt script to Thinkific",[1678],"The script loads on every page, reads the signed-in Thinkific user on its own, and triggers a challenge when one of your policies fires. In Thinkific, go to Settings → Code & analytics → Site footer code, paste this in, and save: \u003Cscript\n  src=\"https:\u002F\u002Fcdn.rupt.dev\u002Fintegrations\u002Fthinkific\u002Fthinkific.js\"\n  data-client-id=\"xxxx-xxxx-xxxx-xxxx\"\n  async\n>\u003C\u002Fscript> Swap xxxx-xxxx-xxxx-xxxx for your client ID from the Rupt dashboard. There's nothing else to wire up. Rupt's Access protection handles detection, the challenge, owner verification, and device capping on the client. No extra code on your side.",{"id":1687,"title":1630,"titles":1688,"content":1689,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fthinkific-account-sharing-prevention#step-2-configure-your-account-sharing-policies",[1678],"Policies are what actually catch sharing. Without at least one policy on the access trigger, the script runs but never challenges anyone. A policy has a trigger (the event it runs on) and a verdict. In your policies dashboard, add a policy on the access trigger that challenges when it sees concurrent sessions or too many devices. The Account sharing prevention guide has the exact policies to start from. Set the challenge success URL to your signup or offer page so the extra viewer can start their own subscription. If no sessions show up in your dashboard after a few logins, check that the script is in Site footer code and that the client ID matches the one in your Rupt dashboard.",{"id":1691,"title":1640,"titles":1692,"content":1693,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fthinkific-account-sharing-prevention#frequently-asked-questions",[1678],"Does Thinkific prevent account sharing?\nThinkific has no native account sharing or password sharing protection. You add it by installing Rupt, which detects concurrent sessions and challenges unauthorized viewers through a script in Thinkific's site footer code. How do I stop password sharing on Thinkific?\nAdd the Rupt script under Settings → Code & analytics → Site footer code, then create an access policy in the Rupt dashboard that triggers on concurrent sessions. Rupt handles detection, the challenge, and device logout automatically. Does Rupt work with Thinkific's existing login?\nYes. The Rupt script reads the signed-in Thinkific user from the page, so no changes to Thinkific's login or course player are needed. It loads with async, so it doesn't block your pages.",{"id":1695,"title":423,"titles":1696,"content":1646,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Fthinkific-account-sharing-prevention#related",[1678],{"id":217,"title":1698,"titles":1699,"content":1700,"level":615},"LearnWorlds account sharing prevention",[],"LearnWorlds has no native account sharing protection. Add Rupt with one script tag to detect and stop account and password sharing on your courses.",{"id":1702,"title":1703,"titles":1704,"content":1705,"level":615},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Flearnworlds-account-sharing-prevention#learnworlds-account-sharing-prevention-with-rupt","LearnWorlds account sharing prevention with Rupt",[],"LearnWorlds has no built-in account sharing or password sharing protection. Rupt adds it with a single script tag: paste it into LearnWorlds' logged-in custom code, add a policy in the Rupt dashboard, and Rupt detects concurrent sessions, challenges the extra viewer, and logs unauthorized devices out. No server code required. This guide assumes you've already chosen Rupt and want to wire it into LearnWorlds. For the background on why account sharing happens and how detection works, see Account sharing prevention.",{"id":1707,"title":1708,"titles":1709,"content":1710,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Flearnworlds-account-sharing-prevention#step-1-add-the-rupt-script-to-learnworlds","Step 1: Add the Rupt script to LearnWorlds",[1703],"The script reads the signed-in user from the data-user-id and data-user-email values, which LearnWorlds fills in from its own template variables. It triggers a challenge when one of your policies fires. In LearnWorlds, go to Site builder → Custom Code, open the \u003Chead> logged in (html) tab, paste this in, and save: \u003Cscript\n  src=\"https:\u002F\u002Fcdn.rupt.dev\u002Fintegrations\u002Flearnworlds\u002Flearnworlds.js\"\n  data-client-id=\"xxxx-xxxx-xxxx-xxxx\"\n  data-user-id=\"{{ USER.ID }}\"\n  data-user-email=\"{{ USER.EMAIL }}\"\n  async\n>\u003C\u002Fscript> Swap xxxx-xxxx-xxxx-xxxx for your client ID from the Rupt dashboard. Leave the {{ USER.ID }} and {{ USER.EMAIL }} values as they are: LearnWorlds replaces them with the real user when the page loads. Rupt's Access protection handles detection, the challenge, owner verification, and device capping on the client. No extra code on your side.",{"id":1712,"title":1630,"titles":1713,"content":1714,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Flearnworlds-account-sharing-prevention#step-2-configure-your-account-sharing-policies",[1703],"Policies are what actually catch sharing. Without at least one policy on the access trigger, the script runs but never challenges anyone. A policy has a trigger (the event it runs on) and a verdict. In your policies dashboard, add a policy on the access trigger that challenges when it sees concurrent sessions or too many devices. The Account sharing prevention guide has the exact policies to start from. Set the challenge success URL to your signup or offer page so the extra viewer can start their own subscription. If no sessions show up in your dashboard after a few logins, check that the script is in the \u003Chead> logged in (html) tab (not the logged-out one) and that the client ID matches the one in your Rupt dashboard.",{"id":1716,"title":1640,"titles":1717,"content":1718,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Flearnworlds-account-sharing-prevention#frequently-asked-questions",[1703],"Does LearnWorlds prevent account sharing?\nLearnWorlds has no native account sharing or password sharing protection. You add it by installing Rupt, which detects concurrent sessions and challenges unauthorized viewers through a script in LearnWorlds' logged-in custom code. How do I stop password sharing on LearnWorlds?\nAdd the Rupt script under Site builder → Custom Code in the logged-in head tab, then create an access policy in the Rupt dashboard that triggers on concurrent sessions. Rupt handles detection, the challenge, and device logout automatically. Does Rupt work with LearnWorlds' existing login?\nYes. The script reads the signed-in user from LearnWorlds' own template variables, so no changes to LearnWorlds' login or member area are needed. It loads with async, so it doesn't block your pages.",{"id":1720,"title":423,"titles":1721,"content":1646,"level":438},"\u002Fdocs\u002Fv3\u002Fintegrations\u002Flearnworlds-account-sharing-prevention#related",[1703],{"id":226,"title":225,"titles":1723,"content":443,"level":615},[],{"id":1725,"title":245,"titles":1726,"content":1727,"level":615},"\u002Fdocs\u002Fv3\u002Fmigration\u002Foverview#migrating-from-v2-to-v3",[],"v3 is a new API with a new SDK on every platform. The clients are not drop-in compatible with v2, so plan for a real upgrade. The upside is that the v3 client is smaller: most of the wiring you did by hand now lives on a policy in the dashboard, and the SDK handles the rest. This section walks the migration one product at a time. It starts with account sharing, the simplest one, on web and on iOS and Android.",{"id":1729,"title":273,"titles":1730,"content":1731,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Foverview#what-changed-at-a-glance",[245],"v2 and v3 refer to the Rupt API. Each platform ships its own client library on top of it, and every library has its own version number. This table maps each API version to the library that talks to it: PlatformAPI v2 libraryAPI v3 libraryWebrupt 2.x@ruptjs\u002Fclient 3.xiOSRuptClient 3.8.1RuptClient 4.0.0Androidcom.github.getrupt:kotlin 2.1.0dev.rupt.android:rupt-android 4.0.0 The library version and the API version are two different numbers. The v3 clients are @ruptjs\u002Fclient 3.x, RuptClient 4.0.0, and rupt-android 4.0.0. They all talk to the same v3 API. When you see a version on a package, that's the library's version, not the API's.",{"id":1733,"title":370,"titles":1734,"content":1735,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Foverview#the-one-idea-to-hold-onto",[245],"v2 gave each product its own method. Account sharing was attach. v3 has a single entry point, evaluate, and the action names the product: evaluate.access runs the account-sharing check (this was attach).evaluate.login runs the login check.evaluate.signup runs the signup check. Challenges are self-managed now. In v2 you passed redirect URLs and challenge callbacks into the client. In v3 the challenge is configured on a policy in the dashboard, gated on the checks you choose, and the SDK surfaces it for you. Your client code shrinks to one call. Account sharing is fully client-side: you call evaluate.access and Rupt handles detection, the challenge, owner verification, and device capping. There is no server step and no evaluation to consume. Login and signup do add a server-side verification, and they will get their own migration pages here.",{"id":1737,"title":423,"titles":1738,"content":1739,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Foverview#related",[245],"Quick start: the shape of a fresh v3 integration.Access protection: the account-sharing fundamental in full.",{"id":230,"title":229,"titles":1741,"content":1742,"level":615},[],"Move the v2 web account-sharing integration onto v3. Swap the package, create a client, and replace Rupt.attach with rupt.evaluate.access from the same call site.",{"id":1744,"title":229,"titles":1745,"content":1746,"level":615},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-web#account-sharing-on-web",[],"Account sharing maps to the access event. In v2 you called Rupt.attach; in v3 you call rupt.evaluate.access from the same place, once per protected page view, for paying users. The call site does not move. What changes is the package, the way you create the client, and the shape of the call.",{"id":1748,"title":1749,"titles":1750,"content":1751,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-web#step-1-swap-the-package","Step 1: Swap the package",[229],"# remove the v2 package\nnpm uninstall rupt\n\n# add the v3 client\nnpm install @ruptjs\u002Fclient Then update the import: \u002F\u002F v2\nimport Rupt from \"rupt\";\n\n\u002F\u002F v3\nimport Rupt from \"@ruptjs\u002Fclient\";",{"id":1753,"title":1754,"titles":1755,"content":1756,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-web#step-2-create-a-client-then-evaluate","Step 2: Create a client, then evaluate",[229],"v2 was a single global call. v3 creates a client once (this is where the logout callback lives) and calls evaluate.access at the same spot attach used to run. \u002F\u002F v2\nimport Rupt from \"rupt\";\n\nawait Rupt.attach({\n  client_id: \"your_client_id\",\n  account: user.id,\n  redirect_urls: {\n    logout_url: \"https:\u002F\u002Fyourapp.com\u002Flogout\",\n    new_account_url: \"https:\u002F\u002Fyourapp.com\u002Fsignup\",\n  },\n  on_current_device_logout: () => {\n    \u002F\u002F Clear your session and sign the user out locally.\n  },\n  on_limit_exceeded: () => {},\n  on_challenge: () => true,\n}); \u002F\u002F v3\nimport Rupt from \"@ruptjs\u002Fclient\";\n\nconst rupt = new Rupt({\n  clientId: \"your_client_id\",\n  on_logout: () => {\n    \u002F\u002F Clear your session and sign the user out locally.\n  },\n});\n\n\u002F\u002F Same call site as attach: once per protected page, for paying users.\nawait rupt.evaluate.access({\n  user: user.id,\n  email: user.email,\n  phone: user.phone,\n}); When Rupt detects sharing, the SDK redirects to the challenge on its own and brings the user back. You do not read the response for this. If you would rather intercept the challenge instead of auto-redirecting, pass auto_challenge: false and read response.redirect yourself.",{"id":1758,"title":1759,"titles":1760,"content":1761,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-web#step-3-recreate-your-policies","Step 3: Recreate your policies",[229],"v2 shipped with default account-sharing behavior. In v3 that behavior is explicit: it lives in policies you create in your policies dashboard, on the access event. Recreate the three checks v2 came with, each triggering an account-sharing challenge: Device limits (device_count): more devices than one person uses.Impossible travel (impossible_travel): back-to-back activity from locations too far apart to bridge.Concurrency (concurrent_sessions): the account is live in two places at once. Start from these three, then tune to your preference. Until a policy exists on the access event, evaluate.access just gathers signals and never challenges anyone.",{"id":1763,"title":1764,"titles":1765,"content":1766,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-web#step-4-set-the-challenge-urls","Step 4: Set the challenge URLs",[229],"In v2, redirect_urls lived in the attach call. In v3 they live on the policy's challenge config: Success URL: where a blocked user goes to create their own account. Point it at your signup page. Someone who hits a sharing challenge is high intent, so this converts the extra user instead of turning them away.Logout URL: where a device is redirected when it gets logged out. On the challenge screen, when the user chooses to log out the current device, that's where it lands. The Logout URL is not the same as the on_logout callback, and account sharing uses both: Logout URL is a redirect, for the device the user is logging out from on the challenge page.on_logout is a callback that fires when the current session is logged out remotely, that is, when the user logs this device out from somewhere else. Use it to clear your app's own session. If a Logout URL is set, the SDK navigates there too; if not, on_logout is your only hook.",{"id":1768,"title":1769,"titles":1770,"content":1771,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-web#what-went-away","What went away",[229],"v2 (attach)v3client_idclientId on the constructoraccountuserredirect_urlsSuccess and Logout URLs on the policy's challenge configon_current_device_logouton_logout on the constructor (or per call)on_challengeRemoved. The challenge auto-redirects (auto_challenge defaults on for access)on_limit_exceededRemoved. Cap devices with a device_count policylimit_config, tolerance, cookie, secretRemoved from the call. Limits and tolerance are policy-sidedevice_id, attached_devices, access in the responseNot returned. You do not need them client-side",{"id":1773,"title":423,"titles":1774,"content":1775,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-web#related",[229],"Access protection: the account-sharing fundamental in full.Account sharing prevention: the policies to add.Account sharing on iOS and Android: the mobile version of this migration. 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 .sHrIR, html code.shiki .sHrIR{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#A6E22E}html pre.shiki code .siibJ, html code.shiki .siibJ{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#E6DB74}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 .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 .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .sZ9uN, html code.shiki .sZ9uN{--shiki-light:#90A4AE;--shiki-default:#E5C07B;--shiki-dark:#F8F8F2}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 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 .s4fYk, html code.shiki .s4fYk{--shiki-light:#FF5370;--shiki-default:#D19A66;--shiki-dark:#AE81FF}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}",{"id":234,"title":233,"titles":1777,"content":1778,"level":615},[],"Move the v2 iOS and Android account-sharing integration onto v3. Which SDK events were renamed, which were dropped, and the step-by-step swap for each platform.",{"id":1780,"title":1781,"titles":1782,"content":1783,"level":615},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#account-sharing-on-ios-and-android","Account sharing on iOS and Android",[],"The shift is the same as on web: attach becomes evaluate with the access action. The bigger change on mobile is how challenges are shown and which callbacks exist. Three changes apply on both platforms: One entry point. Configuring the client, setting the user, and attaching collapse into a single evaluate call that takes user, email, and phone inline.Challenges render through a container. You add RuptContainerView (iOS) or RuptContainer (Android) to your UI and it observes redirectURL. The SDK no longer pushes its own view controller or activity, so the \"should present\" hooks are gone.The callback set is smaller. The device-limit and pre-challenge callbacks are dropped, and two challenge-button callbacks are added. This is a breaking upgrade. Every app needs code changes, and there is no compatibility shim. The steps below are the full swap.",{"id":1785,"title":1786,"titles":1787,"content":437,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#ios-381-to-400","iOS (3.8.1 to 4.0.0)",[1781],{"id":1789,"title":1790,"titles":1791,"content":1792,"level":1052},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#step-1-replace-the-framework","Step 1: Replace the framework",[1781,1786],"Download the new RuptClient.xcframework and replace the old binary in your project.",{"id":1794,"title":1795,"titles":1796,"content":1797,"level":1052},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#step-2-create-the-client-then-evaluate","Step 2: Create the client, then evaluate",[1781,1786],"\u002F\u002F v2\nlet rupt = Rupt(clientID: \"your_client_id\", useViewController: true)\nrupt.onLogoutCurrentDevice = {\n    \u002F\u002F Clear your session and sign the user out locally.\n}\nrupt.setUserID(user.id)\nrupt.setEmail(user.email)\nrupt.attach() \u002F\u002F v4\nlet rupt = Rupt(clientID: \"your_client_id\")\nrupt.onLogout = {\n    \u002F\u002F Fires when this session is logged out remotely. Clear your local session.\n}\n\n\u002F\u002F Same call site as attach: once per protected screen, for paying users.\ntry await rupt.evaluate(\n    action: \"access\",\n    user: user.id,\n    email: user.email,\n    phone: user.phone\n) setUserID, setEmail, setPhone, identify, and getHash are gone. Pass the user inline to evaluate instead.",{"id":1799,"title":1800,"titles":1801,"content":1802,"level":1052},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#step-3-show-challenges-through-the-container","Step 3: Show challenges through the container",[1781,1786],"Wrap your root view so the challenge can overlay it: RuptContainerView(rupt: rupt) {\n    \u002F\u002F your app content\n} UIKit hosts skip the container, observe $redirectURL, and present the challenge web view themselves.",{"id":1804,"title":1805,"titles":1806,"content":1807,"level":1052},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#ios-events","iOS events",[1781,1786],"v2 (3.8.1)v4 (4.0.0)What changedonLogoutCurrentDeviceonLogoutRenamedonChallengeCompletedonCompleteRenamedonBackButtonPressedonBackButtonPressedUnchangedonLimitExceededRemovedCap devices with a device_count policyonChallengeRemovedThe SDK surfaces the challenge via redirectURLonCreateNewAccountRemovedConversion is handled by the challenge Success URLshouldPresentChallengeViewControllerRemovedUse RuptContainerViewAddedonPrimaryCtaFires when the challenge primary button is tappedAddedonSecondaryCtaFires when the challenge secondary button is tapped",{"id":1809,"title":1810,"titles":1811,"content":437,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#android-210-to-400","Android (2.1.0 to 4.0.0)",[1781],{"id":1813,"title":1814,"titles":1815,"content":1816,"level":1052},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#step-1-swap-the-dependency","Step 1: Swap the dependency",[1781,1810],"Move from JitPack to Maven Central: \u002F\u002F v2\nimplementation 'com.github.getrupt:kotlin:2.1.0'\n\n\u002F\u002F v4\nimplementation 'dev.rupt.android:rupt-android:4.0.0' Make sure mavenCentral() is in your repositories.",{"id":1818,"title":1795,"titles":1819,"content":1820,"level":1052},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#step-2-create-the-client-then-evaluate-1",[1781,1810],"v2 was a configure plus attach on the Rupt singleton. v4 is an instance you create with your client ID. \u002F\u002F v2\nRupt.configure(clientId = \"your_client_id\", account = user.id)\nRupt.onLogoutCurrentDevice = {\n    \u002F\u002F Clear your session and sign the user out locally.\n}\nRupt.attach(context = this) \u002F\u002F v4\nval rupt = Rupt(context = this, clientId = \"your_client_id\")\nrupt.onLogout = {\n    \u002F\u002F Fires when this session is logged out remotely. Clear your local session.\n}\n\n\u002F\u002F Same call site as attach: once per protected screen, for paying users.\nlifecycleScope.launch {\n    rupt.evaluate(\n        action = \"access\",\n        user = user.id,\n        email = user.email,\n        phone = user.phone,\n    )\n} detach, identify, and getHash are gone, and there is no longer a separate attach call to repeat on resume.",{"id":1822,"title":1800,"titles":1823,"content":1824,"level":1052},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#step-3-show-challenges-through-the-container-1",[1781,1810],"setContent {\n    RuptContainer(rupt = rupt) {\n        \u002F\u002F your app content\n    }\n} View-system hosts skip the container, collect rupt.redirectURL in a lifecycle scope, and launch ChallengeActivity with the URL.",{"id":1826,"title":1827,"titles":1828,"content":1829,"level":1052},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#android-events","Android events",[1781,1810],"v2 (2.1.0)v4 (4.0.0)What changedonLogoutCurrentDeviceonLogoutRenamedonChallengeCompletedonCompleteRenamedonBackButtonCallbackonBackButtonPressedRenamed, and no longer passed the back stateonLimitExceededRemovedCap devices with a device_count policyonChallengeRemovedThe SDK surfaces the challenge via redirectURLonCreateNewAccountRemovedConversion is handled by the challenge Success URLshouldStartChallengeActivityRemovedUse RuptContainerAddedonPrimaryCtaFires when the challenge primary button is tappedAddedonSecondaryCtaFires when the challenge secondary button is tapped",{"id":1831,"title":1832,"titles":1833,"content":1834,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#where-the-dropped-events-went","Where the dropped events went",[1781],"The renamed callbacks are a straight swap. The dropped ones moved out of the client on purpose: Device limits (onLimitExceeded) are now a policy on the access event. Set a device_count cap in the dashboard and the device-limit challenge does the capping. There is no client callback to wire.The pre-challenge hook (onChallenge) is gone because the SDK surfaces the challenge itself through redirectURL. There is nothing to intercept.The new-account hook (onCreateNewAccount, and the new_account web view message) is gone because conversion is handled by the challenge's Success URL. Point it at your signup page.The presentation hooks (shouldPresentChallengeViewController, shouldStartChallengeActivity) are replaced by the container. Adopt RuptContainerView or RuptContainer, or observe redirectURL and present it yourself. onPrimaryCta and onSecondaryCta are optional. Use them only if you want to react when the user taps a button on the challenge.",{"id":1836,"title":1837,"titles":1838,"content":1839,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#policies-and-challenge-urls","Policies and challenge URLs",[1781],"The client swap above only wires up detection. What actually triggers a challenge is your policies, and those live in the dashboard, the same across every platform. Recreate the three default account-sharing checks (device limits, impossible travel, concurrency) and set the Success and Logout URLs by following Steps 3 and 4 of the web migration. The Logout URL is where a device lands when the user logs it out from the challenge; onLogout is the callback that fires when this session is logged out remotely.",{"id":1841,"title":423,"titles":1842,"content":1843,"level":438},"\u002Fdocs\u002Fv3\u002Fmigration\u002Faccount-sharing-on-mobile#related",[1781],"Access protection: the account-sharing fundamental in full.Account sharing on web: the web version of this migration. 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 .s2NTT, html code.shiki .s2NTT{--shiki-light:#F76D47;--shiki-default:#C678DD;--shiki-dark:#F92672}html pre.shiki code .sJCYa, html code.shiki .sJCYa{--shiki-light:#90A4AE;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}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 .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--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 .s4fYk, html code.shiki .s4fYk{--shiki-light:#FF5370;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .seeE2, html code.shiki .seeE2{--shiki-light:#90A4AE;--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 .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 .sjp9t, html code.shiki .sjp9t{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#A6E22E}html pre.shiki code .sut_7, html code.shiki .sut_7{--shiki-light:#39ADB5;--shiki-default:#56B6C2;--shiki-dark:#F92672}html pre.shiki code .sDahn, html code.shiki .sDahn{--shiki-light:#39ADB5;--shiki-default:#E5C07B;--shiki-dark:#FD971F}html pre.shiki code .srTuz, html code.shiki .srTuz{--shiki-light:#39ADB5;--shiki-default:#C678DD;--shiki-dark:#F92672}",{"id":519,"title":10,"titles":1845,"content":437,"level":615},[],{"id":1847,"title":10,"titles":1848,"content":1849,"level":615},"\u002Fapi\u002Fv3\u002Fintroduction#introduction",[],"The Rupt API is organized around REST. It has predictable, resource-oriented URLs, accepts JSON request bodies, returns JSON responses, and uses standard HTTP status codes, authentication, and verbs.\n    Every v3 endpoint lives under the \u002Fv3 path. Authenticate with your project secret as a Bearer token.\n  \n  \n    https:\u002F\u002Fapi.rupt.dev\u002Fv3 Most of what Rupt does happens in two places. The client SDK runs in the browser, collects signals, and asks Rupt to evaluate an action such as a login or signup. Rupt returns a verdict and, when a user needs to prove themselves, a redirect to a hosted challenge.\n    This reference covers the server side. Your backend uses a server SDK (or plain HTTP) to read the evaluation behind a verdict, consume it so it can't be replayed, list a user's devices, and keep user records up to date.\n  \n  \n  import Rupt from '@ruptjs\u002Fclient';\n\nconst rupt = new Rupt({ clientId: 'YOUR_CLIENT_ID' });\n\nawait rupt.evaluate.login({ user: 'USER_ID' }); The server SDKs wrap the server API. They handle authentication, retries on network and 5xx errors, and give you typed responses. Pass your project secret when you create the client. Available for Node.js (@ruptjs\u002Fapi on npm) and .NET (Rupt.Api on NuGet).\n  \n  \n  import { RuptAPI } from '@ruptjs\u002Fapi';\n\nconst rupt = new RuptAPI(API_SECRET);\nusing Rupt.Api;\n\nvar rupt = new RuptApi(API_SECRET); 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 .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 pre.shiki code .s2NTT, html code.shiki .s2NTT{--shiki-light:#F76D47;--shiki-default:#C678DD;--shiki-dark:#F92672}html pre.shiki code .sdgkD, html code.shiki .sdgkD{--shiki-light:#90A4AE;--shiki-light-text-decoration:inherit;--shiki-default:#E5C07B;--shiki-default-text-decoration:inherit;--shiki-dark:#A6E22E;--shiki-dark-text-decoration:underline}html pre.shiki code .sUO3M, html code.shiki .sUO3M{--shiki-light:#E2931D;--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 .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .sKfv_, html code.shiki .sKfv_{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F92672}html pre.shiki code .sX0Ul, html code.shiki .sX0Ul{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#E5C07B;--shiki-default-text-decoration:inherit;--shiki-dark:#A6E22E;--shiki-dark-text-decoration:underline}",{"id":523,"title":522,"titles":1851,"content":437,"level":615},[],{"id":1853,"title":522,"titles":1854,"content":1855,"level":615},"\u002Fapi\u002Fv3\u002Ferrors#errors",[],"Rupt uses conventional HTTP response codes. A code in the 2xx range means the request worked. A code in the 4xx range means the request failed given the information you sent, for example a missing parameter or a business rule that wasn't met. A code in the 5xx range means something went wrong on Rupt's side, which is rare.\n    The Node SDK turns non-2xx responses into a RuptApiError that carries the status and the parsed response body, so you can branch on either. Network failures throw RuptNetworkError, and a request that runs past the timeout throws RuptTimeoutError.\n  \n  \n    HTTP status code summary\n    \n      \n        \n          \n            200\n            OK\n            Everything worked as expected.\n          \n          \n            400\n            Bad Request\n            The request was unacceptable, often due to missing a required parameter.\n          \n          \n            401\n            Unauthorized\n            No valid project secret was provided.\n          \n          \n            403\n            Forbidden\n            The project secret doesn't have permission to perform the request.\n          \n          \n            404\n            Not Found\n            The requested resource doesn't exist.\n          \n          \n            409\n            Conflict\n            The resource is in a state that blocks the request, for example an evaluation that was already consumed.\n          \n          \n            428\n            Upgrade Required\n            This action requires a Rupt subscription.\n          \n          \n            500\n            Internal Server Error\n            Something went wrong on Rupt's end.",{"id":526,"title":121,"titles":1857,"content":437,"level":615},[],{"id":1859,"title":121,"titles":1860,"content":1861,"level":615},"\u002Fapi\u002Fv3\u002Fchallenges#challenges",[],"A challenge is how Rupt asks a user to prove themselves when an evaluation calls for it, for example by verifying a code sent to their email or phone. Rupt creates and hosts the challenge, and your client SDK sends the user to it. When you need to inspect a challenge from your backend, retrieve it by ID.\n    To learn more, see the challenge object.\n  \n  \n    Endpoints\n    \n      \n        \n          \n            GET\n            \u002Fv3\u002Fchallenges\u002F:id",{"id":531,"title":530,"titles":1863,"content":437,"level":615},[],{"id":1865,"title":530,"titles":1866,"content":1867,"level":615},"\u002Fapi\u002Fv3\u002Fchallenges\u002Fthe-challenge-object#the-challenge-object",[],"Attributes\n    \n    id string\n    \n    The unique identifier of the challenge.\n    \n    status enum\n    \n    Where the challenge is in its lifecycle.\n    Possible enum values\n    \n      created\n      \n      The challenge was created and is waiting to be presented to the user.\n      \n      presented\n      \n      The user has opened the hosted challenge.\n      \n      code_sent\n      \n      A verification code was sent to the user.\n      \n      verified\n      \n      The user entered a valid code.\n      \n      completed\n      \n      The challenge finished successfully.\n      \n      failed\n      \n      The challenge did not pass.\n      \n      skipped\n      \n      The challenge was skipped.\n      \n      overridden\n      \n      The challenge was resolved by an override rather than by the user.\n    \n    \n    type string (optional)\n    \n    What the challenge is guarding against. One of repeat_trial, account_sharing, account_takeover, multi_accounting, or fake_account.\n    \n    challenge_mode string (optional)\n    \n    How the challenge is run. Rupt-hosted challenges return rupt_managed.\n    \n    delivery_status string (optional)\n    \n    The delivery state of the verification code. One of pending, sent, delivered, failed, or bounced.\n    \n    channels array (optional)\n    \n    The channels the verification code was sent through, in the order they were used. Values are email and text.\n    \n    reasons array (optional)\n    \n    The signals that triggered the challenge, for example limit_exceeded, new_fingerprint, or new_ip.\n    \n    actions array (optional)\n    \n    The actions available on the challenge, for example verify, view, or skip.\n    \n    user object (optional)\n    \n    The user being challenged. See the user object.\n    \n    evaluation string (optional)\n    \n    The identifier of the evaluation that created the challenge.\n    \n    origin_url string (optional)\n    \n    The URL the user was on when the challenge was triggered.\n    \n    email_verified boolean (optional)\n    \n    Whether the user verified ownership of their email during the challenge.\n    \n    phone_verified boolean (optional)\n    \n    Whether the user verified ownership of their phone during the challenge.\n    \n    verify_attempts number (optional)\n    \n    How many times the user has tried to enter a verification code.\n    \n    createdAt date\n    \n    When the challenge was created.\n    \n    updatedAt date (optional)\n    \n    When the challenge was last updated.\n  \n  \n    {\n  \"id\": \"649873be6e8b6f9b33722a0c\",\n  \"status\": \"code_sent\",\n  \"type\": \"account_takeover\",\n  \"challenge_mode\": \"rupt_managed\",\n  \"delivery_status\": \"delivered\",\n  \"channels\": [\"email\"],\n  \"reasons\": [\"new_fingerprint\", \"new_ip\"],\n  \"actions\": [\"verify\", \"skip\"],\n  \"user\": {\n    \"rupt_id\": \"649873be6e8b6f9b33722a0c\",\n    \"id\": \"external_account_id\",\n    \"email\": \"user@example.com\",\n    \"phone\": \"+15551234567\"\n  },\n  \"evaluation\": \"649873be6e8b6f9b33722a0c\",\n  \"origin_url\": \"https:\u002F\u002Fapp.example.com\u002Flogin\",\n  \"email_verified\": false,\n  \"phone_verified\": false,\n  \"verify_attempts\": 1,\n  \"createdAt\": \"2021-09-01T00:00:00.000Z\",\n  \"updatedAt\": \"2021-09-01T00:00:00.000Z\"\n} html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .s32IW, html code.shiki .s32IW{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .s49Q_, html code.shiki .s49Q_{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sw10c, html code.shiki .sw10c{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s9uTm, html code.shiki .s9uTm{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s4VVQ, html code.shiki .s4VVQ{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .stE5w, html code.shiki .stE5w{--shiki-light:#39ADB5;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .s4ofd, html code.shiki .s4ofd{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#AE81FF}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);}",{"id":535,"title":534,"titles":1869,"content":437,"level":615},[],{"id":1871,"title":534,"titles":1872,"content":1873,"level":615},"\u002Fapi\u002Fv3\u002Fchallenges\u002Fretrieve-a-challenge#retrieve-a-challenge",[],"Retrieve an existing challenge by its ID. You get the challenge ID from the challenge reference on an evaluation whose verdict is challenge.\n    Parameters\n    \n    No parameters\n    \n    Returns\n    \n    Returns a challenge object.\n  \n  \n    const challenge = await rupt.getChallenge(CHALLENGE_ID);\nvar challenge = await rupt.GetChallengeAsync(CHALLENGE_ID);\nimport axios from 'axios';\n\nconst { data } = await axios.get(\n  'https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fchallenges\u002FCHALLENGE_ID',\n  { headers: { Authorization: `Bearer ${API_SECRET}` } }\n);\ncurl -H \"Authorization: Bearer ${API_SECRET}\" -X GET \\\n  \"https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fchallenges\u002FCHALLENGE_ID\"\n{\n  \"id\": \"649873be6e8b6f9b33722a0c\",\n  \"status\": \"code_sent\",\n  \"type\": \"account_takeover\",\n  \"challenge_mode\": \"rupt_managed\",\n  \"delivery_status\": \"delivered\",\n  \"channels\": [\"email\"],\n  \"reasons\": [\"new_fingerprint\", \"new_ip\"],\n  \"actions\": [\"verify\", \"skip\"],\n  \"user\": {\n    \"rupt_id\": \"649873be6e8b6f9b33722a0c\",\n    \"id\": \"external_account_id\",\n    \"email\": \"user@example.com\",\n    \"phone\": \"+15551234567\"\n  },\n  \"evaluation\": \"649873be6e8b6f9b33722a0c\",\n  \"email_verified\": false,\n  \"phone_verified\": false,\n  \"verify_attempts\": 1,\n  \"createdAt\": \"2021-09-01T00:00:00.000Z\",\n  \"updatedAt\": \"2021-09-01T00:00:00.000Z\"\n} 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 .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 .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}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 .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 .sUO3M, html code.shiki .sUO3M{--shiki-light:#E2931D;--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 .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .sKfv_, html code.shiki .sKfv_{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F92672}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 .sUwfj, html code.shiki .sUwfj{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .srTuz, html code.shiki .srTuz{--shiki-light:#39ADB5;--shiki-default:#C678DD;--shiki-dark:#F92672}html pre.shiki code .sHrIR, html code.shiki .sHrIR{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#A6E22E}html pre.shiki code .spvyc, html code.shiki .spvyc{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .sIaD8, html code.shiki .sIaD8{--shiki-light:#90A4AE;--shiki-default:#56B6C2;--shiki-dark:#AE81FF}html pre.shiki code .s32IW, html code.shiki .s32IW{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .s49Q_, html code.shiki .s49Q_{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sw10c, html code.shiki .sw10c{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s9uTm, html code.shiki .s9uTm{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s4VVQ, html code.shiki .s4VVQ{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .stE5w, html code.shiki .stE5w{--shiki-light:#39ADB5;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .s4ofd, html code.shiki .s4ofd{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#AE81FF}",{"id":538,"title":137,"titles":1875,"content":437,"level":615},[],{"id":1877,"title":137,"titles":1878,"content":1879,"level":615},"\u002Fapi\u002Fv3\u002Fdevices#devices",[],"A device is the specific mobile, tablet, or desktop a user connects from. Rupt attaches and detaches devices as part of the client SDK flow. From your backend you read a user's devices with retrieve user devices.\n    To learn more, see the device object.\n  \n  \n    Endpoints\n    \n      \n        \n          \n            GET\n            \u002Fv3\u002Fuser\u002F:id\u002Fdevices",{"id":543,"title":542,"titles":1881,"content":437,"level":615},[],{"id":1883,"title":542,"titles":1884,"content":1885,"level":615},"\u002Fapi\u002Fv3\u002Fdevices\u002Fthe-device-object#the-device-object",[],"Attributes\n    \n    id string\n    \n    The unique identifier of the device.\n    \n    user string\n    \n    The unique identifier of the account associated with the challenge.\n    \n    status enum\n    \n    One of attached, detached. Describes the current status of the device.\n    Possible enum values\n    \n      attached\n      \n      The device is attached.\n      \n      detached\n      \n      The device is detached. A device can be detached manually using the Rupt SDK or automatically via environmental factors.\n    \n    \n    metadata object\n    \n    The custom metadata associated with the device.\n    \n    info object\n    \n    Hardware information associated with the device. This information may include browser, engine, os, device and cpu.\n    \n    attached_at date\n    \n    The date and time the device was last attached.\n    \n    actively_connected boolean\n    \n    Whether the device currently has a live realtime connection.\n    \n    createdAt date\n    \n    The date and time the device was created.\n    \n    updatedAt date\n    \n    The date and time the device was last updated.\n  \n  \n    {\n  \"id\": \"635940382397b3ac0b81c0b7\",\n  \"user\": \"649873be6e8b6f9b33722a0c\",\n  \"status\": \"attached\",\n  \"metadata\": {\n    \"key\": \"value\"\n  },\n  \"info\": {\n    \"device\": {\n      \"vendor\": \"Apple\",\n      \"model\": \"iPhone 12\",\n      \"type\": \"mobile\"\n    }\n  },\n  \"attached_at\": \"2021-09-01T00:00:00.000Z\",\n  \"actively_connected\": true,\n  \"createdAt\": \"2021-09-01T00:00:00.000Z\",\n  \"updatedAt\": \"2021-09-01T00:00:00.000Z\"\n} html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .s32IW, html code.shiki .s32IW{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .s49Q_, html code.shiki .s49Q_{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sw10c, html code.shiki .sw10c{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s9uTm, html code.shiki .s9uTm{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s4VVQ, html code.shiki .s4VVQ{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sfLoi, html code.shiki .sfLoi{--shiki-light:#F76D47;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .stE5w, html code.shiki .stE5w{--shiki-light:#39ADB5;--shiki-default:#D19A66;--shiki-dark:#AE81FF}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);}",{"id":547,"title":546,"titles":1887,"content":437,"level":615},[],{"id":1889,"title":546,"titles":1890,"content":1891,"level":615},"\u002Fapi\u002Fv3\u002Fusers#users",[],"A user is a single account in your application, identified by the ID you pass to Rupt. From your backend you can list a user's devices and keep their email, phone, metadata, and group memberships in sync.\n    To learn more, see the user object.\n  \n  \n    Endpoints\n    \n      \n        \n          \n            GET\n            \u002Fv3\u002Fuser\u002F:id\u002Fdevices\n          \n          \n            POST\n            \u002Fv3\u002Fuser\u002F:id\u002Fupdate",{"id":552,"title":551,"titles":1893,"content":437,"level":615},[],{"id":1895,"title":551,"titles":1896,"content":1897,"level":615},"\u002Fapi\u002Fv3\u002Fusers\u002Fthe-user-object#the-user-object",[],"Attributes\n    \n    rupt_id string\n    \n    Rupt's internal identifier for the user. Use this when calling Rupt APIs that take a user reference (for example, listing the user's devices).\n    \n    id string (optional)\n    \n    The identifier your application passed when it evaluated an action with the client SDK. This is absent when the original evaluation didn't carry a user ID, for example a preflight signup keyed only by email.\n    \n    email string (optional)\n    \n    The email address Rupt has on file for this user.\n    \n    phone string (optional)\n    \n    The phone number Rupt has on file for this user.\n    \n    skip_challenge_count number (optional)\n    \n    How many times this user has skipped a challenge. Included on the user embedded in a challenge.\n  \n  \n    {\n  \"rupt_id\": \"649873be6e8b6f9b33722a0c\",\n  \"id\": \"external_account_id\",\n  \"email\": \"user@example.com\",\n  \"phone\": \"+15551234567\"\n} html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .s32IW, html code.shiki .s32IW{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .s49Q_, html code.shiki .s49Q_{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sw10c, html code.shiki .sw10c{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s9uTm, html code.shiki .s9uTm{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#CFCFC2}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);}",{"id":556,"title":555,"titles":1899,"content":437,"level":615},[],{"id":1901,"title":555,"titles":1902,"content":1903,"level":615},"\u002Fapi\u002Fv3\u002Fusers\u002Fupdate-a-user#update-a-user",[],"Update an existing user. Pass the user ID your application uses when it evaluates an action with the client SDK. Only the fields you send are changed; anything you leave out stays as it was.\n    Parameters\n    \n    user string REQUIRED\n    \n    The user ID your application uses to identify this user to Rupt.\n    \n    email string\n    \n    The email address of the user.\n    \n    phone string\n    \n    The phone number of the user. \n    \n    metadata object\n    \n    Custom metadata to store on the user.\n    \n    groups object \u002F array of objects\n    \n    The group or groups the user belongs to. Pass a single group object or an array of them.\n    Child parameters\n    \n      group.id string REQUIRED\n      \n      The id of the group in your system.\n      \n      group.name string\n      \n      The name of the group or organization.\n      \n      group.metadata object\n      \n      Custom metadata to store on the group.\n    \n    \n    Returns\n    \n    Returns nothing on success (204 No Content).\n  \n  \n    await rupt.updateUser({\n  user: \"USER_ID\",\n  email: \"user@example.com\",\n  phone: \"+15551234567\",\n  metadata: {\n    key: \"value\",\n  },\n  groups: [\n    {\n      id: \"group_id\",\n      name: \"Group Name\",\n      metadata: {\n        key: \"value\",\n      },\n    },\n  ],\n});\nawait rupt.UpdateUserAsync(\"USER_ID\", new UpdateUserRequest\n{\n    Email = \"user@example.com\",\n    Phone = \"+15551234567\",\n    Metadata = new Dictionary\u003Cstring, object?>\n    {\n        [\"key\"] = \"value\",\n    },\n    Groups =\n    [\n        new Group\n        {\n            Id = \"group_id\",\n            Name = \"Group Name\",\n            Metadata = new Dictionary\u003Cstring, object?>\n            {\n                [\"key\"] = \"value\",\n            },\n        },\n    ],\n});\nimport axios from 'axios';\n\nawait axios.post(\n  'https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fuser\u002FUSER_ID\u002Fupdate',\n  {\n    email: 'user@example.com',\n    phone: '+15551234567',\n    metadata: {\n      key: 'value',\n    },\n    groups: [\n      {\n        id: 'group_id',\n        name: 'Group Name',\n        metadata: {\n          key: 'value',\n        },\n      },\n    ],\n  },\n  {\n    headers: {\n      'Content-Type': 'application\u002Fjson',\n      Authorization: `Bearer ${API_SECRET}`,\n    },\n  }\n);\ncurl -H \"Authorization: Bearer ${API_SECRET}\" -X POST \\\n  \"https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fuser\u002FUSER_ID\u002Fupdate\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"email\": \"user@example.com\",\n    \"phone\": \"+15551234567\",\n    \"metadata\": { \"key\": \"value\" },\n    \"groups\": [\n      { \"id\": \"group_id\", \"name\": \"Group Name\", \"metadata\": { \"key\": \"value\" } }\n    ]\n  }' 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 .sZ9uN, html code.shiki .sZ9uN{--shiki-light:#90A4AE;--shiki-default:#E5C07B;--shiki-dark:#F8F8F2}html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}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 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 .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 .sKfv_, html code.shiki .sKfv_{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F92672}html pre.shiki code .sX0Ul, html code.shiki .sX0Ul{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#E5C07B;--shiki-default-text-decoration:inherit;--shiki-dark:#A6E22E;--shiki-dark-text-decoration:underline}html pre.shiki code .seeE2, html code.shiki .seeE2{--shiki-light:#90A4AE;--shiki-default:#E06C75;--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 .sweOT, html code.shiki .sweOT{--shiki-light:#E53935;--shiki-default:#98C379;--shiki-dark:#E6DB74}html pre.shiki code .sHrIR, html code.shiki .sHrIR{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#A6E22E}html pre.shiki code .spvyc, html code.shiki .spvyc{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .sIaD8, html code.shiki .sIaD8{--shiki-light:#90A4AE;--shiki-default:#56B6C2;--shiki-dark:#AE81FF}",{"id":560,"title":559,"titles":1905,"content":437,"level":615},[],{"id":1907,"title":559,"titles":1908,"content":1909,"level":615},"\u002Fapi\u002Fv3\u002Fusers\u002Fretrieve-user-devices#retrieve-user-devices",[],"List the devices for a user. Pass the user ID your application uses to identify the user to Rupt.\n    Parameters\n    \n    user string REQUIRED\n    \n    The user ID your application uses to identify this user to Rupt.\n    \n    Returns\n    \n    Returns an array of device objects.\n  \n  \n    const devices = await rupt.getUserDevices({ user: \"USER_ID\" });\nvar devices = await rupt.GetUserDevicesAsync(\"USER_ID\");\nimport axios from 'axios';\n\nconst { data } = await axios.get(\n  'https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fuser\u002FUSER_ID\u002Fdevices',\n  { headers: { Authorization: `Bearer ${API_SECRET}` } }\n);\ncurl -H \"Authorization: Bearer ${API_SECRET}\" -X GET \\\n  \"https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fuser\u002FUSER_ID\u002Fdevices\"\n[\n  {\n    \"id\": \"635940382397b3ac0b81c0b7\",\n    \"user\": \"649873be6e8b6f9b33722a0c\",\n    \"status\": \"attached\",\n    \"metadata\": {\n      \"key\": \"value\"\n    },\n    \"info\": {\n      \"device\": {\n        \"vendor\": \"Apple\",\n        \"model\": \"iPhone 12\",\n        \"type\": \"mobile\"\n      }\n    },\n    \"attached_at\": \"2021-09-01T00:00:00.000Z\",\n    \"actively_connected\": true,\n    \"createdAt\": \"2021-09-01T00:00:00.000Z\",\n    \"updatedAt\": \"2021-09-01T00:00:00.000Z\"\n  }\n] 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 .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 .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}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 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 .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 .sUO3M, html code.shiki .sUO3M{--shiki-light:#E2931D;--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 .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .sKfv_, html code.shiki .sKfv_{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F92672}html pre.shiki code .seeE2, html code.shiki .seeE2{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .srTuz, html code.shiki .srTuz{--shiki-light:#39ADB5;--shiki-default:#C678DD;--shiki-dark:#F92672}html pre.shiki code .sHrIR, html code.shiki .sHrIR{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#A6E22E}html pre.shiki code .spvyc, html code.shiki .spvyc{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .sIaD8, html code.shiki .sIaD8{--shiki-light:#90A4AE;--shiki-default:#56B6C2;--shiki-dark:#AE81FF}html pre.shiki code .s32IW, html code.shiki .s32IW{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .s49Q_, html code.shiki .s49Q_{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sw10c, html code.shiki .sw10c{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s9uTm, html code.shiki .s9uTm{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s4VVQ, html code.shiki .s4VVQ{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sfLoi, html code.shiki .sfLoi{--shiki-light:#F76D47;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .stE5w, html code.shiki .stE5w{--shiki-light:#39ADB5;--shiki-default:#D19A66;--shiki-dark:#AE81FF}",{"id":563,"title":93,"titles":1911,"content":437,"level":615},[],{"id":1913,"title":93,"titles":1914,"content":1915,"level":615},"\u002Fapi\u002Fv3\u002Fevaluations#evaluations",[],"An evaluation is Rupt's verdict on an action such as a login or signup: allow it, deny it, restrict it, or challenge the user. The client SDK creates evaluations in the browser. From your backend you retrieve an evaluation to read its verdict and risk signals, and you consume it so the same evaluation can't be used twice.\n    To learn more, see the evaluation object.\n  \n  \n    Endpoints\n    \n      \n        \n          \n            GET\n            \u002Fv3\u002Fevaluations\u002F:id\n          \n          \n            POST\n            \u002Fv3\u002Fevaluations\u002F:id\u002Fconsume",{"id":568,"title":567,"titles":1917,"content":437,"level":615},[],{"id":1919,"title":567,"titles":1920,"content":1921,"level":615},"\u002Fapi\u002Fv3\u002Fevaluations\u002Fthe-evaluation-object#the-evaluation-object",[],"Attributes\n    \n    id string\n    \n    The unique identifier of the evaluation.\n    \n    action string\n    \n    The action that was evaluated. One of login, signup, access, or a custom action string.\n    \n    verdict string\n    \n    The verdict of the evaluation.\n    Possible enum values\n    \n      allow\n      \n      The action should be allowed.\n      \n      challenge\n      \n      The action should require a challenge. A challenge object will be present, and redirect carries the URL to the hosted challenge page.\n      \n      deny\n      \n      The action should be blocked.\n      \n      restrict\n      \n      The action should be restricted.\n      \n      challenge_and_restrict\n      \n      The action should be restricted and a challenge should be issued.\n    \n    \n    reasons array\n    \n    The reasons that contributed to the verdict.\n    \n    redirect string (optional)\n    \n    When the verdict is challenge, the URL to the hosted challenge page. Navigate the user here to begin the challenge.\n    \n    user object (optional)\n    \n    The end user the evaluation is bound to. See The user object.\n    \n    challenge object (optional)\n    \n    The challenge that was issued, when the verdict is challenge.\n    \n      challenge.id string\n      \n      The challenge identifier. Use it with getChallenge to fetch full state.\n      \n      challenge.status string\n      \n      The current status of the challenge.\n      \n      challenge.type string\n      \n      The challenge category (for example, fake_account, account_sharing).\n    \n    \n    policy object (optional)\n    \n    The policy that matched.\n    \n      policy.id string\n      \n      The policy identifier.\n      \n      policy.name string\n      \n      The policy's display name as configured in the Rupt dashboard.\n      \n      policy.action object\n      \n      The action the policy applied, e.g. { \"type\": \"challenge\" }.\n    \n    \n    fingerprint object (optional)\n    \n    The fingerprint resolved for the request.\n    \n      fingerprint.id string\n      \n      The fingerprint identifier.\n      \n      fingerprint.confidence number\n      \n      Match confidence between 0 and 1.\n    \n    \n    geolocation object (optional)\n    \n    Coarse geolocation data resolved from the IP. Includes city, region, country, country_code, ip, and a security sub-object with vpn, proxy, tor, hosting flags.\n    \n    device string (optional)\n    \n    The identifier of the device tied to this evaluation, when the action created or referenced one.\n    \n    access string (optional)\n    \n    The identifier of the access record tied to this evaluation, when applicable (typically only for access actions).\n    \n    risks array (optional)\n    \n    Risks detected during the evaluation. Each has id (the risk's own identifier), definition (the id of the risk definition it was raised against, used to key off the risk type), name (the risk's display name), severity, score, and indicators.\n    \n    metadata object (optional)\n    \n    The custom metadata your application attached when it evaluated the action.\n    \n    checks object (optional)\n    \n    A snapshot of the boolean and numeric checks the policy engine evaluated.\n    \n    consumed boolean (optional)\n    \n    Whether the evaluation has been consumed. See consume an evaluation.\n    \n    consumed_at date (optional)\n    \n    When the evaluation was consumed, if it has been.\n    \n    createdAt date\n    \n    When the evaluation was created.\n    \n    updatedAt date\n    \n    When the evaluation was last updated.\n  \n  \n    {\n  \"id\": \"649873be6e8b6f9b33722a0c\",\n  \"action\": \"login\",\n  \"verdict\": \"challenge\",\n  \"reasons\": [\"new_fingerprint\", \"new_ip\"],\n  \"redirect\": \"https:\u002F\u002Ftrust.rupt.dev\u002F?challenge=649873be6e8b6f9b33722a0c\",\n  \"user\": {\n    \"rupt_id\": \"649873be6e8b6f9b33722a0c\",\n    \"id\": \"external_account_id\",\n    \"email\": \"user@example.com\",\n    \"phone\": \"+15551234567\"\n  },\n  \"challenge\": {\n    \"id\": \"649873be6e8b6f9b33722a0c\",\n    \"status\": \"created\",\n    \"type\": \"account_takeover\"\n  },\n  \"policy\": {\n    \"id\": \"649873be6e8b6f9b33722a0c\",\n    \"name\": \"Challenge new fingerprints\",\n    \"action\": { \"type\": \"challenge\" }\n  },\n  \"fingerprint\": {\n    \"id\": \"649873be6e8b6f9b33722a0c\",\n    \"confidence\": 0.97\n  },\n  \"geolocation\": {\n    \"city\": \"San Francisco\",\n    \"region\": \"CA\",\n    \"country\": \"United States\",\n    \"country_code\": \"US\",\n    \"ip\": \"1.2.3.4\",\n    \"security\": { \"vpn\": false, \"proxy\": false, \"tor\": false, \"hosting\": false }\n  },\n  \"risks\": [\n    {\n      \"id\": \"649873be6e8b6f9b33722a0c\",\n      \"definition\": \"64pol1cy0000000000000abc\",\n      \"name\": \"ato\",\n      \"severity\": \"low\",\n      \"score\": 3,\n      \"indicators\": [\"new_fingerprint\", \"new_ip\"]\n    }\n  ],\n  \"metadata\": { \"key\": \"value\" },\n  \"createdAt\": \"2021-09-01T00:00:00.000Z\",\n  \"updatedAt\": \"2021-09-01T00:00:00.000Z\"\n} html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .s32IW, html code.shiki .s32IW{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .s49Q_, html code.shiki .s49Q_{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sw10c, html code.shiki .sw10c{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s9uTm, html code.shiki .s9uTm{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s4VVQ, html code.shiki .s4VVQ{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sfLoi, html code.shiki .sfLoi{--shiki-light:#F76D47;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .s4ofd, html code.shiki .s4ofd{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .stE5w, html code.shiki .stE5w{--shiki-light:#39ADB5;--shiki-default:#D19A66;--shiki-dark:#AE81FF}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);}",{"id":572,"title":571,"titles":1923,"content":437,"level":615},[],{"id":1925,"title":571,"titles":1926,"content":1927,"level":615},"\u002Fapi\u002Fv3\u002Fevaluations\u002Fretrieve-an-evaluation#retrieve-an-evaluation",[],"Retrieve an evaluation by its ID to read its verdict, the reasons behind it, and any risk signals. Use this from your backend to confirm a verdict the client SDK produced before you act on it.\n    Retrieving an evaluation does not change it. To mark an evaluation as used so it can't be acted on again, see consume an evaluation.\n    Parameters\n    \n    No parameters\n    \n    Returns\n    \n    Returns an evaluation object.\n  \n  \n    const evaluation = await rupt.getEvaluation(EVALUATION_ID);\nvar evaluation = await rupt.GetEvaluationAsync(EVALUATION_ID);\nimport axios from 'axios';\n\nconst { data } = await axios.get(\n  'https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fevaluations\u002FEVALUATION_ID',\n  { headers: { Authorization: `Bearer ${API_SECRET}` } }\n);\ncurl -H \"Authorization: Bearer ${API_SECRET}\" -X GET \\\n  \"https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fevaluations\u002FEVALUATION_ID\"\n{\n  \"id\": \"649873be6e8b6f9b33722a0c\",\n  \"action\": \"login\",\n  \"verdict\": \"challenge\",\n  \"reasons\": [\"new_fingerprint\", \"new_ip\"],\n  \"redirect\": \"https:\u002F\u002Ftrust.rupt.dev\u002F?challenge=649873be6e8b6f9b33722a0c\",\n  \"user\": {\n    \"rupt_id\": \"649873be6e8b6f9b33722a0c\",\n    \"id\": \"external_account_id\",\n    \"email\": \"user@example.com\",\n    \"phone\": \"+15551234567\"\n  },\n  \"challenge\": {\n    \"id\": \"649873be6e8b6f9b33722a0c\",\n    \"status\": \"created\",\n    \"type\": \"account_takeover\"\n  },\n  \"risks\": [\n    {\n      \"id\": \"649873be6e8b6f9b33722a0c\",\n      \"definition\": \"64pol1cy0000000000000abc\",\n      \"name\": \"ato\",\n      \"severity\": \"low\",\n      \"score\": 3,\n      \"indicators\": [\"new_fingerprint\", \"new_ip\"]\n    }\n  ],\n  \"consumed\": false,\n  \"createdAt\": \"2021-09-01T00:00:00.000Z\",\n  \"updatedAt\": \"2021-09-01T00:00:00.000Z\"\n} 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 .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 .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}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 .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 .sUO3M, html code.shiki .sUO3M{--shiki-light:#E2931D;--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 .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .sKfv_, html code.shiki .sKfv_{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F92672}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 .sUwfj, html code.shiki .sUwfj{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .srTuz, html code.shiki .srTuz{--shiki-light:#39ADB5;--shiki-default:#C678DD;--shiki-dark:#F92672}html pre.shiki code .sHrIR, html code.shiki .sHrIR{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#A6E22E}html pre.shiki code .spvyc, html code.shiki .spvyc{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .sIaD8, html code.shiki .sIaD8{--shiki-light:#90A4AE;--shiki-default:#56B6C2;--shiki-dark:#AE81FF}html pre.shiki code .s32IW, html code.shiki .s32IW{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .s49Q_, html code.shiki .s49Q_{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sw10c, html code.shiki .sw10c{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s9uTm, html code.shiki .s9uTm{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s4VVQ, html code.shiki .s4VVQ{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .s4ofd, html code.shiki .s4ofd{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .stE5w, html code.shiki .stE5w{--shiki-light:#39ADB5;--shiki-default:#D19A66;--shiki-dark:#AE81FF}",{"id":576,"title":575,"titles":1929,"content":437,"level":615},[],{"id":1931,"title":575,"titles":1932,"content":1933,"level":615},"\u002Fapi\u002Fv3\u002Fevaluations\u002Fconsume-an-evaluation#consume-an-evaluation",[],"Consume an evaluation to mark it as used. Consuming is single-use and atomic: the first call wins and returns the evaluation with consumed set to true. A later call for the same evaluation fails with a 409, which lets you treat an evaluation as a one-time token and guard against replays.\n    Parameters\n    \n    No parameters\n    \n    Returns\n    \n    Returns the consumed evaluation object.\n  \n  \n    const evaluation = await rupt.consumeEvaluation(EVALUATION_ID);\nvar evaluation = await rupt.ConsumeEvaluationAsync(EVALUATION_ID);\nimport axios from 'axios';\n\nconst { data } = await axios.post(\n  'https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fevaluations\u002FEVALUATION_ID\u002Fconsume',\n  null,\n  { headers: { Authorization: `Bearer ${API_SECRET}` } }\n);\ncurl -H \"Authorization: Bearer ${API_SECRET}\" -X POST \\\n  \"https:\u002F\u002Fapi.rupt.dev\u002Fv3\u002Fevaluations\u002FEVALUATION_ID\u002Fconsume\"\n{\n  \"id\": \"649873be6e8b6f9b33722a0c\",\n  \"action\": \"login\",\n  \"verdict\": \"allow\",\n  \"reasons\": [\"known_device\"],\n  \"user\": {\n    \"rupt_id\": \"649873be6e8b6f9b33722a0c\",\n    \"id\": \"external_account_id\",\n    \"email\": \"user@example.com\",\n    \"phone\": \"+15551234567\"\n  },\n  \"consumed\": true,\n  \"consumed_at\": \"2021-09-01T00:00:00.000Z\",\n  \"createdAt\": \"2021-09-01T00:00:00.000Z\",\n  \"updatedAt\": \"2021-09-01T00:00:00.000Z\"\n} 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 .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 .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}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 .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 .sUO3M, html code.shiki .sUO3M{--shiki-light:#E2931D;--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 .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .sKfv_, html code.shiki .sKfv_{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F92672}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 .stE5w, html code.shiki .stE5w{--shiki-light:#39ADB5;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .sUwfj, html code.shiki .sUwfj{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .srTuz, html code.shiki .srTuz{--shiki-light:#39ADB5;--shiki-default:#C678DD;--shiki-dark:#F92672}html pre.shiki code .sHrIR, html code.shiki .sHrIR{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#A6E22E}html pre.shiki code .spvyc, html code.shiki .spvyc{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#AE81FF}html pre.shiki code .sIaD8, html code.shiki .sIaD8{--shiki-light:#90A4AE;--shiki-default:#56B6C2;--shiki-dark:#AE81FF}html pre.shiki code .s32IW, html code.shiki .s32IW{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .s49Q_, html code.shiki .s49Q_{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}html pre.shiki code .sw10c, html code.shiki .sw10c{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s9uTm, html code.shiki .s9uTm{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#CFCFC2}html pre.shiki code .s4VVQ, html code.shiki .s4VVQ{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#E06C75;--shiki-default-font-style:inherit;--shiki-dark:#66D9EF;--shiki-dark-font-style:italic}",{"id":585,"title":584,"titles":1935,"content":437,"level":615},[],{"id":1937,"title":584,"titles":1938,"content":1939,"level":615},"\u002Fapi\u002Fv3\u002Fwebhooks\u002Fchallenge-initiated#challenge-initiated",[],"SummaryFires when a challenge is created for a user.PayloadThe challenge.initiated event sends the account it relates to and a summary of the challenge.{\n  event: \"challenge.initiated\",\n  account: String,\n  challenge: {\n    id: String,\n    status: String,\n    code: String,\n    type: String,\n    reasons: String[],\n    createdAt: Date,\n    updatedAt: Date,\n  }\n} html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--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 .seeE2, html code.shiki .seeE2{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .s2Cpd, html code.shiki .s2Cpd{--shiki-light:#E53935;--shiki-default:#ABB2BF;--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);}",{"id":589,"title":588,"titles":1941,"content":437,"level":615},[],{"id":1943,"title":588,"titles":1944,"content":1945,"level":615},"\u002Fapi\u002Fv3\u002Fwebhooks\u002Fchallenge-pending#challenge-pending",[],"SummaryFires when a verification code has been sent and the challenge is waiting on the user.PayloadThe challenge.pending event sends the account it relates to and a summary of the challenge.{\n  event: \"challenge.pending\",\n  account: String,\n  challenge: {\n    id: String,\n    status: String,\n    code: String,\n    type: String,\n    reasons: String[],\n    createdAt: Date,\n    updatedAt: Date,\n  }\n} html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--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 .seeE2, html code.shiki .seeE2{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .s2Cpd, html code.shiki .s2Cpd{--shiki-light:#E53935;--shiki-default:#ABB2BF;--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);}",{"id":593,"title":592,"titles":1947,"content":437,"level":615},[],{"id":1949,"title":592,"titles":1950,"content":1951,"level":615},"\u002Fapi\u002Fv3\u002Fwebhooks\u002Fchallenge-skipped#challenge-skipped",[],"SummaryFires when a challenge is skipped for a user.PayloadThe challenge.skipped event sends the account it relates to and a summary of the challenge.{\n  event: \"challenge.skipped\",\n  account: String,\n  challenge: {\n    id: String,\n    status: String,\n    code: String,\n    type: String,\n    reasons: String[],\n    createdAt: Date,\n    updatedAt: Date,\n  }\n} html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--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 .seeE2, html code.shiki .seeE2{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .s2Cpd, html code.shiki .s2Cpd{--shiki-light:#E53935;--shiki-default:#ABB2BF;--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);}",{"id":597,"title":596,"titles":1953,"content":437,"level":615},[],{"id":1955,"title":596,"titles":1956,"content":1957,"level":615},"\u002Fapi\u002Fv3\u002Fwebhooks\u002Fchallenge-completed#challenge-completed",[],"SummaryFires when a user passes a challenge.PayloadThe challenge.completed event sends the account it relates to and a summary of the challenge.{\n  event: \"challenge.completed\",\n  account: String,\n  challenge: {\n    id: String,\n    status: String,\n    code: String,\n    type: String,\n    reasons: String[],\n    createdAt: Date,\n    updatedAt: Date,\n  }\n} html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--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 .seeE2, html code.shiki .seeE2{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .s2Cpd, html code.shiki .s2Cpd{--shiki-light:#E53935;--shiki-default:#ABB2BF;--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);}",{"id":601,"title":600,"titles":1959,"content":437,"level":615},[],{"id":1961,"title":600,"titles":1962,"content":1963,"level":615},"\u002Fapi\u002Fv3\u002Fwebhooks\u002Faccount-sharing-signal#account-sharing-signal",[],"SummaryFires when Rupt detects a sign that an account is being shared.PayloadThe user.account_sharing_signal event sends the account it relates to and that user's metadata fields.{\n  event: \"user.account_sharing_signal\",\n  account: String,\n  metadata_fields: Map\u003CString, String>,\n} html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--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 .seeE2, html code.shiki .seeE2{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .sut_7, html code.shiki .sut_7{--shiki-light:#39ADB5;--shiki-default:#56B6C2;--shiki-dark:#F92672}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);}",{"id":605,"title":604,"titles":1965,"content":437,"level":615},[],{"id":1967,"title":604,"titles":1968,"content":1969,"level":615},"\u002Fapi\u002Fv3\u002Fwebhooks\u002Fdevice-detached#device-detached",[],"SummaryFires when a device is detached from a user, whether manually or automatically.PayloadThe device.detached event sends the device that was detached. The device's info is omitted from this payload.{\n  event: \"device.detached\",\n  device: {\n    id: String,\n    user: String,\n    status: String,\n    metadata: Object,\n    attached_at: Date,\n    actively_connected: Boolean,\n    createdAt: Date,\n    updatedAt: Date,\n  }\n} html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--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 .seeE2, html code.shiki .seeE2{--shiki-light:#90A4AE;--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);}",{"id":609,"title":608,"titles":1971,"content":437,"level":615},[],{"id":1973,"title":608,"titles":1974,"content":1975,"level":615},"\u002Fapi\u002Fv3\u002Fwebhooks\u002Fuser-converted#user-converted",[],"SummaryFires when a user converts, for example from a free trial to a paid account.PayloadThe user.converted event sends the account that converted along with its current device counts and metadata.{\n  event: \"user.converted\",\n  user: {\n    external_id: String,\n    metadata_fields: Map\u003CString, String>,\n    converted: Boolean,\n    converted_at: Date,\n    conversion_detected_at: Date,\n    device_count: Number,\n    attached_device_count: Number,\n    createdAt: Date,\n    updatedAt: Date,\n  }\n} html pre.shiki code .shEKG, html code.shiki .shEKG{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F8F8F2}html pre.shiki code .sX0i6, html code.shiki .sX0i6{--shiki-light:#E2931D;--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 .seeE2, html code.shiki .seeE2{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#F8F8F2}html pre.shiki code .sut_7, html code.shiki .sut_7{--shiki-light:#39ADB5;--shiki-default:#56B6C2;--shiki-dark:#F92672}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);}",1785260595013]