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
96 changes: 89 additions & 7 deletions openapi/developer-portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -1303,6 +1356,9 @@
"enum": ["production", "staging"],
"default": "production"
},
"integrity_bundle": {
"$ref": "#/components/schemas/IntegrityBundle"
},
"responses": {
"type": "array",
"minItems": 1,
Expand All @@ -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."
}
}
},
Expand All @@ -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"
}
Comment on lines +1399 to +1404

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 Enforce the required integrity bundle for Selfie Check

For a request containing an issuer_schema_id: 11 response, this schema still permits integrity_bundle to be omitted because it is absent from the request's required array, and when supplied it permits version 1 through the shared IntegrityBundle enum. Consequently, generated clients and schema validation accept payloads that the description says the verification endpoint rejects; model Selfie Check as a distinct request variant that requires the bundle and constrains its version to 2.

Useful? React with 👍 / 👎.

]
},
"responses": {
"type": "array",
"minItems": 1,
Expand All @@ -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."
}
}
},
Expand All @@ -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,
Expand All @@ -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."
}
}
},
Expand All @@ -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",
Expand Down Expand Up @@ -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."
Comment on lines +1527 to +1530

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 Encode the required Selfie Check score in the schema

Although the new description says sybil_score is required for issuer schema 11, the field is not present in this object's required list. API documentation and generated types therefore expose the score as optional and validate a schema-11 response without it, contradicting the documented IDKit contract; use a dedicated oneOf Selfie response variant that requires sybil_score.

Useful? React with 👍 / 👎.

}
}
},
Expand Down Expand Up @@ -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."
}
}
},
Expand Down
36 changes: 30 additions & 6 deletions snippets/idkit-response.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
"merkle_root": "0x0abc123...root_hash",
"nullifier": "0x04e5f6...nullifier_hash"
}
],
"user_presence_completed": false
]
}
```

Expand All @@ -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
}
]
}
```

Expand All @@ -53,8 +78,7 @@
"issuer_schema_id": 1,
"expires_at_min": 1756166400
}
],
"user_presence_completed": false
]
}
```
</CodeGroup>
25 changes: 15 additions & 10 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, 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
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
- 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.

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
26 changes: 13 additions & 13 deletions world-id/credentials/11.mdx
Original file line number Diff line number Diff line change
@@ -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."
Expand All @@ -10,29 +10,26 @@ description: "A medium-assurance biometric credential using the device camera fo
import { CredentialHero } from "/snippets/credential-hero.jsx";

<CredentialHero
title="Selfie Check (Beta)"
title="Selfie Check"
description="A medium-assurance biometric credential using the device camera for liveness and facial similarity."
image="/images/docs/id/issuers/11/thumbnail.png"
bgColor="#1a3a4a"
issuerName="Tools for Humanity"
issuerHref="https://www.toolsforhumanity.com"
issuerVerified={true}
status="beta"
id={11}
validityPeriod="90 days"
/>

## Introduction

Selfie Check (Beta) uses the user's mobile device camera for liveness and facial
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 and is
considered a medium-assurance verification method.
It returns a proof of the completed check, not a numeric Sybil or uniqueness
score.

Use Selfie Check (Beta) for:
Use Selfie Check for:

* **Liveness detection:** Confirm the user is a real person, not a spoof or injection attack.
* **Abuse resistance:** Add friction to automated and repeated account creation.
Expand All @@ -41,14 +38,17 @@ Use Selfie Check (Beta) for:
Selfie Check has a 90-day inactivity window. After 90 days without use, the
user completes the camera flow again before returning another proof.

<Warning>
Selfie Check (Beta) is access-gated. To use it, [request access](mailto:developers@toolsforhumanity.com)
so the feature flag can be enabled for your app.
</Warning>

Once enabled, anyone with World ID App can use Selfie Check. No Orb, passport or
Anyone with World ID App can use Selfie Check. No Orb, passport or
other prerequisite credential is required.

## Understand the Sybil score

Each Selfie Check response includes a versioned `sybil_score` that
can help an app assess the risk of repeated enrollment. It is a risk signal, not
a uniqueness verdict, and should be considered alongside other evidence. The
response also includes an `integrity_bundle` that lets the Developer Portal
verify it came from an authentic World ID App.

## How it works

Use IDKit to integrate Selfie Check into your application.
Expand Down
Loading
Loading