diff --git a/openapi/developer-portal.json b/openapi/developer-portal.json index 3b6eb50..b1fbca8 100644 --- a/openapi/developer-portal.json +++ b/openapi/developer-portal.json @@ -732,7 +732,7 @@ "action": "my_action", "responses": [ { - "identifier": "orb", + "identifier": "proof_of_human", "issuer_schema_id": 1, "nullifier": "0x2bf8406809dcefb1486dadc96c0a897db9bab002053054cf64272db512c6fbd8", "expires_at_min": 49012345, @@ -747,10 +747,10 @@ "value": { "protocol_version": "4.0", "nonce": "0xabc123", - "session_id": "1234567890123456789", + "session_id": "session_5f3a9c2e...8d1b0a", "responses": [ { - "identifier": "orb", + "identifier": "proof_of_human", "issuer_schema_id": 1, "session_nullifier": ["0xaaa", "0xbbb"], "expires_at_min": 49012345, @@ -1302,6 +1302,10 @@ "items": { "$ref": "#/components/schemas/VerifyV4ResponseItemV3" } + }, + "user_presence_completed": { + "type": "boolean", + "description": "Whether World App completed the requested user-presence check. IDKit always sends it; treat a missing value as false." } } }, @@ -1335,6 +1339,10 @@ "items": { "$ref": "#/components/schemas/VerifyV4ResponseItemV4" } + }, + "user_presence_completed": { + "type": "boolean", + "description": "Whether World App completed the requested user-presence check. IDKit always sends it; treat a missing value as false." } } }, @@ -1352,7 +1360,9 @@ "type": "string" }, "session_id": { - "type": "string" + "type": "string", + "description": "Opaque session ID in the format session_<128 hex characters>.", + "example": "session_5f3a9c2e...8d1b0a" }, "environment": { "type": "string", @@ -1365,6 +1375,10 @@ "items": { "$ref": "#/components/schemas/VerifyV4SessionResponseItem" } + }, + "user_presence_completed": { + "type": "boolean", + "description": "Whether World App completed the requested user-presence check. IDKit always sends it; treat a missing value as false." } } }, @@ -1422,9 +1436,6 @@ "expires_at_min": { "type": "integer" }, - "credential_genesis_issued_at_min": { - "type": "integer" - }, "proof": { "type": "array", "minItems": 5, @@ -1468,9 +1479,6 @@ "expires_at_min": { "type": "integer" }, - "credential_genesis_issued_at_min": { - "type": "integer" - }, "proof": { "type": "array", "minItems": 5, @@ -1524,7 +1532,9 @@ "enum": ["production", "staging"] }, "session_id": { - "type": "string" + "type": "string", + "description": "Opaque session ID in the format session_<128 hex characters>.", + "example": "session_5f3a9c2e...8d1b0a" }, "results": { "type": "array", diff --git a/snippets/idkit-response.mdx b/snippets/idkit-response.mdx index db0126a..200baa1 100644 --- a/snippets/idkit-response.mdx +++ b/snippets/idkit-response.mdx @@ -13,7 +13,8 @@ "merkle_root": "0x0abc123...root_hash", "nullifier": "0x04e5f6...nullifier_hash" } - ] + ], + "user_presence_completed": false } ``` @@ -25,14 +26,15 @@ "environment": "production", "responses": [ { - "identifier": "orb", + "identifier": "proof_of_human", "signal_hash": "0x0", "proof": ["0x1a2b...", "0x3c4d...", "0x5e6f...", "0x7a8b...", "0x9c0d..."], "nullifier": "0x04e5f6...rp_scoped_nullifier", "issuer_schema_id": 1, "expires_at_min": 1756166400 } - ] + ], + "user_presence_completed": false } ``` @@ -40,18 +42,19 @@ { "protocol_version": "4.0", "nonce": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", - "session_id": "ses_abc123", + "session_id": "session_5f3a9c2e...8d1b0a", "environment": "production", "responses": [ { - "identifier": "orb", + "identifier": "proof_of_human", "signal_hash": "0x0", "proof": ["0x1a2b...", "0x3c4d...", "0x5e6f...", "0x7a8b...", "0x9c0d..."], "session_nullifier": ["0x04e5f6...session_nullifier", "0x07a8b9...generated_action"], "issuer_schema_id": 1, "expires_at_min": 1756166400 } - ] + ], + "user_presence_completed": false } ``` diff --git a/world-id/4-0-migration.mdx b/world-id/4-0-migration.mdx index e4ace6e..a404486 100644 --- a/world-id/4-0-migration.mdx +++ b/world-id/4-0-migration.mdx @@ -262,7 +262,7 @@ export async function createSession() { const request = await IDKit.createSession({ app_id: APP_ID, rp_context: rpContext, - }).constraints(any(CredentialRequest("orb"))); + }).constraints(any(CredentialRequest("proof_of_human"))); // Web only: render this QR URL const qrUrl = request.connectorURI; @@ -291,7 +291,7 @@ export async function proveSession(sessionId) { const request = await IDKit.proveSession(sessionId, { app_id: APP_ID, rp_context: rpContext, - }).constraints(any(CredentialRequest("orb"))); + }).constraints(any(CredentialRequest("proof_of_human"))); const completion = await request.pollUntilCompletion({ timeout: 120000 }); if (!completion.success) throw new Error(completion.error); diff --git a/world-id/SKILL.md b/world-id/SKILL.md index 2082943..02c6b62 100644 --- a/world-id/SKILL.md +++ b/world-id/SKILL.md @@ -93,11 +93,11 @@ The credential decides what the user proves. Get this explicit before scaffoldin | Preset | What it proves | Use it for | |---|---|---| -| **`orbLegacy`** — Proof of Human (flagship) | The user is a unique person, biometrically verified at an Orb | Sybil resistance, airdrops, one-vote-per-human, gated signups. **The default if the user said "proof of human" or "verify a real human."** | -| **`secureDocumentLegacy`** — Passport | The user holds a valid government passport (NFC-verified) | Higher-assurance flows where you need document-grade identity (regulated apps, age-gating, KYC-adjacent). | +| **`proofOfHuman`** — Proof of Human (flagship) | The user is a unique person, biometrically verified at an Orb | Sybil resistance, airdrops, one-vote-per-human, gated signups. **The default if the user said "proof of human" or "verify a real human."** | +| **`passport`** — Passport | The user holds a valid government passport (NFC-verified) | Higher-assurance flows where you need document-grade identity (regulated apps, age-gating, KYC-adjacent). | | **`selfieCheckLegacy`** — Selfie check | A liveness selfie signal | Lower-assurance "is a human in front of the camera" — friction/bot deterrence without the full Orb requirement. | -**DO NOT default to `orbLegacy` if the user said "passport" or "verify their ID"** — that's `secureDocumentLegacy`. **DO NOT default to `orbLegacy` if the user said "selfie" or "liveness"** — that's `selfieCheckLegacy`. When in doubt, ask one question. +**DO NOT default to `proofOfHuman` if the user said "passport" or "verify their ID"** — that's `passport`. **DO NOT default to `proofOfHuman` if the user said "selfie" or "liveness"** — that's `selfieCheckLegacy`. When in doubt, ask one question. Other legacy presets exist (`documentLegacy`, `deviceLegacy`); reach for them only when the user asks specifically. For sign-in / session reuse across visits, use the v4 **session** flow instead of a uniqueness preset (see the integrate doc). @@ -137,10 +137,11 @@ Surface these proactively when you see the corresponding symptom — don't make |---|---|---| | World App shows "action not found" or QR scan does nothing | Action wasn't created in the environment IDKit is pointing at | Create the missing action with `create_world_id_action` (`environment: "production"` for real devices, `"staging"` for simulator). Confirm `NEXT_PUBLIC_WLD_ENVIRONMENT` matches. | | `/api/v4/verify/{rp_id}` returns `invalid_proof` or `verification_failed` | Often staging/production env mismatch, or proof was mutated before forward | Re-check Step E. Forward the proof JSON byte-for-byte without re-encoding fields. | +| Verification fails in JS/React and the error code alone isn't enough | Need transport/payload diagnostics | Read `getDebugReport()` (or the `onError` `debugReport` arg) — it carries `transport`, `request_id`, request/response payloads, and World App `mini_app` channel info. JS SDKs only. | | `/api/v4/verify/{rp_id}` returns `not_registered` or 4xx with `rp` errors | On-chain registration is still `pending` | Poll `get_world_id_registration_status` until `production_status: registered` and `staging_status: registered`. Don't ship until both. | | Signing key is gone (lost `.env`, never persisted) | `signing_key.private_key` is returned exactly once at create/rotate time | `rotate_world_id_signing_key`, persist the new key immediately, redeploy. The old key is invalidated. | | Duplicate-verification (user submits the same proof twice) | Expected — that's what nullifiers prevent | Reject on the unique-constraint violation. **Do not** "helpfully" upsert. | -| TypeScript: `Property 'allow_legacy_proofs' is missing` | v4 requires this prop on `IDKitRequestWidget` | Add `allow_legacy_proofs={true}` for `orbLegacy` and other legacy presets. | +| TypeScript: `Property 'allow_legacy_proofs' is missing` | v4 requires this prop on `IDKitRequestWidget` | Add `allow_legacy_proofs={true}` for `proofOfHuman`, `orbLegacy`, and other legacy/fallback presets. | | TypeScript: import errors for `IRpContext` / `ISuccessResult` | v3 type names — removed in v4 | Use `RpContext` / `IDKitResult`. No `I` prefix. | | Widget never opens | Treating `IDKitRequestWidget` as a render-prop / function-as-child component | v4 widget is **controlled** — pass `open` and `onOpenChange` props. There is no child function. | | `npm install` fails: `No matching version found for @worldcoin/idkit@^2.x` (or 3.x) | Stale code sample | Pin `^4.x`. Run `npm view @worldcoin/idkit version` to confirm the latest. | diff --git a/world-id/from-idkit-standalone.mdx b/world-id/from-idkit-standalone.mdx index fc47f4b..db01a46 100644 --- a/world-id/from-idkit-standalone.mdx +++ b/world-id/from-idkit-standalone.mdx @@ -9,7 +9,7 @@ description: "Migrate from @worldcoin/idkit-standalone to @worldcoin/idkit-core. This guide helps you migrate from `@worldcoin/idkit-standalone` to `@worldcoin/idkit-core`. The `idkit-standalone` package has been discontinued. If you used it for a vanilla JavaScript or custom QR flow, migrate to `@worldcoin/idkit-core`. -¡ + ## Migration Checklist 1. Enable World ID 4.0 in the [Developer Portal](https://developer.world.org) and keep your `app_id`, `rp_id`, and server-only `signing_key`. diff --git a/world-id/idkit/build-with-llms.mdx b/world-id/idkit/build-with-llms.mdx index 2e37ecb..059036b 100644 --- a/world-id/idkit/build-with-llms.mdx +++ b/world-id/idkit/build-with-llms.mdx @@ -4,10 +4,10 @@ title: "Build with LLMs" "twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" --- -Copy this prompt and paste it into Claude, Cursor, or your preferred AI coding assistant to integrate World ID into your existing project. +Copy this prompt and paste it into Codex, Claude, Cursor, or your preferred AI coding assistant to integrate World ID into your existing project. ```text title="Copy this prompt" - Read world.id/SKILL.md and add World ID to my app +Read world.id/SKILL.md and add World ID to my app ``` ## World MCP diff --git a/world-id/idkit/error-codes.mdx b/world-id/idkit/error-codes.mdx index aa3dda2..ec93f44 100644 --- a/world-id/idkit/error-codes.mdx +++ b/world-id/idkit/error-codes.mdx @@ -165,15 +165,17 @@ This page focuses on IDKit SDK and bridge error codes returned during request fl Widgets expose an `onError` callback. Hooks expose `isError` and `errorCode` on the result object. +In JS and React, failed requests expose an `IDKitDebugReport` for triage. The widget `onError` callback receives it as a second `debugReport` argument for flow/bridge errors (host-app verify failures such as `failed_by_host_app` omit it); hooks expose `getDebugReport(): IDKitDebugReport | undefined`. + Version availability errors such as `world_id_4_not_available` and `world_id_3_not_available` are terminal for the current user and request. Retrying the same request usually returns the same result; change the requested credential policy or show a user-facing fallback instead. -In JS and React, match these with `IDKitErrorCodes`. Kotlin and Swift expose the same raw values through their `IDKitErrorCode` enums. +In JS and React, match these with `IDKitErrorCodes`. Kotlin and Swift expose the same raw values through their `IDKitErrorCode` enums. ```tsx { - console.error("IDKit error", errorCode); + onError={(errorCode, debugReport) => { + console.error("IDKit error", errorCode, debugReport); }} /> ``` @@ -182,6 +184,6 @@ In JS and React, match these with `IDKitErrorCodes`. Kotlin and Swift expose the const flow = useIDKitRequest({ /* ... */ }); if (flow.isError) { - console.error(flow.errorCode); + console.error(flow.errorCode, flow.getDebugReport()); } ``` diff --git a/world-id/idkit/integration-prompt.mdx b/world-id/idkit/integration-prompt.mdx index b94849a..dc67758 100644 --- a/world-id/idkit/integration-prompt.mdx +++ b/world-id/idkit/integration-prompt.mdx @@ -22,13 +22,13 @@ Integrate World ID into my project using IDKit. Here are my app details: 2. Create a backend endpoint that generates RP signatures. Signatures verify that proof requests come from my app. - Use `signRequest(action, signingKey)` which returns `{ sig, nonce, createdAt, expiresAt }`. + Use `signRequest({ signingKeyHex, action })` which returns `{ sig, nonce, createdAt, expiresAt }`. Never expose the signing key to the client. 3. On the client, fetch the RP signature from my backend, then create an IDKit request with: - - `app_id`, `action`, and `rp_context` (containing `rp_id`, `nonce`, `created_at`, `expires_at`, `signature` from the RP signature) + - `app_id`, `action`, and `rp_context` (containing `rp_id`, `nonce`, `created_at`, `expires_at`, `signature`; map the RP signature's `sig`->`signature`, `createdAt`->`created_at`, `expiresAt`->`expires_at`) - `allow_legacy_proofs: true` - - `.preset(orbLegacy())` for Orb verification + - `.preset(proofOfHuman())` for Proof of Human (World ID 4.0, with Orb fallback) - Signal is optional — use it to bind context like a user ID or wallet address into the proof. The backend should enforce the same value. 4. On success, send the IDKit result to my backend. diff --git a/world-id/idkit/javascript.mdx b/world-id/idkit/javascript.mdx index d45e7fa..83ef949 100644 --- a/world-id/idkit/javascript.mdx +++ b/world-id/idkit/javascript.mdx @@ -79,6 +79,7 @@ After `.preset(...)`, you get an `IDKitRequest` object: - `requestId` - `pollOnce()` - `pollUntilCompletion({ pollInterval, timeout })` +- `getDebugReport()` ```ts import { IDKitErrorCodes } from "@worldcoin/idkit-core"; @@ -103,6 +104,26 @@ if (!completion.success) { Outside World App, `connectorURI` is the URL you render as a QR code. +## Debug report + +Both `IDKitRequest` and `IDKitInviteCodeRequest` expose `getDebugReport(): IDKitDebugReport` with diagnostics for the latest request state. + +```ts +import type { IDKitDebugReport } from "@worldcoin/idkit-core"; + +const completion = await request.pollUntilCompletion(); +if (!completion.success) { + const report = request.getDebugReport(); + console.error(report.transport, report.request_id, report); +} +``` + +`IDKitDebugReport` fields: `version`, `package_version`, `transport` (`"bridge" | "mini_app"`), `generated_at`, and optional `request_id`, `request_payload`, `response_payload`, and `mini_app` (`MiniAppDebugInfo`). For bridge transport `response_payload` is the decrypted plaintext response string once the request completes; for native (`mini_app`) transport it is a structured debug object. The `mini_app` object holds World App native-transport diagnostics: `verify_version`, `platform`, `send_channel`, `minikit_subscribed`, and `response_channel`. + + + `setDebug(true)` (or `window.IDKIT_DEBUG = true`) and `isDebug()` toggle verbose `console.debug` logging. This is separate from `getDebugReport()`, which works regardless. + + ## Invite-code mode Use `IDKit.requestWithInviteCode(config)` to open a landing page that displays both an invite code and a QR code. 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. @@ -129,6 +150,7 @@ const completion = await request.pollUntilCompletion(); - `requestId` - `pollOnce()` - `pollUntilCompletion({ pollInterval, timeout })` +- `getDebugReport()` ### Migrating from QR / connect-URL diff --git a/world-id/idkit/kotlin.mdx b/world-id/idkit/kotlin.mdx index 66b0891..b0f8fae 100644 --- a/world-id/idkit/kotlin.mdx +++ b/world-id/idkit/kotlin.mdx @@ -36,6 +36,7 @@ dependencies { ```kotlin import com.worldcoin.idkit.IDKit +import com.worldcoin.idkit.IDKitRequestConfig import com.worldcoin.idkit.IDKitPollOptions import com.worldcoin.idkit.IDKitCompletionResult import com.worldcoin.idkit.orbLegacy diff --git a/world-id/idkit/mini-apps.mdx b/world-id/idkit/mini-apps.mdx index fcbd99b..9420b03 100644 --- a/world-id/idkit/mini-apps.mdx +++ b/world-id/idkit/mini-apps.mdx @@ -59,6 +59,10 @@ This is the same widget from [Step 4 of the integration guide](/world-id/idkit/i onSuccess={() => { // Unlock the protected Mini App experience here. }} + onError={(errorCode, debugReport) => { + // Inside World App, debugReport?.transport is "mini_app". + console.error("IDKit error", errorCode, debugReport); + }} /> ``` diff --git a/world-id/idkit/onchain-verification.mdx b/world-id/idkit/onchain-verification.mdx index 951d5cb..cae8f7d 100644 --- a/world-id/idkit/onchain-verification.mdx +++ b/world-id/idkit/onchain-verification.mdx @@ -166,10 +166,10 @@ contract VerifyUniquenessV4 { Minimal mapping from IDKit result: - `nullifier` = `responses[i].nullifier` - `action` = `keccak256(action)` as `uint256` -- `rpId` = numeric `rp_id` +- `rpId` = numeric form of your `rp_context.rp_id` (the `rp_`-prefixed string from your RP context, not the result) - `nonce` = top-level `nonce` - `signalHash` = `responses[i].signal_hash` - `expiresAtMin` = `responses[i].expires_at_min` - `issuerSchemaId` = `responses[i].issuer_schema_id` -- `credentialGenesisIssuedAtMin` = `responses[i].credential_genesis_issued_at_min ?? 0` +- `credentialGenesisIssuedAtMin` = the request's `genesis_issued_at_min` constraint (`0` if unconstrained) — not returned in `responses[i]` - `proof` = `responses[i].proof` (`uint256[5]`) diff --git a/world-id/idkit/react.mdx b/world-id/idkit/react.mdx index c551255..f17a22f 100644 --- a/world-id/idkit/react.mdx +++ b/world-id/idkit/react.mdx @@ -67,8 +67,8 @@ const rpContext: RpContext = { // Called after `handleVerify` resolves (or immediately if omitted). // Update your app state/UI here. }} - onError={(errorCode) => { - console.error("IDKit error", errorCode); + onError={(errorCode, debugReport) => { + console.error("IDKit error", errorCode, debugReport); }} />; ``` @@ -115,6 +115,7 @@ Hook result fields: - `connectorURI` - `result` - `errorCode` +- `getDebugReport()` ## Invite-code mode @@ -148,6 +149,7 @@ import { IDKitInviteCodeRequestWidget, selfieCheckLegacy } from "@worldcoin/idki - `codeExpiresAt` - `result` - `errorCode` +- `getDebugReport()` ### Migrating from QR / connect-URL @@ -187,6 +189,10 @@ import { IDKitInviteCodeRequestWidget, selfieCheckLegacy } from "@worldcoin/idki Swap the component and preset. Props, callbacks, and backend verification stay the same. The headless equivalent is `useIDKitInviteCodeRequest` in place of `useIDKitRequest`. +## Session flows + +Session verification uses `IDKitSessionWidget` (controlled) or `useIDKitSession` (headless); `onSuccess` returns an `IDKitResultSession`. Both share the request flows' widget callbacks and hook-result fields — including `onError(errorCode, debugReport?)` and `getDebugReport()`. + ## Presets React hooks/widgets take `preset` directly in config.