> ## Documentation Index
> Fetch the complete documentation index at: https://tfh-takis-selfie-check-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

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


# SKILL

# Add World ID to your app

> **For developers:** give this file to your coding agent with a one-line ask like "help me add World ID to my app." The agent will figure out the rest.

**What is World ID?** A privacy-preserving way to prove a user is a unique real human. Use it to gate signups, kill bots, prevent multi-accounting, run one-person-one-vote, or distribute scarce rewards fairly. Zero PII shared, zero-knowledge proofs on the wire.

The full integration guide with code for every language lives at [https://docs.world.org/world-id/idkit/integrate](https://docs.world.org/world-id/idkit/integrate). **This file is the meta-guide that gets you to the right code without missteps.**

***

# Agent: read these instructions before doing anything

Take the user from "I want World ID" to a verified working flow. Inspect first, ask only for missing information, and resume from the user's actual state rather than restarting setup.

## Phase 0 — Establish readiness

Before changing code or creating Portal resources:

1. Read the project and inspect connected tools. Do not ask for information you can infer.
2. Establish the current state:
   * integration goal and credential preset
   * new or existing project; client, backend, package manager, and persistence layer
   * Developer Portal account/team, `app_id`, `rp_id`, and action
   * 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 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.

Do not generate or rotate an RP signing key until a server-only secret destination is ready. Rotation invalidates the old signer: explain the impact and get explicit confirmation first.

## Phase 1 — Use the Developer Portal MCP when available

The **World ID Developer Portal MCP** turns the app-creation lifecycle into MCP tools. It replaces dashboard setup with a few tool calls and makes the one-time `signing_key` response explicit so the agent can persist it immediately.

* Source & full tool reference: [https://github.com/worldcoin/developer-portal/tree/main/web/api/mcp](https://github.com/worldcoin/developer-portal/tree/main/web/api/mcp)
* Endpoint: `https://developer.world.org/api/mcp` (transport: streamable-http)
* Auth: `Authorization: Bearer api_<base64(id:secret)>` (Developer Portal team API key)

Prefer the MCP over the dashboard when connected. Start with `get_team_context`, then `get_app_config` for the selected app before creating anything. Clients may namespace MCP tools differently; match these final tool names:

| Dashboard action                                        | MCP tool                           |
| ------------------------------------------------------- | ---------------------------------- |
| List my team's apps                                     | `get_team_context`                 |
| App details / config snapshot                           | `get_app_config`                   |
| Create a new app                                        | `create_app`                       |
| Configure World ID (mint RP, get signing key)           | `configure_world_id`               |
| Create or update an action                              | `create_world_id_action`           |
| Check on-chain registration status                      | `get_world_id_registration_status` |
| Read the current signer address (never the private key) | `get_world_id_signing_key`         |
| Rotate signing key (returns the new private key once)   | `rotate_world_id_signing_key`      |

If the MCP is not connected, explain that it can inspect and configure the user's Portal resources directly. Offer the client-specific setup at [https://docs.world.org/model-context-protocol/developer-portal](https://docs.world.org/model-context-protocol/developer-portal), or let the user continue through the dashboard. **Do not silently choose a path or block an otherwise valid integration on MCP setup.**

## Phase 2 — Understand the project before writing anything

Two paths land here. Identify which:

* **Path 1 — Building from scratch** (demo, hackathon, new product). You control the full stack. Default to **Next.js (App Router) + TypeScript** as the golden path — every official sample uses it and it's the fastest way to a working flow.
* **Path 2 — Integrating into an existing stack.** Read the codebase first. Confirm the **frontend** (web / mobile) and the **backend** (where secrets live), then pick the right SDK pair below.

Supported clients and backends — all interoperable:

| Client                 | SDK                                                                 |
| ---------------------- | ------------------------------------------------------------------- |
| React / Next.js        | `@worldcoin/idkit` (pre-built widget)                               |
| Other JS / vanilla web | `@worldcoin/idkit-core`                                             |
| iOS                    | [`worldcoin/idkit-swift`](https://github.com/worldcoin/idkit-swift) |
| Android                | `com.worldcoin:idkit` (Gradle)                                      |

| Backend        | RP signing                                                                                 | Proof verify                                           |
| -------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------ |
| Node / Next.js | `@worldcoin/idkit-core/signing`                                                            | `fetch` to `developer.world.org/api/v4/verify/{rp_id}` |
| Go             | server SDK in `/world-id/idkit/go`                                                         | same HTTPS POST                                        |
| Anything else  | re-implement signing ([signatures spec](https://docs.world.org/world-id/idkit/signatures)) | same HTTPS POST                                        |

**DO NOT pin SDKs to `^2.x` or `^3.x`** — those examples litter the public internet and **will not work** with v4. The API was redesigned. Pin **`^4.x`** and verify with `npm view @worldcoin/idkit version` if uncertain.

## Phase 3 — Pick the credential and confirm access

The credential decides what the user proves. Nail this down before scaffolding — switching later means a new action.

| Preset                                         | What it proves                                                | Use it for                                                                                                                                 |
| ---------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **`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 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).

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

**Copy this checklist into your TODO and update it as you go.** Don't move on with an unchecked step.

* [ ] Step 1 — Install IDKit
* [ ] Step 2 — Create or reuse app + RP + action (store any newly generated signing key immediately)
* [ ] Step 3 — Sign RP request in backend
* [ ] Step 4 — Open IDKit widget on client
* [ ] Step 5 — Verify proof in backend
* [ ] Step 6 — Store nullifier with UNIQUE constraint

1. **Install IDKit** — `^4.x`, the right package for the platform (table in Phase 2).
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. 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

* The **production** World ID app only signs **production** proofs.
* A **staging** action only verifies against the World ID **Simulator** ([https://simulator.worldcoin.org](https://simulator.worldcoin.org)).
* The IDKit `environment` prop, the action's `environment`, and the simulator-vs-real-app choice **must all match.**

**CRITICAL: if real users will scan with their phones, the action environment must be `production`.** A staging action with the production World ID app will silently produce zero proofs and look like a frontend bug. If the user needs both simulator testing and real-device QA, create separate staging and production actions.

## Phase 6 — Test the integration end-to-end

Do not declare the integration complete from compilation or Portal configuration alone. Test the selected path and record evidence:

* [ ] RP-signing endpoint succeeds without exposing or logging secrets.
* [ ] Widget/request opens in the selected environment.
* [ ] The selected credential completes with the staging simulator or production World ID as intended.
* [ ] 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.
* [ ] 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.

If a check fails, use the concrete error, response payload, or execution trace to fix the root cause, then rerun the failed check and any downstream checks. Do not weaken a security or access gate just to make validation pass.

## Phase 7 — Gotchas and recovery

Surface these proactively when you see the matching symptom — don't make the user search for the cause.

| Symptom                                                                             | Cause                                                                        | Recovery                                                                                                                                                                               |
| ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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.   |
| `/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 the intended environment is `registered`. Production must be registered before launch.                                                   |
| Signing key is gone (lost `.env`, never persisted)                                  | `signing_key.private_key` is returned exactly once at create/rotate time     | Prepare the replacement secret destination, explain the invalidation impact, get confirmation, then call `rotate_world_id_signing_key`, persist the new key immediately, and redeploy. |
| 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 `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.                                                                                                             |
| Existing app rejects World ID config                                                | App was created as `mini-app` (or wrong `engine`)                            | `app_mode` is fixed at create time. Create a **new** app with the right mode (`external` for IDKit, `mini-app` for MiniKit).                                                           |

## Phase 8 — Hand off with evidence

Before declaring done, report:

* the readiness state you found and which path you followed
* files and routes changed
* `app_id`, `rp_id`, action, and environments configured—never secret values
* commands and tests run, with results
* simulator, phone, or production checks still outstanding
* blockers and exact recovery steps

For launch readiness, also confirm:

* [ ] `RP_SIGNING_KEY` is server-only, in a real secret store, never logged.
* [ ] Action exists in `production` (not just `staging`).
* [ ] Nullifier persistence is real — DB-backed, `NUMERIC(78, 0)`, `UNIQUE (action, nullifier)`. The in-memory `Set` in samples is illustrative only.
* [ ] On-chain registration polled to `registered` (`get_world_id_registration_status`) before launch.
* [ ] The user knows that a leaked or lost signing key requires confirmed rotation and redeployment.

## Reference

* Full integration guide (code for every language): [https://docs.world.org/world-id/idkit/integrate](https://docs.world.org/world-id/idkit/integrate)
* Core concepts: [https://docs.world.org/world-id/concepts](https://docs.world.org/world-id/concepts)
* Credentials reference: [https://docs.world.org/world-id/credentials](https://docs.world.org/world-id/credentials)
* RP signature spec (for non-Node backends): [https://docs.world.org/world-id/idkit/signatures](https://docs.world.org/world-id/idkit/signatures)
* Error codes: [https://docs.world.org/world-id/idkit/error-codes](https://docs.world.org/world-id/idkit/error-codes)
* Developer Portal MCP: [https://github.com/worldcoin/developer-portal/tree/main/web/api/mcp](https://github.com/worldcoin/developer-portal/tree/main/web/api/mcp)
* Developer Portal: [https://developer.world.org](https://developer.world.org)
* World ID Simulator (staging only): [https://simulator.worldcoin.org](https://simulator.worldcoin.org)

***

# TL;DR

**Developer:** give this file to a coding agent with "Help me add World ID."
**Agent:** establish readiness → inspect or configure Portal resources → understand the stack → pick a credential and confirm access → implement → match environments → test end-to-end → hand off with evidence.
