diff --git a/openapi/developer-portal.json b/openapi/developer-portal.json
index 8acb393..738f095 100644
--- a/openapi/developer-portal.json
+++ b/openapi/developer-portal.json
@@ -742,6 +742,32 @@
]
}
},
+ "selfie_check_v4": {
+ "summary": "Selfie Check",
+ "value": {
+ "protocol_version": "4.0",
+ "nonce": "0xabc123",
+ "action": "selfie-check-2026-09-02",
+ "integrity_bundle": {
+ "version": 2,
+ "signature_format": "apple_app_attest",
+ "timestamp": 1788302923,
+ "signature": "a1b2c3d4",
+ "jwt": "eyJ..."
+ },
+ "responses": [
+ {
+ "identifier": "selfie",
+ "issuer_schema_id": 11,
+ "nullifier": "0x2bf8406809dcefb1486dadc96c0a897db9bab002053054cf64272db512c6fbd8",
+ "expires_at_min": 1788302888,
+ "proof": ["0x111", "0x222", "0x333", "0x444", "0x555"],
+ "signal_hash": "0x0",
+ "sybil_score": 10
+ }
+ ]
+ }
+ },
"session_v4": {
"summary": "Session proof (protocol 4.0)",
"value": {
@@ -861,7 +887,7 @@
"device",
"selfie"
],
- "description": "The legacy verification level. Use `selfie` for Selfie Check (Beta). The historical `face` value remains accepted for backward compatibility and behaves the same as `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",
@@ -1250,6 +1276,33 @@
"additionalProperties": true,
"description": "Pass-through GraphQL response. Usually includes `data` and optionally `errors`."
},
+ "IntegrityBundle": {
+ "type": "object",
+ "required": ["version", "signature_format", "timestamp", "signature", "jwt"],
+ "description": "World ID App integrity attestation. Selfie Check responses with `issuer_schema_id: 11` require version 2, which covers the disclosed `sybil_score`.",
+ "properties": {
+ "version": {
+ "type": "integer",
+ "enum": [1, 2]
+ },
+ "signature_format": {
+ "type": "string",
+ "enum": ["apple_app_attest", "android_keystore"]
+ },
+ "timestamp": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "signature": {
+ "type": "string",
+ "description": "Hex-encoded device signature."
+ },
+ "jwt": {
+ "type": "string",
+ "description": "Attestation Gateway JWT used to verify the signing key."
+ }
+ }
+ },
"VerifyV4Request": {
"description": "Choose one request type.",
"oneOf": [
@@ -1303,6 +1356,9 @@
"enum": ["production", "staging"],
"default": "production"
},
+ "integrity_bundle": {
+ "$ref": "#/components/schemas/IntegrityBundle"
+ },
"responses": {
"type": "array",
"minItems": 1,
@@ -1312,7 +1368,7 @@
},
"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."
+ "description": "Whether World App completed the requested user-presence check. IDKit includes this field only when `require_user_presence` was true in the request."
}
}
},
@@ -1337,9 +1393,17 @@
},
"environment": {
"type": "string",
- "enum": ["production", "staging"],
+ "enum": ["production", "staging", "sandbox"],
"default": "production"
},
+ "integrity_bundle": {
+ "description": "Required with version 2 when any response item is a Selfie Check credential (`issuer_schema_id: 11`).",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/IntegrityBundle"
+ }
+ ]
+ },
"responses": {
"type": "array",
"minItems": 1,
@@ -1349,7 +1413,7 @@
},
"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."
+ "description": "Whether World App completed the requested user-presence check. IDKit includes this field only when `require_user_presence` was true in the request."
}
}
},
@@ -1373,9 +1437,17 @@
},
"environment": {
"type": "string",
- "enum": ["production", "staging"],
+ "enum": ["production", "staging", "sandbox"],
"default": "production"
},
+ "integrity_bundle": {
+ "description": "Required with version 2 when any response item is a Selfie Check credential (`issuer_schema_id: 11`).",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/IntegrityBundle"
+ }
+ ]
+ },
"responses": {
"type": "array",
"minItems": 1,
@@ -1385,7 +1457,7 @@
},
"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."
+ "description": "Whether World App completed the requested user-presence check. IDKit includes this field only when `require_user_presence` was true in the request."
}
}
},
@@ -1395,7 +1467,7 @@
"properties": {
"identifier": {
"type": "string",
- "description": "Credential identifier returned by IDKit (for example, `orb` or `selfie`). Use `selfie` for Selfie Check (Beta); the historical `face` value remains accepted as a backward-compatible alias."
+ "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",
@@ -1451,6 +1523,11 @@
"type": "string"
},
"description": "Exactly 5 hex elements (4 compressed proof elements + Merkle root)."
+ },
+ "sybil_score": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Versioned Selfie Check risk signal represented as an integer. Required only when `issuer_schema_id` is 11."
}
}
},
@@ -1493,6 +1570,11 @@
"items": {
"type": "string"
}
+ },
+ "sybil_score": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Versioned Selfie Check risk signal represented as an integer. Required only when `issuer_schema_id` is 11."
}
}
},
diff --git a/snippets/idkit-response.mdx b/snippets/idkit-response.mdx
index 200baa1..1fc4feb 100644
--- a/snippets/idkit-response.mdx
+++ b/snippets/idkit-response.mdx
@@ -13,8 +13,7 @@
"merkle_root": "0x0abc123...root_hash",
"nullifier": "0x04e5f6...nullifier_hash"
}
- ],
- "user_presence_completed": false
+ ]
}
```
@@ -33,8 +32,34 @@
"issuer_schema_id": 1,
"expires_at_min": 1756166400
}
- ],
- "user_presence_completed": false
+ ]
+}
+```
+
+```json title="Selfie Check"
+{
+ "protocol_version": "4.0",
+ "nonce": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "action": "selfie-check-2026-09-02",
+ "environment": "production",
+ "integrity_bundle": {
+ "version": 2,
+ "signature_format": "apple_app_attest",
+ "timestamp": 1788302923,
+ "signature": "a1b2c3d4...device_signature",
+ "jwt": "eyJ...attestation_jwt"
+ },
+ "responses": [
+ {
+ "identifier": "selfie",
+ "signal_hash": "0x0",
+ "proof": ["0x1a2b...", "0x3c4d...", "0x5e6f...", "0x7a8b...", "0x9c0d..."],
+ "nullifier": "0x04e5f6...rp_scoped_nullifier",
+ "issuer_schema_id": 11,
+ "expires_at_min": 1788302888,
+ "sybil_score": 10
+ }
+ ]
}
```
@@ -53,8 +78,7 @@
"issuer_schema_id": 1,
"expires_at_min": 1756166400
}
- ],
- "user_presence_completed": false
+ ]
}
```
diff --git a/world-id/SKILL.md b/world-id/SKILL.md
index 3a66cee..966f3ff 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, Selfie Check (Beta), 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
- - requested credential policy, feature access (especially Selfie Check (Beta)), and whether legacy proof fallback is needed
+ - 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,18 +99,23 @@ 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 (Beta) | 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. |
+| **`selfieCheck`** — Selfie Check | A liveness and facial-similarity credential backed by a World ID 4.0 proof | Medium-assurance friction and bot deterrence without the full Orb requirement. Returns a verified `sybil_score` risk signal. |
-**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.
+**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 `selfieCheck`. 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).
-### Selfie Check (Beta) access
+### Selfie Check
-Before implementing or testing Selfie Check, confirm that its feature flag is
-enabled for the target app. If it is not enabled, stop and tell the user to
-request access through their World contact or the documented support path. A
-valid app or action does not imply Selfie Check access.
+Use `selfieCheck`, which requests the Selfie Check credential. Each Selfie Check
+response includes a required integer `sybil_score`; forward the complete IDKit
+result unchanged so the Developer Portal can verify the proof and version 2
+integrity signature before the app uses the score.
+
+World ID 4.0 uniqueness proofs are one-time per action for each user. If the
+same user needs to complete Selfie Check more than once, use a different action
+for each check. Migrate existing `selfieCheckLegacy` integrations to
+`selfieCheck`.
## Phase 4 — Implement the 6 integration steps and explain the WHY
@@ -150,7 +155,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 Selfie Check, invalid action/signature, or environment mismatch—produce an actionable user-facing error instead of an indefinite loading state.
+- [ ] Relevant failures—replayed nullifiers, 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.
diff --git a/world-id/credentials/11.mdx b/world-id/credentials/11.mdx
index 8e8a2a8..a46863b 100644
--- a/world-id/credentials/11.mdx
+++ b/world-id/credentials/11.mdx
@@ -1,5 +1,5 @@
---
-title: "Selfie Check (Beta)"
+title: "Selfie Check"
icon: "camera"
iconType: "duotone"
description: "A medium-assurance biometric credential using the device camera for liveness and facial similarity."
@@ -10,29 +10,26 @@ description: "A medium-assurance biometric credential using the device camera fo
import { CredentialHero } from "/snippets/credential-hero.jsx";
deviceLegacydeviceLegacy only for existing Device integrations. For new integrations, use Selfie Check (Beta).deviceLegacy only for existing Device integrations. For new integrations, use Selfie Check.&a=
const expiresAt = request.expiresAt; // Unix seconds
@@ -174,7 +175,7 @@ const request = await IDKit.requestWithInviteCode({
action: "my-action",
rp_context,
allow_legacy_proofs: true,
-}).preset(selfieCheckLegacy({ signal: "user-123" }));
+}).preset(proofOfHuman({ signal: "user-123" }));
const connectorURI = request.connectorURI; // display to user (URL with code embedded)
const expiresAt = request.expiresAt; // drive a countdown
diff --git a/world-id/idkit/mini-apps.mdx b/world-id/idkit/mini-apps.mdx
index 9420b03..dce4810 100644
--- a/world-id/idkit/mini-apps.mdx
+++ b/world-id/idkit/mini-apps.mdx
@@ -37,7 +37,7 @@ The Mini-App-specific details:
| Goal | IDKit preset |
| --- | --- |
| Strong sybil resistance or one-human-one-action checks | `proofOfHuman` |
-| Lower-friction liveness or bot deterrence | `selfieCheckLegacy` |
+| Lower-friction liveness or bot deterrence | `selfieCheck` |
| Passport-backed checks | `passport` |
Check out this [page](/world-id/idkit/credentials) to learn about the different World ID credentials and which preset to use for each.
diff --git a/world-id/idkit/react.mdx b/world-id/idkit/react.mdx
index ff2c1f3..331a54b 100644
--- a/world-id/idkit/react.mdx
+++ b/world-id/idkit/react.mdx
@@ -122,7 +122,7 @@ Hook result fields:
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";
+import { IDKitInviteCodeRequestWidget, proofOfHuman } from "@worldcoin/idkit";
{ /* ... */ }}
onSuccess={() => { /* ... */ }}
/>;
@@ -172,7 +172,7 @@ import { IDKitRequestWidget, orbLegacy } from "@worldcoin/idkit";
```tsx
// After — invite-code widget
-import { IDKitInviteCodeRequestWidget, selfieCheckLegacy } from "@worldcoin/idkit";
+import { IDKitInviteCodeRequestWidget, proofOfHuman } from "@worldcoin/idkit";
;
diff --git a/world-id/idkit/swift.mdx b/world-id/idkit/swift.mdx
index 95585b5..9bc1362 100644
--- a/world-id/idkit/swift.mdx
+++ b/world-id/idkit/swift.mdx
@@ -82,7 +82,7 @@ Use `presetWithInviteCode(_:)` on the builder to return an `IDKitInviteCodeReque
```swift
let request = try IDKit.request(config: config)
- .presetWithInviteCode(selfieCheckLegacy(signal: "user-123"))
+ .presetWithInviteCode(proofOfHuman(signal: "user-123"))
let connectorURL = request.connectorURL // URL with &c=&a=
let expiresAt = request.expiresAt // Date
@@ -103,7 +103,7 @@ let completion = await request.pollUntilCompletion()
```swift
// After — invite-code mode
let request = try IDKit.request(config: config)
- .presetWithInviteCode(selfieCheckLegacy(signal: "user-123"))
+ .presetWithInviteCode(proofOfHuman(signal: "user-123"))
let connectorURL = request.connectorURL // display to user (URL with code embedded)
let expiresAt = request.expiresAt // drive a countdown
diff --git a/world-id/idkit/verification-flows.mdx b/world-id/idkit/verification-flows.mdx
index a1467b6..8e20009 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 for Selfie Check (Beta) 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).
+Setup is identical to the [standard integration](/world-id/idkit/integrate) — only the request call changes. Choose the appropriate preset in [Configure Credentials](/world-id/idkit/credentials). 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).
diff --git a/world-id/overview.mdx b/world-id/overview.mdx
index 43c46e2..4d716ae 100644
--- a/world-id/overview.mdx
+++ b/world-id/overview.mdx
@@ -54,7 +54,7 @@ Through credentials like Proof of Human, Document, and Selfie Check, World ID ex
- Selfie Check Beta
+ Selfie Check
Low-friction liveness and uniqueness signal from a selfie flow. Best for sign-up and bot defense where speed matters most.
diff --git a/world-id/sandbox/testing-selfie-check.mdx b/world-id/sandbox/testing-selfie-check.mdx
index 8c45d71..c35ef71 100644
--- a/world-id/sandbox/testing-selfie-check.mdx
+++ b/world-id/sandbox/testing-selfie-check.mdx
@@ -1,21 +1,16 @@
---
-title: "Testing Selfie Check (Beta) in Sandbox"
-description: "Coverage, critical user journeys, and known limitations for testing your Selfie Check (Beta) integration in Sandbox."
+title: "Testing Selfie Check in Sandbox"
+description: "Coverage, critical user journeys, and known limitations for testing your Selfie Check integration in Sandbox."
"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"
---
{/* cspell:ignore idkit DoB reinstalls */}
-Sandbox lets you run the full [Selfie Check (Beta)](/world-id/credentials/11) relying-party
+Sandbox lets you run the full [Selfie Check](/world-id/credentials/11) relying-party
journey end-to-end — from your surface, through IDKit, into the sandbox World ID app,
and back — without touching production identities or real proofs.
-
- Selfie Check (Beta) must be enabled for your app before you can test it. To
- enable the feature flag, request access through your World point of contact.
-
-
New to Sandbox? Start with [What is Sandbox?](/world-id/sandbox/what-is-sandbox) and
[How to get access](/world-id/sandbox/sandbox-access) before working through this
guide.