From 3fdcde27e0e693b66b1858b1b639e9ad79a253c2 Mon Sep 17 00:00:00 2001 From: Takis Kakalis <80459599+Takaros999@users.noreply.github.com> Date: Mon, 27 Jul 2026 22:35:37 -0700 Subject: [PATCH] docs: clarify Selfie Check integration --- openapi/developer-portal.json | 13 +++++++--- world-id/SKILL.md | 34 ++++----------------------- world-id/credentials/11.mdx | 22 ++++++++++------- world-id/idkit/credentials.mdx | 16 ++++++++++--- world-id/idkit/error-codes.mdx | 4 ++-- world-id/idkit/react.mdx | 2 +- world-id/idkit/verification-flows.mdx | 2 +- 7 files changed, 45 insertions(+), 48 deletions(-) diff --git a/openapi/developer-portal.json b/openapi/developer-portal.json index 7113d22..5888746 100644 --- a/openapi/developer-portal.json +++ b/openapi/developer-portal.json @@ -682,7 +682,7 @@ "/api/v4/verify/{rp_id}": { "post": { "summary": "Verify", - "description": "Verifies World ID 4.0 proofs and legacy 3.0 proofs. Use `rp_id` (`rp_...`) when possible; `app_id` (`app_...`) is still accepted for backward compatibility.", + "description": "Verifies World ID 4.0 proofs and legacy 3.0 proofs. Forward the complete IDKit result without remapping response identifiers or constructing a legacy `verification_level`. Use `rp_id` (`rp_...`) when possible; `app_id` (`app_...`) is still accepted for backward compatibility.", "x-mint": { "href": "/api-reference/developer-portal/verify" }, @@ -854,7 +854,14 @@ }, "verification_level": { "type": "string", - "description": "The verification level, as provided by [IDKit](/world-id/idkit/integrate)." + "enum": [ + "orb", + "secure_document", + "document", + "device", + "selfie" + ], + "description": "The legacy verification level. Use `selfie` for Selfie Check. The historical `face` value remains accepted for backward compatibility and behaves the same as `selfie`." }, "action": { "type": "string", @@ -1388,7 +1395,7 @@ "properties": { "identifier": { "type": "string", - "description": "Credential type (e.g. orb)." + "description": "Credential identifier returned by IDKit (for example, `orb` or `selfie`). Use `selfie` for Selfie Check; the historical `face` value remains accepted as a backward-compatible alias." }, "signal_hash": { "type": "string", diff --git a/world-id/SKILL.md b/world-id/SKILL.md index e09449c..fdd2f39 100644 --- a/world-id/SKILL.md +++ b/world-id/SKILL.md @@ -1,7 +1,7 @@ --- name: world-id-integration description: | - Use this skill when adding, upgrading, debugging, or testing World ID verification with IDKit in a new or existing web or mobile app. Covers Proof of Human, passport/document, Face Check, and session/sign-in flows; Developer Portal app, RP, and action setup; server-side signing and proof verification; environment matching; nullifier replay protection; and launch testing. Trigger when the user asks to add World ID, verify humans, stop bots or multi-accounting, add Sybil resistance, or mentions IDKit, Orb, World ID proof flows, World App proof flows, @worldcoin/idkit, signing keys, rp_id, or app_id. + Use this skill when adding, upgrading, debugging, or testing World ID verification with IDKit in a new or existing web or mobile app. Covers Proof of Human, passport/document, Selfie Check, and session/sign-in flows; Developer Portal app, RP, and action setup; server-side signing and proof verification; environment matching; nullifier replay protection; and launch testing. Trigger when the user asks to add World ID, verify humans, stop bots or multi-accounting, add Sybil resistance, or mentions IDKit, Orb, World ID proof flows, World App proof flows, @worldcoin/idkit, signing keys, rp_id, or app_id. version: 0.1.0 metadata: author: worldcoin @@ -38,7 +38,7 @@ Before changing code or creating Portal resources: - target environment and test path: staging simulator, production World ID, or both - Developer Portal MCP connection - whether an RP signing key already exists in a server-side secret store - - credential access, especially Face Check + - requested credential policy and whether legacy proof fallback is needed 3. Report a short readiness summary and ask only for unresolved blockers. **Never ask the user to paste a signing key, Portal API key, or other secret into chat.** Ask only whether it exists and where the application expects it. 4. Build a TODO from the missing steps. Preserve working configuration and existing Portal resources unless the user explicitly wants replacements. @@ -99,35 +99,12 @@ The credential decides what the user proves. Nail this down before scaffolding |---|---|---| | **`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. | +| **`selfieCheckLegacy`** — Selfie Check | A liveness selfie signal backed by a World ID 3.0 proof | Lower-assurance "is a human in front of the camera" — friction/bot deterrence without the full Orb requirement. | **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). -### Face Check access gate - -After the app and action exist—but before implementing or opening a Face Check flow—confirm the `app_id` is enabled: - -If either resource is missing, mark this gate pending, provision the resource in Phase 4 step 2, and return here before implementing the client request. - -1. Prefer `get_app_config` if the MCP response exposes `enable_face_check`. -2. Otherwise call the public precheck endpoint with the real app and action: - - ```http - POST https://developer.world.org/api/v1/precheck/{app_id} - Content-Type: application/json - - { "action": "your-action" } - ``` - -3. Read `enable_face_check` from the response: - - `true`: continue. - - `false`: stop and tell the user Face Check is not enabled for this app. Point them to their World contact or a documented support path to request access; if none is documented, say so instead of inventing one. Do not let the integration fail as an unexplained spinner. - - missing field or failed precheck: treat access as unknown, surface the response details, and resolve the gate before continuing. - -Do not interpret a valid app, RP, or action as proof of Face Check access. It is a separate app-level capability. - ## Phase 4 — Implement the 6 integration steps and explain the WHY The full code for each step is at [https://docs.world.org/world-id/idkit/integrate](https://docs.world.org/world-id/idkit/integrate). Don't reproduce it; link to it and adapt to the user's framework. The agent owns making sure each step is done **and understood**. @@ -145,7 +122,7 @@ The full code for each step is at [https://docs.world.org/world-id/idkit/integra 2. **Create or reuse the Portal resources.** Use the MCP when available. Reuse an existing app, RP, and action when they match the requested integration. For a new RP, capture `app_id`, `rp_id`, and `signing_key.private_key` from `configure_world_id`, create the action in the intended environment, and write the signing key to the prepared server-only secret store in the same step. The portal returns it exactly once. **Do not print, log, or return the private key to chat.** If the key is lost, explain that `get_world_id_signing_key` cannot recover it; rotation creates a new key and invalidates the old signer. 3. **Generate the RP signature in your backend.** *Why backend?* The signing key authenticates your app to the protocol. Leaking it lets anyone impersonate your app and forge proof requests. **CRITICAL: never sign on the client. Never expose `RP_SIGNING_KEY` as a `NEXT_PUBLIC_*` var. Never log it.** 4. **Open the IDKit widget on the client** with the signature your backend returned. The widget hands off to World ID, which produces a zero-knowledge proof. -5. **Verify the proof in your backend** by POSTing it **as-is** to `https://developer.world.org/api/v4/verify/{rp_id}`. *Why backend?* A client can return any JSON it wants. Only the World verifier — called from a trusted server — confirms the proof is real and tied to a unique credential. Verifying client-side defeats the entire point. **DO NOT mutate, re-encode, or trim the proof JSON before forwarding** — pass exactly what IDKit returned. +5. **Verify the proof in your backend** by POSTing it **as-is** to `https://developer.world.org/api/v4/verify/{rp_id}`. *Why backend?* A client can return any JSON it wants. Only the World verifier — called from a trusted server — confirms the proof is real and tied to a unique credential. Verifying client-side defeats the entire point. **DO NOT mutate, re-encode, or trim the proof JSON before forwarding** — pass exactly what IDKit returned. For Selfie Check, IDKit returns `responses[].identifier: "selfie"`. Do not turn it into a hand-built `verification_level`; `face` is only a backward-compatible alias for legacy integrations. 6. **Store the nullifier.** Every successful proof returns a `nullifier` — an RP-scoped, action-scoped, non-reversible identifier for that user. *Why store it?* Without uniqueness storage, a user can verify the same proof twice and double-claim a reward, vote, etc. Persist `(action, nullifier)` with a `UNIQUE` constraint and reject duplicates on insert. Column type: **`NUMERIC(78, 0)`** (256-bit field elements). The nullifier reveals nothing about the user — safe to store, but it's the *only* anti-replay mechanism, so it's required. ## Phase 5 — Match environments end-to-end @@ -166,7 +143,7 @@ Do not declare the integration complete from compilation or Portal configuration - [ ] Backend verification succeeds and the exact IDKit result reaches `/api/v4/verify/{rp_id}`. - [ ] The verified nullifier is persisted. - [ ] Replaying the same nullifier is rejected by the database uniqueness constraint. -- [ ] Relevant failures—unavailable Face Check, invalid action/signature, or environment mismatch—produce an actionable user-facing error instead of an indefinite loading state. +- [ ] Relevant failures—unavailable Selfie Check, invalid action/signature, or environment mismatch—produce an actionable user-facing error instead of an indefinite loading state. - [ ] JS/React failures retain the `debugReport` and `request_id` needed for diagnosis without logging secrets. Run automated tests for the routes and persistence behavior. Clearly identify simulator, phone, or production checks that still require the user; never imply a manual proof flow ran when it did not. @@ -179,7 +156,6 @@ Surface these proactively when you see the matching symptom — don't make the u | Symptom | Cause | Recovery | |---|---|---| -| Face Check appears unresponsive or never starts | The app may not be enabled for Face Check | Read `enable_face_check` through MCP when available or `/api/v1/precheck/{app_id}`. If false, stop and explain how to request access. | | World ID 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 Phase 5. 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. | diff --git a/world-id/credentials/11.mdx b/world-id/credentials/11.mdx index 2982dde..99d13ed 100644 --- a/world-id/credentials/11.mdx +++ b/world-id/credentials/11.mdx @@ -17,35 +17,39 @@ import { CredentialHero } from "/snippets/credential-hero.jsx"; issuerName="Tools for Humanity" issuerHref="https://www.toolsforhumanity.com" issuerVerified={true} - status="beta" id={11} sybilResistance="some" sybilResistanceDescription="Facial similarity checks provide some sybil resistance, but not as strong as Orb or NFC verification." validityPeriod="90 days" - sourceCodeHref="coming-soon" /> ## Introduction -Selfie Check (Beta) uses the user's mobile device camera to establish humanness (liveness) and returns a "Sybil score", a similarity signal that flags whether the user has created an abnormal number of accounts on your platform. Unlike high-assurance Orb verification (Iris), Selfie Check focuses on frictionless, privacy-preserving onboarding. +Selfie Check uses the user's mobile device camera for liveness and facial +similarity checks. It adds friction against automated and repeated account +creation without requiring a Proof of Human. Unlike high-assurance Orb verification, +Selfie Check does not provide a strict one-person-one-account guarantee. -Use Selfie Check (Beta) for: +Use Selfie Check for: * **Liveness detection:** Confirm the user is a real person, not a spoof or injection attack. -* **Sybil resistance:** Prevent mass account creation through facial similarity checks. +* **Sybil resistance:** Make mass account creation harder through facial similarity checks. * **Continuity:** Confirm a returning user is the same person who originally enrolled. +Anyone with World App can use Selfie Check. No Orb, passport or other +prerequisite credential is required. + ## How it works -Use IDKit to integrate Selfie Check (Beta) into your application. +Use IDKit to integrate Selfie Check into your application. -* **On Mobile (iOS/Android):** Use IDKit to generate a Deep Link and attach it to a "Verify" CTA. When the user taps it, they are redirected to World ID to complete the Selfie Check (Beta) flow. -* **On Desktop:** Use IDKit to generate a **QR Code** and display it to the user. When the user scans it with their mobile device camera, World ID launches and guides them through the Selfie Check (Beta) flow. +* **On mobile (iOS/Android):** Use IDKit to generate a deep link and attach it to a "Verify" CTA. When the user taps it, they are redirected to World App to complete Selfie Check. +* **On desktop:** Use IDKit to generate a QR code and display it to the user. When the user scans it with their mobile device camera, World App launches and guides them through Selfie Check. ## User Experience Flow 1. **Challenge:** The user initiates the flow on your app (Relying Party). -2. **Hand-off:** The user is redirected to World ID. If they don't have World ID installed, they are guided to download it and go straight into the Selfie Check (Beta) experience. +2. **Hand-off:** The user is redirected to World App. If they don't have World App installed, they are guided to download it and go straight into the Selfie Check experience. 3. **Enrollment/Auth:** * **New User:** Prompts to "Take a selfie", then runs a liveness check and a uniqueness check against other faces. * **Returning User:** Runs a quick Face Auth to verify continuity. diff --git a/world-id/idkit/credentials.mdx b/world-id/idkit/credentials.mdx index 89ccbff..f097621 100644 --- a/world-id/idkit/credentials.mdx +++ b/world-id/idkit/credentials.mdx @@ -14,7 +14,7 @@ A [Credential](https://docs.rs/world-id-primitives/latest/world_id_primitives/cr | --- | --- | --- | | Proof of Human | The user is a unique human, backed by anonymous biometric verification by the Orb. | `proofOfHuman` | | Passport | The user holds a verified NFC passport credential. | `passport` | -| Selfie Check | A low-assurance biometric credential using the device camera for liveness and facial similarity. | `selfieCheckLegacy` today; World ID 4.0 support rolling out soon. | +| Selfie Check | A low-assurance biometric credential using the device camera for liveness and facial similarity. | `selfieCheckLegacy` | | Identity Check | An attestation that a document-backed property about the user matches your requested attributes. | `identityCheck` with attributes like `minimum_age`, `nationality`, or `document_type`. | You can also add a liveness check to any preset with `require_user_presence`. @@ -105,14 +105,24 @@ const preset = passport({ signal: "user-123" }); ## Selfie Check -Selfie Check is available today through the legacy preset and returns a World ID 3.0 Face proof. World ID 4.0 support is rolling out soon. Learn more in [Selfie Check](/world-id/credentials/11). +Use the `selfieCheckLegacy` preset to request Selfie Check. Anyone with World App can +complete the flow: no Orb, passport, document, or other prerequisite credential +is required. To test your integration end-to-end, see [Testing Selfie Check in Sandbox](/world-id/sandbox/testing-selfie-check). - Selfie Check is currently available to select partners. To discuss access or learn more, please [contact us](mailto:developers@toolsforhumanity.com). + “Legacy” refers to the proof protocol, not the Selfie Check product. This + preset returns World ID 3.0 proofs and requires `allow_legacy_proofs=true`. + Selfie Check with World ID 4.0 is not available yet. +IDKit returns `identifier: "selfie"` for Selfie Check. Legacy integrations may +still use `face`; verification services accept it as a backward-compatible +alias. Applications should not translate the identifier or construct a +`verification_level`. Forward the complete IDKit result to the +[verification endpoint](/api-reference/developer-portal/verify). + ```typescript title="JavaScript" import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core"; diff --git a/world-id/idkit/error-codes.mdx b/world-id/idkit/error-codes.mdx index 2b3f82e..742feb5 100644 --- a/world-id/idkit/error-codes.mdx +++ b/world-id/idkit/error-codes.mdx @@ -80,8 +80,8 @@ This page documents the IDKit SDK and bridge error codes returned during request failed_by_host_app - Host app callback failed while processing a successful proof. - Fix host callback/backend logic and retry. + World App returned a proof, but the host app's handleVerify callback threw or rejected it. + Inspect the callback and backend verification response. This is not a camera or credential-enrollment failure. invalid_rp_signature diff --git a/world-id/idkit/react.mdx b/world-id/idkit/react.mdx index f17a22f..ff2c1f3 100644 --- a/world-id/idkit/react.mdx +++ b/world-id/idkit/react.mdx @@ -77,7 +77,7 @@ const rpContext: RpContext = { - `onSuccess` is required for `IDKitRequestWidget` and `IDKitSessionWidget`. - `handleVerify` is optional and only available on widgets. Use it to verify the proof in your backend before success is emitted. -- If `handleVerify` throws/rejects, the widget enters an error state, emits `onError("failed_by_host_app")`, and does not call `onSuccess`. +- If `handleVerify` throws/rejects, the widget enters an error state, emits `onError("failed_by_host_app")`, and does not call `onSuccess`. This means World App already returned a proof, but your callback or backend did not accept it. - `onError` is optional. ## Headless hooks diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx index 9a787ff..6e69460 100644 --- a/world-id/idkit/verification-flows.mdx +++ b/world-id/idkit/verification-flows.mdx @@ -130,4 +130,4 @@ sequenceDiagram **Integrate** -Setup is identical to the [standard integration](/world-id/idkit/integrate) — only the request call changes. Only the `selfieCheckLegacy` preset is supported today. For code samples and migration guides, see the per-SDK sections: [JavaScript](/world-id/idkit/javascript#invite-code-mode), [React](/world-id/idkit/react#invite-code-mode), [Swift](/world-id/idkit/swift#invite-code-mode). +Setup is identical to the [standard integration](/world-id/idkit/integrate) — only the request call changes. Only the `selfieCheckLegacy` preset is supported today. It returns World ID 3.0 proofs; Selfie Check with World ID 4.0 is not available yet. For code samples and migration guides, see the per-SDK sections: [JavaScript](/world-id/idkit/javascript#invite-code-mode), [React](/world-id/idkit/react#invite-code-mode), [Swift](/world-id/idkit/swift#invite-code-mode).