From fbdc8e6e856f552d0d239393583ff4bb747c2d17 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Fri, 1 May 2026 09:59:39 -0700 Subject: [PATCH 01/32] docs: add IDKit invite-code mode page Documents the cross-device verification flow added in idkit APP-9428, where the relying party shows a 6-character code the user types into World App on a different device instead of scanning a QR. - New page world-id/idkit/invite-code.mdx covering when to use the mode, the code format, lifecycle, and JS/React/Swift integration. - Wired into IDKit nav between Integrate and Signatures. - Cross-link from the standard Integrate page. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs.json | 1 + world-id/idkit/integrate.mdx | 6 + world-id/idkit/invite-code.mdx | 198 +++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 world-id/idkit/invite-code.mdx diff --git a/docs.json b/docs.json index 91b0237..a1c514a 100644 --- a/docs.json +++ b/docs.json @@ -63,6 +63,7 @@ "group": "IDKit", "pages": [ "world-id/idkit/integrate", + "world-id/idkit/invite-code", "world-id/idkit/signatures", "world-id/idkit/build-with-llms", "world-id/idkit/onchain-verification", diff --git a/world-id/idkit/integrate.mdx b/world-id/idkit/integrate.mdx index a4be0cd..f4554ad 100644 --- a/world-id/idkit/integrate.mdx +++ b/world-id/idkit/integrate.mdx @@ -12,6 +12,12 @@ To familiarize yourself with the core concepts of World ID, check out this [page > Tip: To integrate faster, give your coding agent the [Build with LLMs prompt](/world-id/idkit/build-with-llms) — copy it once, paste into Claude, Cursor, or any AI coding assistant. + + Building a cross-device flow (e.g. desktop browser ↔ phone)? See + [Invite-code mode](/world-id/idkit/invite-code) for the typeable 6-character + code alternative to QR scanning. + + # Step 1: Install IDKit Make sure you're using the latest `4.x` version. diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx new file mode 100644 index 0000000..595a952 --- /dev/null +++ b/world-id/idkit/invite-code.mdx @@ -0,0 +1,198 @@ +--- +title: "Invite-code mode" +description: "Verify users with World ID across devices using a 6-character invite code instead of a QR scan." +"og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" +"twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" +--- + +Invite-code mode is a sibling to the default QR / connect-URL flow. Instead of showing a QR code that the user scans with World App, your app displays a short **6-character code** that the user types into World App on a different device. + +The proof, polling lifecycle, status enum, and verification step are identical to QR mode. Only the discovery channel changes. + +## When to use it + +Use invite-code mode when the relying party (your app) and World App are reliably **on different devices** — for example, a desktop browser experience where the user has World App on their phone. Typing a 6-character code is more robust than asking the user to point a phone camera at their own laptop screen. + +Use the standard [QR / connect-URL flow](/world-id/idkit/integrate) when the user can plausibly verify on the same device, when you want a deep-link App Clip handoff, or when you want native in-app verification inside World App. + + + Invite-code mode is bridge-only by design. There is no in-app native path — + the user is, by definition, on a different device than World App. + + +## Code format + +- The canonical code is **6 characters of [Crockford Base32](https://www.crockford.com/base32.html)** with a built-in check character. It catches 100% of single-character substitutions. +- The canonical form has no separator. Your UI may format the code for display as `ABC-DEF`, but never store or transmit the formatted version. +- World App accepts the code in upper or lower case and ignores separators when the user types it in. + +## Lifecycle + +- The bridge issues the code with a short TTL (currently ten minutes). Surface the expiry to the user with a countdown. +- The code is **one-shot**: once redeemed in World App, it cannot be reused. +- After the user redeems the code, your app's existing poll loop receives the proof exactly as it does in QR mode. + +## Integrate + +The setup steps that precede the request — creating an app in the Developer Portal and generating an RP signature on your backend — are identical to the [standard integration](/world-id/idkit/integrate). Only the request call and the rendered UI change. + +### Create the request + + +```typescript title="JavaScript" +import { IDKit, orbLegacy } from "@worldcoin/idkit-core"; + +const request = await IDKit.requestWithInviteCode({ + app_id: "app_xxxxx", + action: "my-action", + rp_context: { + rp_id: "rp_xxxxx", + nonce: rpSig.nonce, + created_at: rpSig.created_at, + expires_at: rpSig.expires_at, + signature: rpSig.sig, + }, + allow_legacy_proofs: true, + environment: "production", +}).preset(orbLegacy({ signal: "user-123" })); + +// Display this to the user. Format as "ABC-DEF" for readability if you like — +// the canonical form has no separator. +const code = request.code; +const expiresAt = request.expiresAt; // Unix seconds + +const completion = await request.pollUntilCompletion(); +``` + +```tsx title="React" +import { + IDKitInviteCodeRequestWidget, + orbLegacy, + type RpContext, +} from "@worldcoin/idkit"; + +const rpContext: RpContext = { + rp_id: "rp_xxxxx", + nonce: rpSig.nonce, + created_at: rpSig.created_at, + expires_at: rpSig.expires_at, + signature: rpSig.sig, +}; + + { + const response = await fetch("/api/verify-proof", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ rp_id: rpContext.rp_id, idkitResponse: result }), + }); + if (!response.ok) throw new Error("Backend verification failed"); + }} + onSuccess={() => { + // Update app state here. + }} +/>; +``` + +```swift title="Swift" +import IDKit + +let config = IDKitRequestConfig( + appId: "app_xxxxx", + action: "my-action", + rpContext: rpContext, + allowLegacyProofs: true, + environment: .production +) + +let request = try IDKit.request(config: config) + .presetWithInviteCode(orbLegacy(signal: "user-123")) + +let code = request.code // 6-character canonical form +let expiresAt = request.expiresAt // Date + +let completion = await request.pollUntilCompletion() +``` + + +The config object is the same one you pass to `IDKit.request(...)` — invite-code mode introduces no new required fields. + +### Display the code + +Show `code` prominently. A formatted display (`ABC-DEF` in a large, monospaced font) is easier to read aloud and harder to mistype than the unseparated form. The Next.js example app and iOS sample app in the IDKit repo both ship a reference UI you can crib from. + +Pair the code with a live countdown driven by `expiresAt`. When the countdown hits zero, surface a clear "Generate a new code" affordance — re-running the request returns a fresh code with a fresh TTL. + +### Poll for completion + +Polling is identical to QR mode. The same `Status` values are emitted (`waiting_for_connection`, `awaiting_confirmation`, `confirmed`, `failed`) and the same `IDKitCompletionResult` is returned. Whatever loop you wrote for QR mode works unchanged. + +```typescript +const completion = await request.pollUntilCompletion({ + pollInterval: 2_000, + timeout: 600_000, // align with the code TTL +}); + +if (completion.success) { + await verifyProofOnBackend(completion.result); +} +``` + +### Verify the proof + +Backend verification is unchanged from QR mode. Forward the IDKit result payload as-is to `POST https://developer.world.org/api/v4/verify/{rp_id}`. See [Verify the proof in your backend](/world-id/idkit/integrate#step-5-verify-the-proof-in-your-backend). + +## API reference + +### JavaScript — `@worldcoin/idkit-core` + +| Symbol | Description | +| --- | --- | +| `IDKit.requestWithInviteCode(config)` | Entry point. Returns an `IDKitInviteCodeBuilder`. Validates the same fields as `IDKit.request`. | +| `.constraints(constraints)` / `.preset(preset)` | Finalize the builder. Returns a `Promise`. | +| `IDKitInviteCodeRequest.code` | Canonical 6-character code (no separator). | +| `IDKitInviteCodeRequest.expiresAt` | Unix-seconds expiry. | +| `IDKitInviteCodeRequest.requestId` | Unique request ID. | +| `IDKitInviteCodeRequest.pollOnce()` | One-shot poll. Returns `Promise`. | +| `IDKitInviteCodeRequest.pollUntilCompletion(options?)` | Poll-until-terminal helper. Returns `Promise`. | + +### React — `@worldcoin/idkit` + +| Symbol | Description | +| --- | --- | +| `` | Drop-in widget. Renders the code, expiry countdown, and instruction copy. Same `handleVerify` / `onSuccess` / `onError` callbacks as `IDKitRequestWidget`. | +| `useIDKitInviteCodeRequest(config)` | Headless hook for custom UI. Returns a `UseIDKitInviteCodeRequestHookResult`. | +| `IDKitInviteCodeHookResult` | Hook state. Exposes `code`, `codeExpiresAt`, plus `isAwaitingUserConnection`, `isAwaitingUserConfirmation`, `isSuccess`, `isError`, `result`, `errorCode`, `open()`, `reset()`. | +| `IDKitInviteCodeRequestWidgetProps` | Widget prop type. Same shape as `IDKitRequestWidgetProps` — invite-code mode adds no new fields. | + +### Swift — `IDKit` + +| Symbol | Description | +| --- | --- | +| `IDKitBuilder.presetWithInviteCode(_:)` | Finalize the builder in invite-code mode. Returns `IDKitInviteCodeRequest`. | +| `IDKitInviteCodeRequest.code` | Canonical 6-character code. | +| `IDKitInviteCodeRequest.expiresAt` | `Date` of code expiry. | +| `IDKitInviteCodeRequest.requestID` | `UUID` request ID. | +| `pollStatusOnce()` / `pollUntilCompletion(options:)` | Identical signatures to `IDKitRequest`. | + +## Migrating from QR mode + +The adopter diff is two lines per integration: + +```diff +- const request = await IDKit.request(config).preset(orbLegacy({ signal })); +- showQR(request.connectorURI); ++ const request = await IDKit.requestWithInviteCode(config).preset(orbLegacy({ signal })); ++ showCode(request.code); + +const completion = await request.pollUntilCompletion(); +``` + +The two modes can coexist in the same app — pick the one that fits the surface (e.g. desktop site uses invite-code mode, native iOS app uses QR / App Clip handoff). From 1767ec58e159dc49868d9e3e8d316918135e4c64 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Fri, 1 May 2026 11:53:50 -0700 Subject: [PATCH 02/32] docs: address PR review on invite-code page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reframe "When to use it" to lead with the onboarding use case (the hero use case is letting World App run in-app onboarding flows so users can satisfy a credential they don't yet hold); cross-device is now described as a secondary fit. - Switch JS/React/Swift examples and the migration diff to selfieCheckLegacy so the doc reflects the current hero preset. - Remove the "easier to read aloud" line and the reference to the example apps under "Display the code" — a React component will ship for that surface. - Update the cross-link in integrate.mdx to match the new framing. Co-Authored-By: Claude Opus 4.7 (1M context) --- world-id/idkit/integrate.mdx | 7 ++++--- world-id/idkit/invite-code.mdx | 29 +++++++++++++---------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/world-id/idkit/integrate.mdx b/world-id/idkit/integrate.mdx index f4554ad..02ee4cd 100644 --- a/world-id/idkit/integrate.mdx +++ b/world-id/idkit/integrate.mdx @@ -13,9 +13,10 @@ To familiarize yourself with the core concepts of World ID, check out this [page > Tip: To integrate faster, give your coding agent the [Build with LLMs prompt](/world-id/idkit/build-with-llms) — copy it once, paste into Claude, Cursor, or any AI coding assistant. - Building a cross-device flow (e.g. desktop browser ↔ phone)? See - [Invite-code mode](/world-id/idkit/invite-code) for the typeable 6-character - code alternative to QR scanning. + Need to onboard users who don't yet hold the credential you're requesting? + See [Invite-code mode](/world-id/idkit/invite-code) — it gives the user a + short code to enter in World App, which then walks them through onboarding + before returning the proof. # Step 1: Install IDKit diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index 595a952..4816443 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -1,24 +1,21 @@ --- title: "Invite-code mode" -description: "Verify users with World ID across devices using a 6-character invite code instead of a QR scan." +description: "Onboard users into the World Network with a short 6-character invite code instead of a QR scan." "og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" "twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" --- -Invite-code mode is a sibling to the default QR / connect-URL flow. Instead of showing a QR code that the user scans with World App, your app displays a short **6-character code** that the user types into World App on a different device. +Invite-code mode is a sibling to the default QR / connect-URL flow. Instead of showing a QR code that the user scans with World App, your app displays a short **6-character code** that the user enters into World App. The proof, polling lifecycle, status enum, and verification step are identical to QR mode. Only the discovery channel changes. ## When to use it -Use invite-code mode when the relying party (your app) and World App are reliably **on different devices** — for example, a desktop browser experience where the user has World App on their phone. Typing a 6-character code is more robust than asking the user to point a phone camera at their own laptop screen. +Invite-code mode is built to **expedite onboarding users into the World Network**. When a user enters your app's invite code into World App, World App uses it to launch the in-app onboarding flows the user needs to complete in order to satisfy your IDKit request. This lets you request a credential the user does not yet hold — World App walks them through getting it, then returns the proof to your app. -Use the standard [QR / connect-URL flow](/world-id/idkit/integrate) when the user can plausibly verify on the same device, when you want a deep-link App Clip handoff, or when you want native in-app verification inside World App. +It is also a good fit for **cross-device flows** (e.g. the user is on a desktop browser and has World App on their phone), where typing a short code is more robust than scanning a QR. - - Invite-code mode is bridge-only by design. There is no in-app native path — - the user is, by definition, on a different device than World App. - +Use the standard [QR / connect-URL flow](/world-id/idkit/integrate) when the user already holds the credential and can verify on the same device, when you want a deep-link App Clip handoff, or when you want native in-app verification inside World App. ## Code format @@ -40,7 +37,7 @@ The setup steps that precede the request — creating an app in the Developer Po ```typescript title="JavaScript" -import { IDKit, orbLegacy } from "@worldcoin/idkit-core"; +import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core"; const request = await IDKit.requestWithInviteCode({ app_id: "app_xxxxx", @@ -54,7 +51,7 @@ const request = await IDKit.requestWithInviteCode({ }, allow_legacy_proofs: true, environment: "production", -}).preset(orbLegacy({ signal: "user-123" })); +}).preset(selfieCheckLegacy({ signal: "user-123" })); // Display this to the user. Format as "ABC-DEF" for readability if you like — // the canonical form has no separator. @@ -67,7 +64,7 @@ const completion = await request.pollUntilCompletion(); ```tsx title="React" import { IDKitInviteCodeRequestWidget, - orbLegacy, + selfieCheckLegacy, type RpContext, } from "@worldcoin/idkit"; @@ -86,7 +83,7 @@ const rpContext: RpContext = { action="my-action" rp_context={rpContext} allow_legacy_proofs={true} - preset={orbLegacy({ signal: "user-123" })} + preset={selfieCheckLegacy({ signal: "user-123" })} handleVerify={async (result) => { const response = await fetch("/api/verify-proof", { method: "POST", @@ -113,7 +110,7 @@ let config = IDKitRequestConfig( ) let request = try IDKit.request(config: config) - .presetWithInviteCode(orbLegacy(signal: "user-123")) + .presetWithInviteCode(selfieCheckLegacy(signal: "user-123")) let code = request.code // 6-character canonical form let expiresAt = request.expiresAt // Date @@ -126,7 +123,7 @@ The config object is the same one you pass to `IDKit.request(...)` — invite-co ### Display the code -Show `code` prominently. A formatted display (`ABC-DEF` in a large, monospaced font) is easier to read aloud and harder to mistype than the unseparated form. The Next.js example app and iOS sample app in the IDKit repo both ship a reference UI you can crib from. +Show `code` prominently. A formatted display (`ABC-DEF` in a large, monospaced font) is harder to mistype than the unseparated form. Pair the code with a live countdown driven by `expiresAt`. When the countdown hits zero, surface a clear "Generate a new code" affordance — re-running the request returns a fresh code with a fresh TTL. @@ -187,9 +184,9 @@ Backend verification is unchanged from QR mode. Forward the IDKit result payload The adopter diff is two lines per integration: ```diff -- const request = await IDKit.request(config).preset(orbLegacy({ signal })); +- const request = await IDKit.request(config).preset(selfieCheckLegacy({ signal })); - showQR(request.connectorURI); -+ const request = await IDKit.requestWithInviteCode(config).preset(orbLegacy({ signal })); ++ const request = await IDKit.requestWithInviteCode(config).preset(selfieCheckLegacy({ signal })); + showCode(request.code); const completion = await request.pollUntilCompletion(); From 638ca50dc6ffd751dce00829700cd65aec48955a Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Fri, 1 May 2026 12:00:22 -0700 Subject: [PATCH 03/32] docs: align invite-code page with IDKit house style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trim the overview and move per-SDK details to the existing reference pages so the structure matches how other IDKit features are documented. - Tighten the lede; collapse "When to use it" to two paragraphs. - Drop the Crockford / check-character internals — feature pages don't explain crypto details (signatures.mdx is the spec page). - Drop the inline "Migrating from QR mode" section — migration content belongs on 4-0-migration.mdx. - Replace the three Markdown API tables with a "SDK references" link list pointing into the per-language pages. - Add a Warning callout about generating rp_context server-side. - Show the RpContext(...) constructor in the Swift sample so it parallels JS/React. - Add "Invite-code mode" sections to javascript.mdx, react.mdx, and swift.mdx using the existing terse-bullet style of those pages. - Add IDKit.requestWithInviteCode and selfieCheckLegacy to the JS entry-points list. Co-Authored-By: Claude Opus 4.7 (1M context) --- world-id/idkit/invite-code.mdx | 116 +++++++++------------------------ world-id/idkit/javascript.mdx | 30 ++++++++- world-id/idkit/react.mdx | 33 ++++++++++ world-id/idkit/swift.mdx | 21 ++++++ 4 files changed, 112 insertions(+), 88 deletions(-) diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index 4816443..24b52da 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -5,40 +5,34 @@ description: "Onboard users into the World Network with a short 6-character invi "twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" --- -Invite-code mode is a sibling to the default QR / connect-URL flow. Instead of showing a QR code that the user scans with World App, your app displays a short **6-character code** that the user enters into World App. - -The proof, polling lifecycle, status enum, and verification step are identical to QR mode. Only the discovery channel changes. +Invite-code mode displays a short 6-character code in your app that the user enters into World App. World App treats the code as an entry point to the in-app onboarding flows the user needs to complete in order to satisfy your IDKit request, then returns the proof. ## When to use it -Invite-code mode is built to **expedite onboarding users into the World Network**. When a user enters your app's invite code into World App, World App uses it to launch the in-app onboarding flows the user needs to complete in order to satisfy your IDKit request. This lets you request a credential the user does not yet hold — World App walks them through getting it, then returns the proof to your app. - -It is also a good fit for **cross-device flows** (e.g. the user is on a desktop browser and has World App on their phone), where typing a short code is more robust than scanning a QR. +Use invite-code mode to expedite onboarding users who don't yet hold the credential you're requesting — World App walks them through getting it. It also fits cross-device flows, where typing a short code is more robust than scanning a QR. Use the standard [QR / connect-URL flow](/world-id/idkit/integrate) when the user already holds the credential and can verify on the same device, when you want a deep-link App Clip handoff, or when you want native in-app verification inside World App. ## Code format -- The canonical code is **6 characters of [Crockford Base32](https://www.crockford.com/base32.html)** with a built-in check character. It catches 100% of single-character substitutions. -- The canonical form has no separator. Your UI may format the code for display as `ABC-DEF`, but never store or transmit the formatted version. -- World App accepts the code in upper or lower case and ignores separators when the user types it in. +- 6 characters, case-insensitive. +- Separators are ignored on entry. Display as `ABC-DEF` if you like; the canonical form has no separator. ## Lifecycle -- The bridge issues the code with a short TTL (currently ten minutes). Surface the expiry to the user with a countdown. -- The code is **one-shot**: once redeemed in World App, it cannot be reused. -- After the user redeems the code, your app's existing poll loop receives the proof exactly as it does in QR mode. +- Codes expire after a short TTL (currently ten minutes). Pair the code with a countdown driven by `expiresAt`. +- Codes are one-shot — once redeemed, they cannot be reused. Re-running the request returns a fresh code with a fresh TTL. +- After the user redeems the code, your existing poll loop receives the proof exactly as it does in QR mode. ## Integrate The setup steps that precede the request — creating an app in the Developer Portal and generating an RP signature on your backend — are identical to the [standard integration](/world-id/idkit/integrate). Only the request call and the rendered UI change. -### Create the request - ```typescript title="JavaScript" import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core"; +// `rpSig` is fetched from your backend — see the standard integration guide. const request = await IDKit.requestWithInviteCode({ app_id: "app_xxxxx", action: "my-action", @@ -53,10 +47,8 @@ const request = await IDKit.requestWithInviteCode({ environment: "production", }).preset(selfieCheckLegacy({ signal: "user-123" })); -// Display this to the user. Format as "ABC-DEF" for readability if you like — -// the canonical form has no separator. -const code = request.code; -const expiresAt = request.expiresAt; // Unix seconds +const code = request.code; // 6-character canonical form +const expiresAt = request.expiresAt; // Unix seconds const completion = await request.pollUntilCompletion(); ``` @@ -101,6 +93,15 @@ const rpContext: RpContext = { ```swift title="Swift" import IDKit +// `rpSig` is fetched from your backend — see the standard integration guide. +let rpContext = try RpContext( + rpId: "rp_xxxxx", + nonce: rpSig.nonce, + createdAt: rpSig.createdAt, + expiresAt: rpSig.expiresAt, + signature: rpSig.sig +) + let config = IDKitRequestConfig( appId: "app_xxxxx", action: "my-action", @@ -119,77 +120,18 @@ let completion = await request.pollUntilCompletion() ``` -The config object is the same one you pass to `IDKit.request(...)` — invite-code mode introduces no new required fields. - -### Display the code - -Show `code` prominently. A formatted display (`ABC-DEF` in a large, monospaced font) is harder to mistype than the unseparated form. - -Pair the code with a live countdown driven by `expiresAt`. When the countdown hits zero, surface a clear "Generate a new code" affordance — re-running the request returns a fresh code with a fresh TTL. - -### Poll for completion + + Generate `rp_context` in your backend only. Never expose your RP signing key in client code. + -Polling is identical to QR mode. The same `Status` values are emitted (`waiting_for_connection`, `awaiting_confirmation`, `confirmed`, `failed`) and the same `IDKitCompletionResult` is returned. Whatever loop you wrote for QR mode works unchanged. - -```typescript -const completion = await request.pollUntilCompletion({ - pollInterval: 2_000, - timeout: 600_000, // align with the code TTL -}); - -if (completion.success) { - await verifyProofOnBackend(completion.result); -} -``` - -### Verify the proof - -Backend verification is unchanged from QR mode. Forward the IDKit result payload as-is to `POST https://developer.world.org/api/v4/verify/{rp_id}`. See [Verify the proof in your backend](/world-id/idkit/integrate#step-5-verify-the-proof-in-your-backend). - -## API reference - -### JavaScript — `@worldcoin/idkit-core` - -| Symbol | Description | -| --- | --- | -| `IDKit.requestWithInviteCode(config)` | Entry point. Returns an `IDKitInviteCodeBuilder`. Validates the same fields as `IDKit.request`. | -| `.constraints(constraints)` / `.preset(preset)` | Finalize the builder. Returns a `Promise`. | -| `IDKitInviteCodeRequest.code` | Canonical 6-character code (no separator). | -| `IDKitInviteCodeRequest.expiresAt` | Unix-seconds expiry. | -| `IDKitInviteCodeRequest.requestId` | Unique request ID. | -| `IDKitInviteCodeRequest.pollOnce()` | One-shot poll. Returns `Promise`. | -| `IDKitInviteCodeRequest.pollUntilCompletion(options?)` | Poll-until-terminal helper. Returns `Promise`. | - -### React — `@worldcoin/idkit` - -| Symbol | Description | -| --- | --- | -| `` | Drop-in widget. Renders the code, expiry countdown, and instruction copy. Same `handleVerify` / `onSuccess` / `onError` callbacks as `IDKitRequestWidget`. | -| `useIDKitInviteCodeRequest(config)` | Headless hook for custom UI. Returns a `UseIDKitInviteCodeRequestHookResult`. | -| `IDKitInviteCodeHookResult` | Hook state. Exposes `code`, `codeExpiresAt`, plus `isAwaitingUserConnection`, `isAwaitingUserConfirmation`, `isSuccess`, `isError`, `result`, `errorCode`, `open()`, `reset()`. | -| `IDKitInviteCodeRequestWidgetProps` | Widget prop type. Same shape as `IDKitRequestWidgetProps` — invite-code mode adds no new fields. | - -### Swift — `IDKit` - -| Symbol | Description | -| --- | --- | -| `IDKitBuilder.presetWithInviteCode(_:)` | Finalize the builder in invite-code mode. Returns `IDKitInviteCodeRequest`. | -| `IDKitInviteCodeRequest.code` | Canonical 6-character code. | -| `IDKitInviteCodeRequest.expiresAt` | `Date` of code expiry. | -| `IDKitInviteCodeRequest.requestID` | `UUID` request ID. | -| `pollStatusOnce()` / `pollUntilCompletion(options:)` | Identical signatures to `IDKitRequest`. | - -## Migrating from QR mode +The config object is the same one you pass to `IDKit.request(...)` — invite-code mode introduces no new required fields. -The adopter diff is two lines per integration: +Polling and proof verification are unchanged from QR mode: the same `Status` values are emitted and the same `IDKitCompletionResult` is returned. Forward the result payload as-is to `POST https://developer.world.org/api/v4/verify/{rp_id}` — see [Verify the proof in your backend](/world-id/idkit/integrate#step-5-verify-the-proof-in-your-backend). -```diff -- const request = await IDKit.request(config).preset(selfieCheckLegacy({ signal })); -- showQR(request.connectorURI); -+ const request = await IDKit.requestWithInviteCode(config).preset(selfieCheckLegacy({ signal })); -+ showCode(request.code); +## SDK references -const completion = await request.pollUntilCompletion(); -``` +For the full surface — entry points, hook results, and type signatures — see the per-SDK reference: -The two modes can coexist in the same app — pick the one that fits the surface (e.g. desktop site uses invite-code mode, native iOS app uses QR / App Clip handoff). +- [JavaScript](/world-id/idkit/javascript#invite-code-mode) +- [React](/world-id/idkit/react#invite-code-mode) +- [Swift](/world-id/idkit/swift#invite-code-mode) diff --git a/world-id/idkit/javascript.mdx b/world-id/idkit/javascript.mdx index 8d82e55..a33f416 100644 --- a/world-id/idkit/javascript.mdx +++ b/world-id/idkit/javascript.mdx @@ -27,7 +27,8 @@ yarn add @worldcoin/idkit-core ## Entry points - `IDKit.request(config)` for uniqueness proofs -- `orbLegacy`, `secureDocumentLegacy`, `documentLegacy` for presets +- `IDKit.requestWithInviteCode(config)` for invite-code mode +- `orbLegacy`, `secureDocumentLegacy`, `documentLegacy`, `selfieCheckLegacy` for presets Each entry point returns a builder. Finalize it with `.preset(...)`. @@ -102,6 +103,33 @@ if (!completion.success) { Outside World App, `connectorURI` is the URL you render as a QR code. +## Invite-code mode + +Use `IDKit.requestWithInviteCode(config)` to display a short code instead of a QR. Validation, the returned `Status` shape, and the poll loop are identical to `IDKit.request`. See [Invite-code mode](/world-id/idkit/invite-code) for when to use it. + +```ts +import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core"; + +const request = await IDKit.requestWithInviteCode({ + app_id: "app_xxxxx", + action: "my-action", + rp_context, + allow_legacy_proofs: true, +}).preset(selfieCheckLegacy({ signal: "user-123" })); + +const code = request.code; // 6-character canonical form +const expiresAt = request.expiresAt; // Unix seconds +const completion = await request.pollUntilCompletion(); +``` + +`IDKitInviteCodeRequest` exposes: + +- `code` +- `expiresAt` +- `requestId` +- `pollOnce()` +- `pollUntilCompletion({ pollInterval, timeout })` + ## Server-side helpers Use subpath exports on your backend: diff --git a/world-id/idkit/react.mdx b/world-id/idkit/react.mdx index 872f3d8..8624157 100644 --- a/world-id/idkit/react.mdx +++ b/world-id/idkit/react.mdx @@ -116,6 +116,39 @@ Hook result fields: - `result` - `errorCode` +## Invite-code mode + +For invite-code flows, use `IDKitInviteCodeRequestWidget` (controlled) or `useIDKitInviteCodeRequest` (headless). Config matches `IDKitRequestWidget` / `useIDKitRequest` — invite-code mode adds no new required fields. See [Invite-code mode](/world-id/idkit/invite-code) for when to use it. + +```tsx +import { IDKitInviteCodeRequestWidget, selfieCheckLegacy } from "@worldcoin/idkit"; + + { /* ... */ }} + onSuccess={() => { /* ... */ }} +/>; +``` + +`useIDKitInviteCodeRequest` result fields (sibling of `useIDKitRequest`'s): + +- `open()` +- `reset()` +- `isAwaitingUserConnection` +- `isAwaitingUserConfirmation` +- `isSuccess` +- `isError` +- `code` +- `codeExpiresAt` +- `result` +- `errorCode` + ## Presets React hooks/widgets take `preset` directly in config. diff --git a/world-id/idkit/swift.mdx b/world-id/idkit/swift.mdx index b74f87b..93ea6b1 100644 --- a/world-id/idkit/swift.mdx +++ b/world-id/idkit/swift.mdx @@ -75,3 +75,24 @@ case .failure(let error): print(error) } ``` + +## Invite-code mode + +Use `presetWithInviteCode(_:)` on the builder to return an `IDKitInviteCodeRequest` instead of `IDKitRequest`. The polling surface is identical. See [Invite-code mode](/world-id/idkit/invite-code) for when to use it. + +```swift +let request = try IDKit.request(config: config) + .presetWithInviteCode(selfieCheckLegacy(signal: "user-123")) + +let code = request.code // 6-character canonical form +let expiresAt = request.expiresAt // Date +let completion = await request.pollUntilCompletion() +``` + +`IDKitInviteCodeRequest` exposes: + +- `code: String` +- `expiresAt: Date` +- `requestID: UUID` +- `pollStatusOnce() async -> IDKitStatus` +- `pollUntilCompletion(options:) async -> IDKitCompletionResult` From 9e12ff2cfa60ef5c58b6065115682dd521b3ef3e Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Fri, 1 May 2026 12:06:20 -0700 Subject: [PATCH 04/32] docs: flag invite-code mode as iOS-only for now Invite-code redemption is currently shipped only in World App for iOS; Android users cannot redeem a code today. Surface the constraint as a Note callout on the overview page and append it to the cross-link in integrate.mdx so anyone evaluating the feature sees it. Co-Authored-By: Claude Opus 4.7 (1M context) --- world-id/idkit/integrate.mdx | 2 +- world-id/idkit/invite-code.mdx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/world-id/idkit/integrate.mdx b/world-id/idkit/integrate.mdx index 02ee4cd..3b72d7d 100644 --- a/world-id/idkit/integrate.mdx +++ b/world-id/idkit/integrate.mdx @@ -16,7 +16,7 @@ To familiarize yourself with the core concepts of World ID, check out this [page Need to onboard users who don't yet hold the credential you're requesting? See [Invite-code mode](/world-id/idkit/invite-code) — it gives the user a short code to enter in World App, which then walks them through onboarding - before returning the proof. + before returning the proof. Currently iOS-only on the World App side. # Step 1: Install IDKit diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index 24b52da..1f900f0 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -7,6 +7,10 @@ description: "Onboard users into the World Network with a short 6-character invi Invite-code mode displays a short 6-character code in your app that the user enters into World App. World App treats the code as an entry point to the in-app onboarding flows the user needs to complete in order to satisfy your IDKit request, then returns the proof. + + Invite-code mode currently requires World App on iOS. Users on Android will not be able to redeem the code today. + + ## When to use it Use invite-code mode to expedite onboarding users who don't yet hold the credential you're requesting — World App walks them through getting it. It also fits cross-device flows, where typing a short code is more robust than scanning a QR. From f48194a850417c3701c47b7da0ee2cb1e5bab41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=C3=A1n=20Olszewski?= Date: Fri, 1 May 2026 12:07:16 -0700 Subject: [PATCH 05/32] Apply suggestion from @SeanROlszewski --- world-id/idkit/invite-code.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index 1f900f0..4d10c58 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -8,7 +8,7 @@ description: "Onboard users into the World Network with a short 6-character invi Invite-code mode displays a short 6-character code in your app that the user enters into World App. World App treats the code as an entry point to the in-app onboarding flows the user needs to complete in order to satisfy your IDKit request, then returns the proof. - Invite-code mode currently requires World App on iOS. Users on Android will not be able to redeem the code today. + Invite-code mode requires World App on iOS. ## When to use it From d45490b5fa81b4fda21d340516209c20035b8224 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Fri, 1 May 2026 12:12:33 -0700 Subject: [PATCH 06/32] docs: flag selfieCheckLegacy as the only supported preset Invite-code mode currently only works with the selfie-check preset. Add a Note alongside the iOS-only one so the constraint is visible on the overview page; the docs will be updated as support is added for other credentials. Co-Authored-By: Claude Opus 4.7 (1M context) --- world-id/idkit/invite-code.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index 4d10c58..9dc27ad 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -11,6 +11,10 @@ Invite-code mode displays a short 6-character code in your app that the user ent Invite-code mode requires World App on iOS. + + Only the `selfieCheckLegacy` preset is supported today. The docs will be updated as support is added for other credentials. + + ## When to use it Use invite-code mode to expedite onboarding users who don't yet hold the credential you're requesting — World App walks them through getting it. It also fits cross-device flows, where typing a short code is more robust than scanning a QR. From 2ee44c4c7c18f8f0e62a353edcc1f983f844c9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=C3=A1n=20Olszewski?= Date: Fri, 1 May 2026 12:19:18 -0700 Subject: [PATCH 07/32] Apply suggestion from @SeanROlszewski --- world-id/idkit/integrate.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world-id/idkit/integrate.mdx b/world-id/idkit/integrate.mdx index 3b72d7d..998f1d3 100644 --- a/world-id/idkit/integrate.mdx +++ b/world-id/idkit/integrate.mdx @@ -16,7 +16,7 @@ To familiarize yourself with the core concepts of World ID, check out this [page Need to onboard users who don't yet hold the credential you're requesting? See [Invite-code mode](/world-id/idkit/invite-code) — it gives the user a short code to enter in World App, which then walks them through onboarding - before returning the proof. Currently iOS-only on the World App side. + before returning the proof. Supported by World App on iOS only. # Step 1: Install IDKit From 0374aee1845396d48ccdb3a4aeb25cb1e5163ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=C3=A1n=20Olszewski?= Date: Fri, 1 May 2026 12:19:55 -0700 Subject: [PATCH 08/32] Apply suggestion from @SeanROlszewski --- world-id/idkit/invite-code.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index 9dc27ad..d7b35e6 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -8,7 +8,7 @@ description: "Onboard users into the World Network with a short 6-character invi Invite-code mode displays a short 6-character code in your app that the user enters into World App. World App treats the code as an entry point to the in-app onboarding flows the user needs to complete in order to satisfy your IDKit request, then returns the proof. - Invite-code mode requires World App on iOS. + Invite-code mode is only supported by World App on iOS. From 0de1f5c72810efdb90b7b72c5c110538d04d4a97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=C3=A1n=20Olszewski?= Date: Fri, 1 May 2026 12:20:17 -0700 Subject: [PATCH 09/32] Apply suggestion from @SeanROlszewski --- world-id/idkit/invite-code.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index d7b35e6..d0b0d88 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -12,7 +12,7 @@ Invite-code mode displays a short 6-character code in your app that the user ent - Only the `selfieCheckLegacy` preset is supported today. The docs will be updated as support is added for other credentials. + Only the `selfieCheckLegacy` preset is supported today. ## When to use it From c7c111590f1f1a76b1124289b83133129fbbc714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=C3=A1n=20Olszewski?= Date: Fri, 1 May 2026 12:20:45 -0700 Subject: [PATCH 10/32] Apply suggestion from @SeanROlszewski --- world-id/idkit/invite-code.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index d0b0d88..11d0d39 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -17,7 +17,7 @@ Invite-code mode displays a short 6-character code in your app that the user ent ## When to use it -Use invite-code mode to expedite onboarding users who don't yet hold the credential you're requesting — World App walks them through getting it. It also fits cross-device flows, where typing a short code is more robust than scanning a QR. +Use invite-code mode to expedite onboarding users who don't yet hold the credential you're requesting — World App walks them through getting it. It also fits cross-device flows, where typing a short code can be easier than scanning a QR. Use the standard [QR / connect-URL flow](/world-id/idkit/integrate) when the user already holds the credential and can verify on the same device, when you want a deep-link App Clip handoff, or when you want native in-app verification inside World App. From 449f8341a835a8dd3e37714bc4caa194922cd59c Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Fri, 1 May 2026 12:25:34 -0700 Subject: [PATCH 11/32] docs: blank-line CodeGroup so GitHub renders it as markdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without the blank lines, opens a CommonMark Type-7 HTML block that swallows the inner code fences — backticks render as literal text on github.com. Adding a blank line after the opening tag and before the closing tag ends the HTML block, letting the fences parse as Markdown. Mintlify renders the same either way. Co-Authored-By: Claude Opus 4.7 (1M context) --- world-id/idkit/invite-code.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index 11d0d39..031e0bf 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -37,6 +37,7 @@ Use the standard [QR / connect-URL flow](/world-id/idkit/integrate) when the use The setup steps that precede the request — creating an app in the Developer Portal and generating an RP signature on your backend — are identical to the [standard integration](/world-id/idkit/integrate). Only the request call and the rendered UI change. + ```typescript title="JavaScript" import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core"; @@ -126,6 +127,7 @@ let expiresAt = request.expiresAt // Date let completion = await request.pollUntilCompletion() ``` + From a598264d0d58afff57088f08367a09742acfc319 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Fri, 1 May 2026 12:32:39 -0700 Subject: [PATCH 12/32] Update dox --- world-id/idkit/invite-code.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index 031e0bf..ec74122 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -38,7 +38,7 @@ The setup steps that precede the request — creating an app in the Developer Po -```typescript title="JavaScript" +```typescript import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core"; // `rpSig` is fetched from your backend — see the standard integration guide. From ef3970e06ce4fb5cb8006a71c63ccfab4a2ccf9b Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Fri, 1 May 2026 12:42:58 -0700 Subject: [PATCH 13/32] docs: drop remaining title= attrs and explain allow_legacy_proofs - Drop title="React" and title="Swift" from the CodeGroup so GitHub renders the fences correctly (matches the JS fence). - Add a paragraph after the CodeGroup explaining why allow_legacy_proofs: true is required (selfieCheckLegacy is a v3 preset), so a copy-paste user trying a non-legacy preset later isn't surprised. Co-Authored-By: Claude Opus 4.7 (1M context) --- world-id/idkit/invite-code.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index ec74122..7309a90 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -62,7 +62,7 @@ const expiresAt = request.expiresAt; // Unix seconds const completion = await request.pollUntilCompletion(); ``` -```tsx title="React" +```tsx import { IDKitInviteCodeRequestWidget, selfieCheckLegacy, @@ -99,7 +99,7 @@ const rpContext: RpContext = { />; ``` -```swift title="Swift" +```swift import IDKit // `rpSig` is fetched from your backend — see the standard integration guide. @@ -136,6 +136,8 @@ let completion = await request.pollUntilCompletion() The config object is the same one you pass to `IDKit.request(...)` — invite-code mode introduces no new required fields. +Set `allow_legacy_proofs: true` because `selfieCheckLegacy` is a v3 ("legacy") preset; the flag lets World App accept v3 proofs to satisfy the request. When invite-code mode adds support for v4 presets, set it to `false` for those flows. + Polling and proof verification are unchanged from QR mode: the same `Status` values are emitted and the same `IDKitCompletionResult` is returned. Forward the result payload as-is to `POST https://developer.world.org/api/v4/verify/{rp_id}` — see [Verify the proof in your backend](/world-id/idkit/integrate#step-5-verify-the-proof-in-your-backend). ## SDK references From b5ddd2fcbd6856343615f54242ea6aa7bea86f56 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 10:26:22 -0700 Subject: [PATCH 14/32] docs: add verification flows page (hot, warm, cold) Converts the internal Cold Flow Dev Docs Notion page into a developer-facing doc explaining the three verification paths and platform-specific cold-flow behavior on Android vs iOS. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 1 + world-id/idkit/verification-flows.mdx | 68 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 world-id/idkit/verification-flows.mdx diff --git a/docs.json b/docs.json index a1c514a..23d3d2f 100644 --- a/docs.json +++ b/docs.json @@ -64,6 +64,7 @@ "pages": [ "world-id/idkit/integrate", "world-id/idkit/invite-code", + "world-id/idkit/verification-flows", "world-id/idkit/signatures", "world-id/idkit/build-with-llms", "world-id/idkit/onchain-verification", diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx new file mode 100644 index 0000000..da16c14 --- /dev/null +++ b/world-id/idkit/verification-flows.mdx @@ -0,0 +1,68 @@ +--- +title: "Verification flows" +description: "Understand the three verification paths a user can take when your app requests a World ID proof — hot, warm, and cold." +"og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" +"twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" +--- + +When your app requests a World ID proof, the user lands in one of three flows depending on their current state. Understanding these flows helps you design the right UX — especially for cold users who have never interacted with World ID before. + +## Hot flow + +The user already holds the credential your app is requesting. World App opens, displays a proof consent modal, and the user approves. The proof is returned to your app immediately. + +This is the fastest path — typically under 10 seconds. + +## Warm flow + +The user has World App installed but does not hold the credential your app is requesting. World App opens and walks the user through the relevant credential enrollment flow (e.g., PoH credential, Face credential, or NFC credential). Once the credential is issued, the proof consent modal appears and the user approves. The proof is returned to your app. + +## Cold flow + +The user does not have World App installed at all. This is the highest-friction path and differs significantly between platforms. + +### Android + +On Android, deep linking preserves context through the install process. After the user downloads World App from the Play Store and opens it, the original verification context is carried forward — World App picks up where the user left off and routes them directly into the credential enrollment flow. No re-trigger from your app is needed. + +### iOS + +iOS does not preserve context through the App Store install. This makes the cold flow higher friction and more prone to drop-off. There are two modes you can use. + +#### Default cold flow + +1. Your app triggers an IDKit verification request. +2. The user is redirected to the App Store to download World App. +3. The user opens World App and completes account creation onboarding. +4. The user returns to your app (manually or via a redirect). +5. Your app re-triggers the IDKit request. +6. World App opens and walks the user through credential enrollment. +7. The proof consent modal appears, the user approves, and the proof is returned to your app. + +The re-trigger in step 5 is necessary because iOS does not carry the original request context through the App Store install. Once the app is installed and onboarding is complete, the user needs to re-enter the verification flow from your app. + +#### Invite-code mode + +1. Your app triggers an IDKit invite-code request. +2. Your app displays a verify URL (typically as a QR) that embeds a 6-character invite code. +3. The user opens the URL on their phone. The `world.org/verify` landing page surfaces the code and a download link. +4. The user downloads World App from the App Store. +5. The user opens World App and completes account creation onboarding. +6. The user enters the invite code. +7. World App resolves the code, picks up the original verification context, and walks the user through credential enrollment. +8. The proof consent modal appears, the user approves, and the proof is returned to your app. + +No re-trigger from your app is needed. The 6-character code persists across the App Store install — in the user's memory, clipboard, or screenshot — so once World App is installed and onboarded the user can resume the verification flow directly. This mode also covers cross-device scenarios (e.g., a desktop browser displaying the QR for the user's phone) where deep linking cannot carry context. + + + For integration details and code examples, see [Invite-code mode](/world-id/idkit/invite-code). + + +## Choosing the right approach + +| Scenario | Recommended flow | Why | +| --- | --- | --- | +| User likely already has the credential | Default (hot/warm) | Fastest path, no extra UX needed | +| Onboarding new users on Android | Default | Deep linking preserves context automatically | +| Onboarding new users on iOS | Invite-code mode | Avoids the re-trigger step and reduces drop-off | +| Cross-device (e.g., desktop to phone) | Invite-code mode | Deep linking cannot bridge devices; a short code can | From f63d2c45b1d52bb7552fa993a46fa02b6da65ffb Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 10:27:36 -0700 Subject: [PATCH 15/32] docs: move verification-flows before invite-code in nav Verification flows establishes the problem that invite codes solve, so it should come first. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs.json b/docs.json index 23d3d2f..96ebba9 100644 --- a/docs.json +++ b/docs.json @@ -63,8 +63,8 @@ "group": "IDKit", "pages": [ "world-id/idkit/integrate", - "world-id/idkit/invite-code", "world-id/idkit/verification-flows", + "world-id/idkit/invite-code", "world-id/idkit/signatures", "world-id/idkit/build-with-llms", "world-id/idkit/onchain-verification", From 1d4b65ca93d638b65b9750fde6885d3d3599c15f Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 10:31:49 -0700 Subject: [PATCH 16/32] docs: reframe verification flows as user-determined paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hot/warm/cold flows aren't an RP choice — they describe what happens based on the user's state. Remove the "choosing the right approach" table and adjust framing accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index da16c14..13f72c4 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -5,7 +5,7 @@ description: "Understand the three verification paths a user can take when your "twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" --- -When your app requests a World ID proof, the user lands in one of three flows depending on their current state. Understanding these flows helps you design the right UX — especially for cold users who have never interacted with World ID before. +When your app requests a World ID proof, the user lands in one of three flows depending on their current state. Which flow runs is determined entirely by the user — whether they have World App installed and whether they already hold the credential you're requesting. ## Hot flow @@ -27,9 +27,9 @@ On Android, deep linking preserves context through the install process. After th ### iOS -iOS does not preserve context through the App Store install. This makes the cold flow higher friction and more prone to drop-off. There are two modes you can use. +iOS does not preserve context through the App Store install. This makes the cold flow higher friction and more prone to drop-off. -#### Default cold flow +#### Default behavior 1. Your app triggers an IDKit verification request. 2. The user is redirected to the App Store to download World App. @@ -41,7 +41,9 @@ iOS does not preserve context through the App Store install. This makes the cold The re-trigger in step 5 is necessary because iOS does not carry the original request context through the App Store install. Once the app is installed and onboarding is complete, the user needs to re-enter the verification flow from your app. -#### Invite-code mode +#### With invite-code mode + +If your app uses [invite-code mode](/world-id/idkit/invite-code), the re-trigger is avoided: 1. Your app triggers an IDKit invite-code request. 2. Your app displays a verify URL (typically as a QR) that embeds a 6-character invite code. @@ -52,17 +54,4 @@ The re-trigger in step 5 is necessary because iOS does not carry the original re 7. World App resolves the code, picks up the original verification context, and walks the user through credential enrollment. 8. The proof consent modal appears, the user approves, and the proof is returned to your app. -No re-trigger from your app is needed. The 6-character code persists across the App Store install — in the user's memory, clipboard, or screenshot — so once World App is installed and onboarded the user can resume the verification flow directly. This mode also covers cross-device scenarios (e.g., a desktop browser displaying the QR for the user's phone) where deep linking cannot carry context. - - - For integration details and code examples, see [Invite-code mode](/world-id/idkit/invite-code). - - -## Choosing the right approach - -| Scenario | Recommended flow | Why | -| --- | --- | --- | -| User likely already has the credential | Default (hot/warm) | Fastest path, no extra UX needed | -| Onboarding new users on Android | Default | Deep linking preserves context automatically | -| Onboarding new users on iOS | Invite-code mode | Avoids the re-trigger step and reduces drop-off | -| Cross-device (e.g., desktop to phone) | Invite-code mode | Deep linking cannot bridge devices; a short code can | +The 6-character code persists across the App Store install — in the user's memory, clipboard, or screenshot — so once World App is installed and onboarded the user can resume the verification flow without returning to your app first. This also covers cross-device scenarios (e.g., a desktop browser displaying the QR for the user's phone) where deep linking cannot carry context. From 19e1466dcf71b108b17ff065e1cf3c6d57acf467 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 10:32:29 -0700 Subject: [PATCH 17/32] docs: spell out the two factors that determine verification flow Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 13f72c4..6b528d2 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -5,7 +5,7 @@ description: "Understand the three verification paths a user can take when your "twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" --- -When your app requests a World ID proof, the user lands in one of three flows depending on their current state. Which flow runs is determined entirely by the user — whether they have World App installed and whether they already hold the credential you're requesting. +When your app requests a World ID proof, the user lands in one of three flows based on two factors: whether they have World App installed, and whether they already hold the credential you're requesting. ## Hot flow From 0644784953e61ec417f55f3f198fa778739e7fc6 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 10:33:59 -0700 Subject: [PATCH 18/32] docs: tweak verification flows intro wording Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 6b528d2..c6f731f 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -5,7 +5,7 @@ description: "Understand the three verification paths a user can take when your "twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" --- -When your app requests a World ID proof, the user lands in one of three flows based on two factors: whether they have World App installed, and whether they already hold the credential you're requesting. +When your app requests a World ID proof, the user is taken through one of three flows based on two factors: whether they have World App installed, and whether they already hold the credential you're requesting. ## Hot flow From 0983b4ccaa71a67b76aefc9adf4ab311ff9d31e7 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 10:39:01 -0700 Subject: [PATCH 19/32] docs: s/proof consent modal/proof consent/ Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index c6f731f..3bd883b 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -9,13 +9,13 @@ When your app requests a World ID proof, the user is taken through one of three ## Hot flow -The user already holds the credential your app is requesting. World App opens, displays a proof consent modal, and the user approves. The proof is returned to your app immediately. +The user already holds the credential your app is requesting. World App opens, displays a proof consent, and the user approves. The proof is returned to your app immediately. This is the fastest path — typically under 10 seconds. ## Warm flow -The user has World App installed but does not hold the credential your app is requesting. World App opens and walks the user through the relevant credential enrollment flow (e.g., PoH credential, Face credential, or NFC credential). Once the credential is issued, the proof consent modal appears and the user approves. The proof is returned to your app. +The user has World App installed but does not hold the credential your app is requesting. World App opens and walks the user through the relevant credential enrollment flow (e.g., PoH credential, Face credential, or NFC credential). Once the credential is issued, the proof consent appears and the user approves. The proof is returned to your app. ## Cold flow @@ -37,7 +37,7 @@ iOS does not preserve context through the App Store install. This makes the cold 4. The user returns to your app (manually or via a redirect). 5. Your app re-triggers the IDKit request. 6. World App opens and walks the user through credential enrollment. -7. The proof consent modal appears, the user approves, and the proof is returned to your app. +7. The proof consent appears, the user approves, and the proof is returned to your app. The re-trigger in step 5 is necessary because iOS does not carry the original request context through the App Store install. Once the app is installed and onboarding is complete, the user needs to re-enter the verification flow from your app. @@ -52,6 +52,6 @@ If your app uses [invite-code mode](/world-id/idkit/invite-code), the re-trigger 5. The user opens World App and completes account creation onboarding. 6. The user enters the invite code. 7. World App resolves the code, picks up the original verification context, and walks the user through credential enrollment. -8. The proof consent modal appears, the user approves, and the proof is returned to your app. +8. The proof consent appears, the user approves, and the proof is returned to your app. The 6-character code persists across the App Store install — in the user's memory, clipboard, or screenshot — so once World App is installed and onboarded the user can resume the verification flow without returning to your app first. This also covers cross-device scenarios (e.g., a desktop browser displaying the QR for the user's phone) where deep linking cannot carry context. From 7c2a9e00294f896328c0478271d0a9780fbf5382 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 10:47:14 -0700 Subject: [PATCH 20/32] docs: add App Clip section for iOS document enrollment cold flow Describes how the World App App Clip lets cold users begin NFC document scanning before installing the full app, with data persisted through a shared container. Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 3bd883b..13452b8 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -41,6 +41,21 @@ iOS does not preserve context through the App Store install. This makes the cold The re-trigger in step 5 is necessary because iOS does not carry the original request context through the App Store install. Once the app is installed and onboarding is complete, the user needs to re-enter the verification flow from your app. +#### With the App Clip (document enrollment) + +When your app requests a document credential (e.g., NFC passport or national ID), the World App [App Clip](https://developer.apple.com/app-clips/) lets the user begin enrollment before the full app is installed: + +1. Your app triggers an IDKit verification request for a document credential. +2. The user is presented with the World App App Clip. +3. The App Clip walks the user through country and document selection (passport, national ID, or My Number Card). +4. The user scans their document via NFC inside the App Clip. +5. The App Clip persists the scan data and selected document to a shared container, then prompts the user to install World App. +6. The user installs World App from the App Store. +7. World App reads the shared container, completes account creation onboarding, and finishes credential enrollment using the data collected by the App Clip. +8. The proof consent appears, the user approves, and the proof is returned to your app. + +Because the App Clip and World App share a data container, the document scan and selection survive the install — the user does not need to re-scan or re-select their document after installing the full app. + #### With invite-code mode If your app uses [invite-code mode](/world-id/idkit/invite-code), the re-trigger is avoided: From 80d3e74f0e4953d4eeee8c1b123ebd56db0d936c Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 10:49:58 -0700 Subject: [PATCH 21/32] docs: replace hot/warm/cold prose with summary table Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 13452b8..5000c7f 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -7,19 +7,15 @@ description: "Understand the three verification paths a user can take when your When your app requests a World ID proof, the user is taken through one of three flows based on two factors: whether they have World App installed, and whether they already hold the credential you're requesting. -## Hot flow - -The user already holds the credential your app is requesting. World App opens, displays a proof consent, and the user approves. The proof is returned to your app immediately. - -This is the fastest path — typically under 10 seconds. - -## Warm flow - -The user has World App installed but does not hold the credential your app is requesting. World App opens and walks the user through the relevant credential enrollment flow (e.g., PoH credential, Face credential, or NFC credential). Once the credential is issued, the proof consent appears and the user approves. The proof is returned to your app. +| Flow | World App installed | Has credential | What happens | +| --- | --- | --- | --- | +| Hot | Yes | Yes | World App opens, displays a proof consent, and the user approves. Typically under 10 seconds. | +| Warm | Yes | No | World App opens and walks the user through credential enrollment (e.g., PoH, Face, or NFC). Once issued, a proof consent appears and the user approves. | +| Cold | No | No | The user must install World App first. The experience differs by platform — see below. | ## Cold flow -The user does not have World App installed at all. This is the highest-friction path and differs significantly between platforms. +The cold flow is the highest-friction path and differs significantly between platforms. ### Android From cad6528bf4838e770b388d93e3451f816c441136 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 10:51:58 -0700 Subject: [PATCH 22/32] docs: rewrite iOS default cold flow without re-trigger assumption IDKit clients can direct users to install World App before issuing a request rather than re-triggering after install. Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 5000c7f..00e50ea 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -27,15 +27,12 @@ iOS does not preserve context through the App Store install. This makes the cold #### Default behavior -1. Your app triggers an IDKit verification request. -2. The user is redirected to the App Store to download World App. -3. The user opens World App and completes account creation onboarding. -4. The user returns to your app (manually or via a redirect). -5. Your app re-triggers the IDKit request. -6. World App opens and walks the user through credential enrollment. -7. The proof consent appears, the user approves, and the proof is returned to your app. - -The re-trigger in step 5 is necessary because iOS does not carry the original request context through the App Store install. Once the app is installed and onboarding is complete, the user needs to re-enter the verification flow from your app. +1. The user downloads World App from the App Store and completes account creation onboarding. +2. Your app triggers an IDKit verification request. +3. World App opens and walks the user through credential enrollment. +4. The proof consent appears, the user approves, and the proof is returned to your app. + +Because iOS does not preserve context through the App Store install, your app should direct the user to install World App before issuing the IDKit request. #### With the App Clip (document enrollment) From d6c9720fa2935e4762e0e2a92feea0a88521da6d Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 10:54:03 -0700 Subject: [PATCH 23/32] docs: add migration section to invite-code mode Covers the two changes needed when switching from the standard QR / connect-URL flow to invite-code mode. Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/invite-code.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx index 7309a90..920ddb3 100644 --- a/world-id/idkit/invite-code.mdx +++ b/world-id/idkit/invite-code.mdx @@ -140,6 +140,15 @@ Set `allow_legacy_proofs: true` because `selfieCheckLegacy` is a v3 ("legacy") p Polling and proof verification are unchanged from QR mode: the same `Status` values are emitted and the same `IDKitCompletionResult` is returned. Forward the result payload as-is to `POST https://developer.world.org/api/v4/verify/{rp_id}` — see [Verify the proof in your backend](/world-id/idkit/integrate#step-5-verify-the-proof-in-your-backend). +## Migrating from the QR / connect-URL flow + +If your app already uses the standard `IDKit.request(...)` flow, switching to invite-code mode requires two changes: + +1. **Swap the request entry point.** Replace `IDKit.request(...)` with `IDKit.requestWithInviteCode(...)` (JavaScript) or chain `.presetWithInviteCode(...)` instead of `.preset(...)` (Swift). The config object stays the same. +2. **Render the code instead of a QR.** The response includes a `code` and `expiresAt` instead of a `connectorURI`. Display the code to the user (optionally formatted as `ABC-DEF`) with a countdown driven by `expiresAt`. + +Everything else — RP signature generation, polling, proof verification, and nullifier storage — is unchanged. + ## SDK references For the full surface — entry points, hook results, and type signatures — see the per-SDK reference: From 69d48b0a4bb87b9df4d5e21899b8140218ccddc6 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 11:22:27 -0700 Subject: [PATCH 24/32] docs: merge invite-code page into verification-flows Moves all invite-code content (notes, code format, lifecycle, integration code, migration guide, SDK references) under the "With invite-code mode" section of verification-flows. Removes the standalone invite-code.mdx, adds a redirect, and updates all internal links. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 5 +- world-id/idkit/integrate.mdx | 2 +- world-id/idkit/invite-code.mdx | 158 -------------------------- world-id/idkit/javascript.mdx | 2 +- world-id/idkit/react.mdx | 2 +- world-id/idkit/swift.mdx | 2 +- world-id/idkit/verification-flows.mdx | 146 +++++++++++++++++++++++- 7 files changed, 153 insertions(+), 164 deletions(-) delete mode 100644 world-id/idkit/invite-code.mdx diff --git a/docs.json b/docs.json index 96ebba9..0f41e5c 100644 --- a/docs.json +++ b/docs.json @@ -64,7 +64,6 @@ "pages": [ "world-id/idkit/integrate", "world-id/idkit/verification-flows", - "world-id/idkit/invite-code", "world-id/idkit/signatures", "world-id/idkit/build-with-llms", "world-id/idkit/onchain-verification", @@ -768,6 +767,10 @@ "source": "/world-id/id/idkit-v4-preview", "destination": "/world-id/idkit/integrate" }, + { + "source": "/world-id/idkit/invite-code", + "destination": "/world-id/idkit/verification-flows#with-invite-code-mode" + }, { "source": "/world-id/credentials/presets", "destination": "/world-id/credentials/legacy-presets" diff --git a/world-id/idkit/integrate.mdx b/world-id/idkit/integrate.mdx index 998f1d3..835b1a5 100644 --- a/world-id/idkit/integrate.mdx +++ b/world-id/idkit/integrate.mdx @@ -14,7 +14,7 @@ To familiarize yourself with the core concepts of World ID, check out this [page Need to onboard users who don't yet hold the credential you're requesting? - See [Invite-code mode](/world-id/idkit/invite-code) — it gives the user a + See [Invite-code mode](/world-id/idkit/verification-flows#with-invite-code-mode) — it gives the user a short code to enter in World App, which then walks them through onboarding before returning the proof. Supported by World App on iOS only. diff --git a/world-id/idkit/invite-code.mdx b/world-id/idkit/invite-code.mdx deleted file mode 100644 index 920ddb3..0000000 --- a/world-id/idkit/invite-code.mdx +++ /dev/null @@ -1,158 +0,0 @@ ---- -title: "Invite-code mode" -description: "Onboard users into the World Network with a short 6-character invite code instead of a QR scan." -"og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" -"twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" ---- - -Invite-code mode displays a short 6-character code in your app that the user enters into World App. World App treats the code as an entry point to the in-app onboarding flows the user needs to complete in order to satisfy your IDKit request, then returns the proof. - - - Invite-code mode is only supported by World App on iOS. - - - - Only the `selfieCheckLegacy` preset is supported today. - - -## When to use it - -Use invite-code mode to expedite onboarding users who don't yet hold the credential you're requesting — World App walks them through getting it. It also fits cross-device flows, where typing a short code can be easier than scanning a QR. - -Use the standard [QR / connect-URL flow](/world-id/idkit/integrate) when the user already holds the credential and can verify on the same device, when you want a deep-link App Clip handoff, or when you want native in-app verification inside World App. - -## Code format - -- 6 characters, case-insensitive. -- Separators are ignored on entry. Display as `ABC-DEF` if you like; the canonical form has no separator. - -## Lifecycle - -- Codes expire after a short TTL (currently ten minutes). Pair the code with a countdown driven by `expiresAt`. -- Codes are one-shot — once redeemed, they cannot be reused. Re-running the request returns a fresh code with a fresh TTL. -- After the user redeems the code, your existing poll loop receives the proof exactly as it does in QR mode. - -## Integrate - -The setup steps that precede the request — creating an app in the Developer Portal and generating an RP signature on your backend — are identical to the [standard integration](/world-id/idkit/integrate). Only the request call and the rendered UI change. - - - -```typescript -import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core"; - -// `rpSig` is fetched from your backend — see the standard integration guide. -const request = await IDKit.requestWithInviteCode({ - app_id: "app_xxxxx", - action: "my-action", - rp_context: { - rp_id: "rp_xxxxx", - nonce: rpSig.nonce, - created_at: rpSig.created_at, - expires_at: rpSig.expires_at, - signature: rpSig.sig, - }, - allow_legacy_proofs: true, - environment: "production", -}).preset(selfieCheckLegacy({ signal: "user-123" })); - -const code = request.code; // 6-character canonical form -const expiresAt = request.expiresAt; // Unix seconds - -const completion = await request.pollUntilCompletion(); -``` - -```tsx -import { - IDKitInviteCodeRequestWidget, - selfieCheckLegacy, - type RpContext, -} from "@worldcoin/idkit"; - -const rpContext: RpContext = { - rp_id: "rp_xxxxx", - nonce: rpSig.nonce, - created_at: rpSig.created_at, - expires_at: rpSig.expires_at, - signature: rpSig.sig, -}; - - { - const response = await fetch("/api/verify-proof", { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ rp_id: rpContext.rp_id, idkitResponse: result }), - }); - if (!response.ok) throw new Error("Backend verification failed"); - }} - onSuccess={() => { - // Update app state here. - }} -/>; -``` - -```swift -import IDKit - -// `rpSig` is fetched from your backend — see the standard integration guide. -let rpContext = try RpContext( - rpId: "rp_xxxxx", - nonce: rpSig.nonce, - createdAt: rpSig.createdAt, - expiresAt: rpSig.expiresAt, - signature: rpSig.sig -) - -let config = IDKitRequestConfig( - appId: "app_xxxxx", - action: "my-action", - rpContext: rpContext, - allowLegacyProofs: true, - environment: .production -) - -let request = try IDKit.request(config: config) - .presetWithInviteCode(selfieCheckLegacy(signal: "user-123")) - -let code = request.code // 6-character canonical form -let expiresAt = request.expiresAt // Date - -let completion = await request.pollUntilCompletion() -``` - - - - - Generate `rp_context` in your backend only. Never expose your RP signing key in client code. - - -The config object is the same one you pass to `IDKit.request(...)` — invite-code mode introduces no new required fields. - -Set `allow_legacy_proofs: true` because `selfieCheckLegacy` is a v3 ("legacy") preset; the flag lets World App accept v3 proofs to satisfy the request. When invite-code mode adds support for v4 presets, set it to `false` for those flows. - -Polling and proof verification are unchanged from QR mode: the same `Status` values are emitted and the same `IDKitCompletionResult` is returned. Forward the result payload as-is to `POST https://developer.world.org/api/v4/verify/{rp_id}` — see [Verify the proof in your backend](/world-id/idkit/integrate#step-5-verify-the-proof-in-your-backend). - -## Migrating from the QR / connect-URL flow - -If your app already uses the standard `IDKit.request(...)` flow, switching to invite-code mode requires two changes: - -1. **Swap the request entry point.** Replace `IDKit.request(...)` with `IDKit.requestWithInviteCode(...)` (JavaScript) or chain `.presetWithInviteCode(...)` instead of `.preset(...)` (Swift). The config object stays the same. -2. **Render the code instead of a QR.** The response includes a `code` and `expiresAt` instead of a `connectorURI`. Display the code to the user (optionally formatted as `ABC-DEF`) with a countdown driven by `expiresAt`. - -Everything else — RP signature generation, polling, proof verification, and nullifier storage — is unchanged. - -## SDK references - -For the full surface — entry points, hook results, and type signatures — see the per-SDK reference: - -- [JavaScript](/world-id/idkit/javascript#invite-code-mode) -- [React](/world-id/idkit/react#invite-code-mode) -- [Swift](/world-id/idkit/swift#invite-code-mode) diff --git a/world-id/idkit/javascript.mdx b/world-id/idkit/javascript.mdx index a33f416..21369ed 100644 --- a/world-id/idkit/javascript.mdx +++ b/world-id/idkit/javascript.mdx @@ -105,7 +105,7 @@ if (!completion.success) { ## Invite-code mode -Use `IDKit.requestWithInviteCode(config)` to display a short code instead of a QR. Validation, the returned `Status` shape, and the poll loop are identical to `IDKit.request`. See [Invite-code mode](/world-id/idkit/invite-code) for when to use it. +Use `IDKit.requestWithInviteCode(config)` to display a short code instead of a QR. Validation, the returned `Status` shape, and the poll loop are identical to `IDKit.request`. See [Invite-code mode](/world-id/idkit/verification-flows#with-invite-code-mode) for when to use it. ```ts import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core"; diff --git a/world-id/idkit/react.mdx b/world-id/idkit/react.mdx index 8624157..ed57acc 100644 --- a/world-id/idkit/react.mdx +++ b/world-id/idkit/react.mdx @@ -118,7 +118,7 @@ Hook result fields: ## Invite-code mode -For invite-code flows, use `IDKitInviteCodeRequestWidget` (controlled) or `useIDKitInviteCodeRequest` (headless). Config matches `IDKitRequestWidget` / `useIDKitRequest` — invite-code mode adds no new required fields. See [Invite-code mode](/world-id/idkit/invite-code) for when to use it. +For invite-code flows, use `IDKitInviteCodeRequestWidget` (controlled) or `useIDKitInviteCodeRequest` (headless). Config matches `IDKitRequestWidget` / `useIDKitRequest` — invite-code mode adds no new required fields. See [Invite-code mode](/world-id/idkit/verification-flows#with-invite-code-mode) for when to use it. ```tsx import { IDKitInviteCodeRequestWidget, selfieCheckLegacy } from "@worldcoin/idkit"; diff --git a/world-id/idkit/swift.mdx b/world-id/idkit/swift.mdx index 93ea6b1..f3eecff 100644 --- a/world-id/idkit/swift.mdx +++ b/world-id/idkit/swift.mdx @@ -78,7 +78,7 @@ case .failure(let error): ## Invite-code mode -Use `presetWithInviteCode(_:)` on the builder to return an `IDKitInviteCodeRequest` instead of `IDKitRequest`. The polling surface is identical. See [Invite-code mode](/world-id/idkit/invite-code) for when to use it. +Use `presetWithInviteCode(_:)` on the builder to return an `IDKitInviteCodeRequest` instead of `IDKitRequest`. The polling surface is identical. See [Invite-code mode](/world-id/idkit/verification-flows#with-invite-code-mode) for when to use it. ```swift let request = try IDKit.request(config: config) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 00e50ea..5df7f1a 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -51,7 +51,15 @@ Because the App Clip and World App share a data container, the document scan and #### With invite-code mode -If your app uses [invite-code mode](/world-id/idkit/invite-code), the re-trigger is avoided: +Invite-code mode displays a short 6-character code in your app that the user enters into World App. World App treats the code as an entry point to the in-app onboarding flows the user needs to complete in order to satisfy your IDKit request, then returns the proof. + + + Invite-code mode is only supported by World App on iOS. + + + + Only the `selfieCheckLegacy` preset is supported today. + 1. Your app triggers an IDKit invite-code request. 2. Your app displays a verify URL (typically as a QR) that embeds a 6-character invite code. @@ -63,3 +71,139 @@ If your app uses [invite-code mode](/world-id/idkit/invite-code), the re-trigger 8. The proof consent appears, the user approves, and the proof is returned to your app. The 6-character code persists across the App Store install — in the user's memory, clipboard, or screenshot — so once World App is installed and onboarded the user can resume the verification flow without returning to your app first. This also covers cross-device scenarios (e.g., a desktop browser displaying the QR for the user's phone) where deep linking cannot carry context. + +##### Code format + +- 6 characters, case-insensitive. +- Separators are ignored on entry. Display as `ABC-DEF` if you like; the canonical form has no separator. + +##### Lifecycle + +- Codes expire after a short TTL (currently ten minutes). Pair the code with a countdown driven by `expiresAt`. +- Codes are one-shot — once redeemed, they cannot be reused. Re-running the request returns a fresh code with a fresh TTL. +- After the user redeems the code, your existing poll loop receives the proof exactly as it does in QR mode. + +##### Integrate + +The setup steps that precede the request — creating an app in the Developer Portal and generating an RP signature on your backend — are identical to the [standard integration](/world-id/idkit/integrate). Only the request call and the rendered UI change. + + + +```typescript +import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core"; + +// `rpSig` is fetched from your backend — see the standard integration guide. +const request = await IDKit.requestWithInviteCode({ + app_id: "app_xxxxx", + action: "my-action", + rp_context: { + rp_id: "rp_xxxxx", + nonce: rpSig.nonce, + created_at: rpSig.created_at, + expires_at: rpSig.expires_at, + signature: rpSig.sig, + }, + allow_legacy_proofs: true, + environment: "production", +}).preset(selfieCheckLegacy({ signal: "user-123" })); + +const code = request.code; // 6-character canonical form +const expiresAt = request.expiresAt; // Unix seconds + +const completion = await request.pollUntilCompletion(); +``` + +```tsx +import { + IDKitInviteCodeRequestWidget, + selfieCheckLegacy, + type RpContext, +} from "@worldcoin/idkit"; + +const rpContext: RpContext = { + rp_id: "rp_xxxxx", + nonce: rpSig.nonce, + created_at: rpSig.created_at, + expires_at: rpSig.expires_at, + signature: rpSig.sig, +}; + + { + const response = await fetch("/api/verify-proof", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ rp_id: rpContext.rp_id, idkitResponse: result }), + }); + if (!response.ok) throw new Error("Backend verification failed"); + }} + onSuccess={() => { + // Update app state here. + }} +/>; +``` + +```swift +import IDKit + +// `rpSig` is fetched from your backend — see the standard integration guide. +let rpContext = try RpContext( + rpId: "rp_xxxxx", + nonce: rpSig.nonce, + createdAt: rpSig.createdAt, + expiresAt: rpSig.expiresAt, + signature: rpSig.sig +) + +let config = IDKitRequestConfig( + appId: "app_xxxxx", + action: "my-action", + rpContext: rpContext, + allowLegacyProofs: true, + environment: .production +) + +let request = try IDKit.request(config: config) + .presetWithInviteCode(selfieCheckLegacy(signal: "user-123")) + +let code = request.code // 6-character canonical form +let expiresAt = request.expiresAt // Date + +let completion = await request.pollUntilCompletion() +``` + + + + + Generate `rp_context` in your backend only. Never expose your RP signing key in client code. + + +The config object is the same one you pass to `IDKit.request(...)` — invite-code mode introduces no new required fields. + +Set `allow_legacy_proofs: true` because `selfieCheckLegacy` is a v3 ("legacy") preset; the flag lets World App accept v3 proofs to satisfy the request. When invite-code mode adds support for v4 presets, set it to `false` for those flows. + +Polling and proof verification are unchanged from QR mode: the same `Status` values are emitted and the same `IDKitCompletionResult` is returned. Forward the result payload as-is to `POST https://developer.world.org/api/v4/verify/{rp_id}` — see [Verify the proof in your backend](/world-id/idkit/integrate#step-5-verify-the-proof-in-your-backend). + +##### Migrating from the QR / connect-URL flow + +If your app already uses the standard `IDKit.request(...)` flow, switching to invite-code mode requires two changes: + +1. **Swap the request entry point.** Replace `IDKit.request(...)` with `IDKit.requestWithInviteCode(...)` (JavaScript) or chain `.presetWithInviteCode(...)` instead of `.preset(...)` (Swift). The config object stays the same. +2. **Render the code instead of a QR.** The response includes a `code` and `expiresAt` instead of a `connectorURI`. Display the code to the user (optionally formatted as `ABC-DEF`) with a countdown driven by `expiresAt`. + +Everything else — RP signature generation, polling, proof verification, and nullifier storage — is unchanged. + +##### SDK references + +For the full surface — entry points, hook results, and type signatures — see the per-SDK reference: + +- [JavaScript](/world-id/idkit/javascript#invite-code-mode) +- [React](/world-id/idkit/react#invite-code-mode) +- [Swift](/world-id/idkit/swift#invite-code-mode) From 446b4dba6bd1d1ce3684e625ea8d7835fc781a17 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 11:26:46 -0700 Subject: [PATCH 25/32] docs: trim invite-code persistence detail Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 5df7f1a..3201942 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -70,7 +70,7 @@ Invite-code mode displays a short 6-character code in your app that the user ent 7. World App resolves the code, picks up the original verification context, and walks the user through credential enrollment. 8. The proof consent appears, the user approves, and the proof is returned to your app. -The 6-character code persists across the App Store install — in the user's memory, clipboard, or screenshot — so once World App is installed and onboarded the user can resume the verification flow without returning to your app first. This also covers cross-device scenarios (e.g., a desktop browser displaying the QR for the user's phone) where deep linking cannot carry context. +The 6-character code persists across the App Store install, so once World App is installed and onboarded the user can resume the verification flow without returning to your app first. This also covers cross-device scenarios (e.g., a desktop browser displaying the QR for the user's phone) where deep linking cannot carry context. ##### Code format From 827158ec91bb5ca9a8c0ec8a1538daab76f3ef21 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 11:28:24 -0700 Subject: [PATCH 26/32] docs: fix invite-code flow step 2 to reflect current behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The client opens an IDKit-provided URL; the world.org/verify landing page displays the invite code and QR — the client no longer renders these directly. Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 3201942..5f24760 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -62,13 +62,12 @@ Invite-code mode displays a short 6-character code in your app that the user ent 1. Your app triggers an IDKit invite-code request. -2. Your app displays a verify URL (typically as a QR) that embeds a 6-character invite code. -3. The user opens the URL on their phone. The `world.org/verify` landing page surfaces the code and a download link. -4. The user downloads World App from the App Store. -5. The user opens World App and completes account creation onboarding. -6. The user enters the invite code. -7. World App resolves the code, picks up the original verification context, and walks the user through credential enrollment. -8. The proof consent appears, the user approves, and the proof is returned to your app. +2. Your app opens the URL that IDKit provides. The `world.org/verify` landing page displays the invite code and a QR code for the request. +3. The user downloads World App from the App Store. +4. The user opens World App and completes account creation onboarding. +5. The user enters the invite code. +6. World App resolves the code, picks up the original verification context, and walks the user through credential enrollment. +7. The proof consent appears, the user approves, and the proof is returned to your app. The 6-character code persists across the App Store install, so once World App is installed and onboarded the user can resume the verification flow without returning to your app first. This also covers cross-device scenarios (e.g., a desktop browser displaying the QR for the user's phone) where deep linking cannot carry context. From 1bb9188aca58f0cb5d8bbfe5caca20f942e357dd Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 11:38:03 -0700 Subject: [PATCH 27/32] docs: reference hot/warm flow in cold flow steps Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 5f24760..6c0e58b 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -29,7 +29,7 @@ iOS does not preserve context through the App Store install. This makes the cold 1. The user downloads World App from the App Store and completes account creation onboarding. 2. Your app triggers an IDKit verification request. -3. World App opens and walks the user through credential enrollment. +3. World App opens and takes the user through a hot or warm flow. 4. The proof consent appears, the user approves, and the proof is returned to your app. Because iOS does not preserve context through the App Store install, your app should direct the user to install World App before issuing the IDKit request. From e41250c5383562bc98a0d2dd866c599037e8128a Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 11:40:30 -0700 Subject: [PATCH 28/32] docs: soften cold flow intro wording Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 6c0e58b..b437dd5 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -15,7 +15,7 @@ When your app requests a World ID proof, the user is taken through one of three ## Cold flow -The cold flow is the highest-friction path and differs significantly between platforms. +The cold flow requires the most steps and works differently on each platform. ### Android From eb527f6131e3f7f3fbcd8a32e7ecc4ea14c284db Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 12:07:30 -0700 Subject: [PATCH 29/32] docs: move invite-code migration to SDK references as code examples Replace the prose migration section in verification-flows with before/after code snippets in the JavaScript, React, and Swift SDK reference pages. Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/javascript.mdx | 31 ++++++++++++++++++++++ world-id/idkit/react.mdx | 38 +++++++++++++++++++++++++++ world-id/idkit/swift.mdx | 23 ++++++++++++++++ world-id/idkit/verification-flows.mdx | 9 ------- 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/world-id/idkit/javascript.mdx b/world-id/idkit/javascript.mdx index 21369ed..a7e9dfb 100644 --- a/world-id/idkit/javascript.mdx +++ b/world-id/idkit/javascript.mdx @@ -130,6 +130,37 @@ const completion = await request.pollUntilCompletion(); - `pollOnce()` - `pollUntilCompletion({ pollInterval, timeout })` +### Migrating from QR / connect-URL + +```ts +// Before — QR / connect-URL flow +const request = await IDKit.request({ + app_id: "app_xxxxx", + action: "my-action", + rp_context, + allow_legacy_proofs: true, +}).preset(orbLegacy({ signal: "user-123" })); + +const connectorURI = request.connectorURI; // render as QR +const completion = await request.pollUntilCompletion(); +``` + +```ts +// After — invite-code mode +const request = await IDKit.requestWithInviteCode({ + app_id: "app_xxxxx", + action: "my-action", + rp_context, + allow_legacy_proofs: true, +}).preset(selfieCheckLegacy({ signal: "user-123" })); + +const code = request.code; // display to user +const expiresAt = request.expiresAt; // drive a countdown +const completion = await request.pollUntilCompletion(); +``` + +The config object is unchanged. Replace `connectorURI` (QR) with `code` and `expiresAt` in your UI. Polling, proof verification, and nullifier storage stay the same. + ## Server-side helpers Use subpath exports on your backend: diff --git a/world-id/idkit/react.mdx b/world-id/idkit/react.mdx index ed57acc..2f96234 100644 --- a/world-id/idkit/react.mdx +++ b/world-id/idkit/react.mdx @@ -149,6 +149,44 @@ import { IDKitInviteCodeRequestWidget, selfieCheckLegacy } from "@worldcoin/idki - `result` - `errorCode` +### Migrating from QR / connect-URL + +```tsx +// Before — QR / connect-URL widget +import { IDKitRequestWidget, orbLegacy } from "@worldcoin/idkit"; + +; +``` + +```tsx +// After — invite-code widget +import { IDKitInviteCodeRequestWidget, selfieCheckLegacy } from "@worldcoin/idkit"; + +; +``` + +Swap the component and preset. Props, callbacks, and backend verification stay the same. The headless equivalent is `useIDKitInviteCodeRequest` in place of `useIDKitRequest`. + ## Presets React hooks/widgets take `preset` directly in config. diff --git a/world-id/idkit/swift.mdx b/world-id/idkit/swift.mdx index f3eecff..5c8ed56 100644 --- a/world-id/idkit/swift.mdx +++ b/world-id/idkit/swift.mdx @@ -89,6 +89,29 @@ let expiresAt = request.expiresAt // Date let completion = await request.pollUntilCompletion() ``` +### Migrating from QR / connect-URL + +```swift +// Before — QR / connect-URL flow +let request = try IDKit.request(config: config) + .preset(orbLegacy(signal: "user-123")) + +let connectURL = request.connectorURL // render as QR +let completion = await request.pollUntilCompletion() +``` + +```swift +// After — invite-code mode +let request = try IDKit.request(config: config) + .presetWithInviteCode(selfieCheckLegacy(signal: "user-123")) + +let code = request.code // display to user +let expiresAt = request.expiresAt // drive a countdown +let completion = await request.pollUntilCompletion() +``` + +The config object is unchanged. Replace `connectorURL` (QR) with `code` and `expiresAt` in your UI. Polling, proof verification, and nullifier storage stay the same. + `IDKitInviteCodeRequest` exposes: - `code: String` diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index b437dd5..bd04c83 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -190,15 +190,6 @@ Set `allow_legacy_proofs: true` because `selfieCheckLegacy` is a v3 ("legacy") p Polling and proof verification are unchanged from QR mode: the same `Status` values are emitted and the same `IDKitCompletionResult` is returned. Forward the result payload as-is to `POST https://developer.world.org/api/v4/verify/{rp_id}` — see [Verify the proof in your backend](/world-id/idkit/integrate#step-5-verify-the-proof-in-your-backend). -##### Migrating from the QR / connect-URL flow - -If your app already uses the standard `IDKit.request(...)` flow, switching to invite-code mode requires two changes: - -1. **Swap the request entry point.** Replace `IDKit.request(...)` with `IDKit.requestWithInviteCode(...)` (JavaScript) or chain `.presetWithInviteCode(...)` instead of `.preset(...)` (Swift). The config object stays the same. -2. **Render the code instead of a QR.** The response includes a `code` and `expiresAt` instead of a `connectorURI`. Display the code to the user (optionally formatted as `ABC-DEF`) with a countdown driven by `expiresAt`. - -Everything else — RP signature generation, polling, proof verification, and nullifier storage — is unchanged. - ##### SDK references For the full surface — entry points, hook results, and type signatures — see the per-SDK reference: From af1ebe4632f029ffffe2eb0b2f349370f854187d Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 12:38:42 -0700 Subject: [PATCH 30/32] docs: address PR #111 review comments - Introduce deferred deep linking concept in cold flow section - Fix App Clip flow: selection only, NFC scan happens in full app - Update invite-code note to explain why it's iOS-only - Remove world.org/verify reference from landing page step - Remove code format section and countdown mention from lifecycle - Move integrate.mdx Note to step 4, reframe as verification flows intro - Add mermaid sequence diagrams for all cold flow variants Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/integrate.mdx | 14 ++-- world-id/idkit/verification-flows.mdx | 101 ++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 23 deletions(-) diff --git a/world-id/idkit/integrate.mdx b/world-id/idkit/integrate.mdx index 835b1a5..86031d6 100644 --- a/world-id/idkit/integrate.mdx +++ b/world-id/idkit/integrate.mdx @@ -12,13 +12,6 @@ To familiarize yourself with the core concepts of World ID, check out this [page > Tip: To integrate faster, give your coding agent the [Build with LLMs prompt](/world-id/idkit/build-with-llms) — copy it once, paste into Claude, Cursor, or any AI coding assistant. - - Need to onboard users who don't yet hold the credential you're requesting? - See [Invite-code mode](/world-id/idkit/verification-flows#with-invite-code-mode) — it gives the user a - short code to enter in World App, which then walks them through onboarding - before returning the proof. Supported by World App on iOS only. - - # Step 1: Install IDKit Make sure you're using the latest `4.x` version. @@ -123,6 +116,13 @@ func handleRPSignature(w http.ResponseWriter, r *http.Request) { + + The steps below cover the standard request flow. Depending on whether the user + already has World App and the credential you're requesting, they may be taken + through a different experience. See [Verification flows](/world-id/idkit/verification-flows) + for details. + + # Step 4: Generate the connect URL and collect proof You can test during development using the [simulator](https://simulator.worldcoin.org/) and setting `environment` to `"staging"`. diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index bd04c83..474704a 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -15,15 +15,32 @@ When your app requests a World ID proof, the user is taken through one of three ## Cold flow -The cold flow requires the most steps and works differently on each platform. +The cold flow requires the most steps and works differently on each platform because of how each platform handles **deferred deep linking** — the ability to preserve a link's context through an app store install so the app can act on it at first launch. ### Android -On Android, deep linking preserves context through the install process. After the user downloads World App from the Play Store and opens it, the original verification context is carried forward — World App picks up where the user left off and routes them directly into the credential enrollment flow. No re-trigger from your app is needed. +Android supports deferred deep linking through the Play Store. After the user downloads World App, the original verification context is carried forward at first launch — World App picks up where the user left off and routes them directly into credential enrollment. No re-trigger from your app is needed. + +```mermaid +sequenceDiagram + participant App as Your App + participant User + participant Play as Play Store + participant WA as World App + + App->>User: IDKit request (connect URL / QR) + User->>Play: Download World App + Play-->>WA: Deferred deep link preserved + WA->>User: Account onboarding + WA->>User: Credential enrollment + WA->>User: Proof consent + User->>WA: Approve + WA-->>App: Proof returned +``` ### iOS -iOS does not preserve context through the App Store install. This makes the cold flow higher friction and more prone to drop-off. +iOS does not support deferred deep linking through the App Store. The original verification context is lost during install, so additional mechanisms are needed to resume the flow. #### Default behavior @@ -34,6 +51,22 @@ iOS does not preserve context through the App Store install. This makes the cold Because iOS does not preserve context through the App Store install, your app should direct the user to install World App before issuing the IDKit request. +```mermaid +sequenceDiagram + participant App as Your App + participant User + participant Store as App Store + participant WA as World App + + App->>User: Prompt to install World App + User->>Store: Download World App + WA->>User: Account onboarding + User->>App: Return to your app + App->>User: IDKit request (connect URL / QR) + WA->>User: Hot or warm flow + WA-->>App: Proof returned +``` + #### With the App Clip (document enrollment) When your app requests a document credential (e.g., NFC passport or national ID), the World App [App Clip](https://developer.apple.com/app-clips/) lets the user begin enrollment before the full app is installed: @@ -41,20 +74,41 @@ When your app requests a document credential (e.g., NFC passport or national ID) 1. Your app triggers an IDKit verification request for a document credential. 2. The user is presented with the World App App Clip. 3. The App Clip walks the user through country and document selection (passport, national ID, or My Number Card). -4. The user scans their document via NFC inside the App Clip. -5. The App Clip persists the scan data and selected document to a shared container, then prompts the user to install World App. -6. The user installs World App from the App Store. -7. World App reads the shared container, completes account creation onboarding, and finishes credential enrollment using the data collected by the App Clip. -8. The proof consent appears, the user approves, and the proof is returned to your app. +4. The App Clip persists the selection to a shared container, then prompts the user to install World App. +5. The user installs World App from the App Store. +6. World App reads the shared container, completes account creation onboarding, and walks the user through credential enrollment (including NFC scanning). +7. The proof consent appears, the user approves, and the proof is returned to your app. -Because the App Clip and World App share a data container, the document scan and selection survive the install — the user does not need to re-scan or re-select their document after installing the full app. +Because the App Clip and World App share a data container, the user's country and document selection survive the install. + +```mermaid +sequenceDiagram + participant App as Your App + participant User + participant AC as App Clip + participant Store as App Store + participant WA as World App + + App->>User: IDKit request (document credential) + User->>AC: Presented with App Clip + AC->>User: Country & document selection + AC->>AC: Persist selection to shared container + AC->>User: Prompt to install World App + User->>Store: Download World App + WA->>WA: Read shared container + WA->>User: Account onboarding + WA->>User: Credential enrollment (incl. NFC scan) + WA->>User: Proof consent + User->>WA: Approve + WA-->>App: Proof returned +``` #### With invite-code mode Invite-code mode displays a short 6-character code in your app that the user enters into World App. World App treats the code as an entry point to the in-app onboarding flows the user needs to complete in order to satisfy your IDKit request, then returns the proof. - Invite-code mode is only supported by World App on iOS. + Invite-code mode only applies to iOS as Android preserves deferred deep linking context via the Play Store. @@ -62,7 +116,7 @@ Invite-code mode displays a short 6-character code in your app that the user ent 1. Your app triggers an IDKit invite-code request. -2. Your app opens the URL that IDKit provides. The `world.org/verify` landing page displays the invite code and a QR code for the request. +2. Your app opens the URL that IDKit provides. A landing page displays the invite code and a QR code for the request. 3. The user downloads World App from the App Store. 4. The user opens World App and completes account creation onboarding. 5. The user enters the invite code. @@ -71,14 +125,29 @@ Invite-code mode displays a short 6-character code in your app that the user ent The 6-character code persists across the App Store install, so once World App is installed and onboarded the user can resume the verification flow without returning to your app first. This also covers cross-device scenarios (e.g., a desktop browser displaying the QR for the user's phone) where deep linking cannot carry context. -##### Code format - -- 6 characters, case-insensitive. -- Separators are ignored on entry. Display as `ABC-DEF` if you like; the canonical form has no separator. +```mermaid +sequenceDiagram + participant App as Your App + participant User + participant LP as Landing Page + participant Store as App Store + participant WA as World App + + App->>LP: Open IDKit-provided URL + LP->>User: Display invite code + QR + User->>Store: Download World App + WA->>User: Account onboarding + User->>WA: Enter invite code + WA->>WA: Resolve code & restore context + WA->>User: Credential enrollment + WA->>User: Proof consent + User->>WA: Approve + WA-->>App: Proof returned +``` ##### Lifecycle -- Codes expire after a short TTL (currently ten minutes). Pair the code with a countdown driven by `expiresAt`. +- Codes expire after a short TTL (currently ten minutes). - Codes are one-shot — once redeemed, they cannot be reused. Re-running the request returns a fresh code with a fresh TTL. - After the user redeems the code, your existing poll loop receives the proof exactly as it does in QR mode. From 715820bc3ed8ce7f62582c985322be3019b41782 Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 12:40:48 -0700 Subject: [PATCH 31/32] docs: remove App Clip section from verification flows Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/verification-flows.mdx | 36 --------------------------- 1 file changed, 36 deletions(-) diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 474704a..0c6b03e 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -67,42 +67,6 @@ sequenceDiagram WA-->>App: Proof returned ``` -#### With the App Clip (document enrollment) - -When your app requests a document credential (e.g., NFC passport or national ID), the World App [App Clip](https://developer.apple.com/app-clips/) lets the user begin enrollment before the full app is installed: - -1. Your app triggers an IDKit verification request for a document credential. -2. The user is presented with the World App App Clip. -3. The App Clip walks the user through country and document selection (passport, national ID, or My Number Card). -4. The App Clip persists the selection to a shared container, then prompts the user to install World App. -5. The user installs World App from the App Store. -6. World App reads the shared container, completes account creation onboarding, and walks the user through credential enrollment (including NFC scanning). -7. The proof consent appears, the user approves, and the proof is returned to your app. - -Because the App Clip and World App share a data container, the user's country and document selection survive the install. - -```mermaid -sequenceDiagram - participant App as Your App - participant User - participant AC as App Clip - participant Store as App Store - participant WA as World App - - App->>User: IDKit request (document credential) - User->>AC: Presented with App Clip - AC->>User: Country & document selection - AC->>AC: Persist selection to shared container - AC->>User: Prompt to install World App - User->>Store: Download World App - WA->>WA: Read shared container - WA->>User: Account onboarding - WA->>User: Credential enrollment (incl. NFC scan) - WA->>User: Proof consent - User->>WA: Approve - WA-->>App: Proof returned -``` - #### With invite-code mode Invite-code mode displays a short 6-character code in your app that the user enters into World App. World App treats the code as an entry point to the in-app onboarding flows the user needs to complete in order to satisfy your IDKit request, then returns the proof. From 16bb67236d7d76c80102f31b7604a64ab898494e Mon Sep 17 00:00:00 2001 From: Sean Olszewski Date: Wed, 6 May 2026 12:48:40 -0700 Subject: [PATCH 32/32] docs: fix inconsistent title casing in IDKit section Standardize to title case: "Verification Flows", "On-chain Verification", and "JavaScript". Co-Authored-By: Claude Opus 4.6 (1M context) --- world-id/idkit/javascript.mdx | 2 +- world-id/idkit/onchain-verification.mdx | 2 +- world-id/idkit/verification-flows.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/world-id/idkit/javascript.mdx b/world-id/idkit/javascript.mdx index a7e9dfb..2e7d823 100644 --- a/world-id/idkit/javascript.mdx +++ b/world-id/idkit/javascript.mdx @@ -1,5 +1,5 @@ --- -title: "Javascript" +title: "JavaScript" description: "Reference for `@worldcoin/idkit-core`" "og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" "twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" diff --git a/world-id/idkit/onchain-verification.mdx b/world-id/idkit/onchain-verification.mdx index f10a369..2074fb9 100644 --- a/world-id/idkit/onchain-verification.mdx +++ b/world-id/idkit/onchain-verification.mdx @@ -1,5 +1,5 @@ --- -title: "On-chain verification" +title: "On-chain Verification" description: "Verify World ID proofs directly in Solidity for web3-native flows." "og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" "twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 0c6b03e..0db3e4c 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -1,5 +1,5 @@ --- -title: "Verification flows" +title: "Verification Flows" description: "Understand the three verification paths a user can take when your app requests a World ID proof — hot, warm, and cold." "og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" "twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png"