Skip to content
Merged
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
32 changes: 21 additions & 11 deletions openapi/developer-portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@
"action": "my_action",
"responses": [
{
"identifier": "orb",
"identifier": "proof_of_human",
"issuer_schema_id": 1,
"nullifier": "0x2bf8406809dcefb1486dadc96c0a897db9bab002053054cf64272db512c6fbd8",
"expires_at_min": 49012345,
Expand All @@ -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,
Expand Down Expand Up @@ -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."
}
}
},
Expand Down Expand Up @@ -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."
}
}
},
Expand All @@ -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",
Expand All @@ -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."
}
}
},
Expand Down Expand Up @@ -1422,9 +1436,6 @@
"expires_at_min": {
"type": "integer"
},
"credential_genesis_issued_at_min": {
"type": "integer"
},
"proof": {
"type": "array",
"minItems": 5,
Expand Down Expand Up @@ -1468,9 +1479,6 @@
"expires_at_min": {
"type": "integer"
},
"credential_genesis_issued_at_min": {
"type": "integer"
},
"proof": {
"type": "array",
"minItems": 5,
Expand Down Expand Up @@ -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",
Expand Down
15 changes: 9 additions & 6 deletions snippets/idkit-response.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"merkle_root": "0x0abc123...root_hash",
"nullifier": "0x04e5f6...nullifier_hash"
}
]
],
"user_presence_completed": false
}
```

Expand All @@ -25,33 +26,35 @@
"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
}
```

```json title="World ID 4.0 Session"
{
"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
}
```
</CodeGroup>
4 changes: 2 additions & 2 deletions world-id/4-0-migration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 5 additions & 4 deletions world-id/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down Expand Up @@ -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. |
Expand Down
2 changes: 1 addition & 1 deletion world-id/from-idkit-standalone.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
4 changes: 2 additions & 2 deletions world-id/idkit/build-with-llms.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions world-id/idkit/error-codes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
<IDKitRequestWidget
// ...
onError={(errorCode) => {
console.error("IDKit error", errorCode);
onError={(errorCode, debugReport) => {
console.error("IDKit error", errorCode, debugReport);
}}
/>
```
Expand All @@ -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());
}
```
6 changes: 3 additions & 3 deletions world-id/idkit/integration-prompt.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
22 changes: 22 additions & 0 deletions world-id/idkit/javascript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ After `.preset(...)`, you get an `IDKitRequest` object:
- `requestId`
- `pollOnce()`
- `pollUntilCompletion({ pollInterval, timeout })`
- `getDebugReport()`

```ts
import { IDKitErrorCodes } from "@worldcoin/idkit-core";
Expand All @@ -103,6 +104,26 @@ if (!completion.success) {
Outside World App, `connectorURI` is the URL you render as a QR code.
</Note>

## 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`.

<Note>
`setDebug(true)` (or `window.IDKIT_DEBUG = true`) and `isDebug()` toggle verbose `console.debug` logging. This is separate from `getDebugReport()`, which works regardless.
</Note>

## 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.
Expand All @@ -129,6 +150,7 @@ const completion = await request.pollUntilCompletion();
- `requestId`
- `pollOnce()`
- `pollUntilCompletion({ pollInterval, timeout })`
- `getDebugReport()`

### Migrating from QR / connect-URL

Expand Down
1 change: 1 addition & 0 deletions world-id/idkit/kotlin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions world-id/idkit/mini-apps.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}}
/>
```

Expand Down
4 changes: 2 additions & 2 deletions world-id/idkit/onchain-verification.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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]`)
10 changes: 8 additions & 2 deletions world-id/idkit/react.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}}
/>;
```
Expand Down Expand Up @@ -115,6 +115,7 @@ Hook result fields:
- `connectorURI`
- `result`
- `errorCode`
- `getDebugReport()`

## Invite-code mode

Expand Down Expand Up @@ -148,6 +149,7 @@ import { IDKitInviteCodeRequestWidget, selfieCheckLegacy } from "@worldcoin/idki
- `codeExpiresAt`
- `result`
- `errorCode`
- `getDebugReport()`

### Migrating from QR / connect-URL

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