Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions openapi/developer-portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down Expand Up @@ -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"
Comment on lines +859 to +862

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the documented face alias in the enum

The description says that legacy requests using verification_level: "face" remain accepted, but the new OpenAPI enum excludes that value. Any generated client or request validator that enforces this schema will reject those backward-compatible requests before they reach /api/v2/verify/{app_id}. Add face to the enum, marking it deprecated in the description if appropriate.

Useful? React with 👍 / 👎.

],
"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",
Expand Down Expand Up @@ -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",
Expand Down
34 changes: 5 additions & 29 deletions world-id/SKILL.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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**.
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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. |
Expand Down
22 changes: 13 additions & 9 deletions world-id/credentials/11.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 13 additions & 3 deletions world-id/idkit/credentials.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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).

<Note>
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.
</Note>

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).

<CodeGroup title="Selfie Check">
```typescript title="JavaScript"
import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core";
Expand Down
4 changes: 2 additions & 2 deletions world-id/idkit/error-codes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ This page documents the IDKit SDK and bridge error codes returned during request
</tr>
<tr>
<td className="p-2 align-middle whitespace-nowrap"><code>failed_by_host_app</code></td>
<td className="p-2 align-middle">Host app callback failed while processing a successful proof.</td>
<td className="p-2 align-middle">Fix host callback/backend logic and retry.</td>
<td className="p-2 align-middle">World App returned a proof, but the host app's <code>handleVerify</code> callback threw or rejected it.</td>
<td className="p-2 align-middle">Inspect the callback and backend verification response. This is not a camera or credential-enrollment failure.</td>
</tr>
<tr>
<td className="p-2 align-middle whitespace-nowrap"><code>invalid_rp_signature</code></td>
Expand Down
2 changes: 1 addition & 1 deletion world-id/idkit/react.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion world-id/idkit/verification-flows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Loading