diff --git a/world-id/id/idkit-v4-preview.mdx b/world-id/id/idkit-v4-preview.mdx new file mode 100644 index 0000000..b2d55af --- /dev/null +++ b/world-id/id/idkit-v4-preview.mdx @@ -0,0 +1,282 @@ +--- +title: "IDKit v4 (Preview)" +description: "Preview guide for @worldcoin/idkit-core v4 — the new pure TypeScript SDK for World ID verification." +"og:image": "/images/docs/docs-meta.png" +"twitter:image": "/images/docs/docs-meta.png" +--- + + + IDKit v4 is currently in **preview**. APIs may change before the stable + release. Use `@worldcoin/idkit-core@^4.0` for early access. + + +## Install + + +```bash npm +npm install @worldcoin/idkit-core +``` + +```bash pnpm +pnpm add @worldcoin/idkit-core +``` + +```bash yarn +yarn add @worldcoin/idkit-core +``` + + + +## Prerequisites + +Before writing code you need: + +1. An **app ID** (`app_...`) from the [Developer Portal](https://developer.worldcoin.org). +2. An **RP ID** (`rp_...`) and **signing key** — obtained when you register your Relying Party in the Developer Portal. +3. A backend endpoint that generates RP signatures (see [Backend setup](#backend-setup)). + +## Quick start (browser) + +```typescript +import { IDKit, orbLegacy } from "@worldcoin/idkit-core"; + +// 1. Initialize (call once on page load) +await IDKit.init(); + +// 2. Fetch an RP signature from your backend +const rpSig = await fetch("/api/rp-signature", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ action: "my-action" }), +}).then((r) => r.json()); + +// 3. Create a verification request +const request = await IDKit.request({ + app_id: "app_staging_xxxxx", + action: "my-action", + rp_context: { + rp_id: "rp_xxxxx", + nonce: rpSig.nonce, + created_at: rpSig.created_at, + expires_at: rpSig.expires_at, + signature: rpSig.sig, + }, + allow_legacy_proofs: true, +}).preset(orbLegacy({ signal: "user-123" })); + +// 4. Display QR code for users to scan with World App +console.log("Scan this:", request.connectorURI); + +// 5. Wait for the proof +const result = await request.pollForUpdates({ + pollInterval: 2000, // ms between polls + timeout: 120_000, // 2 minute timeout +}); + +// 6. Send result directly to the Developer Portal v4 verify endpoint +const verification = await fetch("/api/verify-proof", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(result), +}); +``` + + + Use `IDKit.init()` in the browser and `IDKit.initServer()` in Node.js. Both + are safe to call multiple times — initialization only happens once. + + +## Backend setup + +Your backend needs two endpoints: one to generate RP signatures, and one to verify proofs. + +### Generate RP signatures + +The `signRequest` function must run server-side — it requires your RP signing key. + +```typescript +import { IDKit, signRequest } from "@worldcoin/idkit-core"; + +// Initialize for Node.js (call once at startup) +await IDKit.initServer(); + +const SIGNING_KEY = process.env.RP_SIGNING_KEY; // 32-byte hex private key + +app.post("/api/rp-signature", (req, res) => { + const { action } = req.body; + const sig = signRequest(action, SIGNING_KEY); + + res.json({ + sig: sig.sig, + nonce: sig.nonce, + created_at: Number(sig.createdAt), + expires_at: Number(sig.expiresAt), + }); +}); +``` + + + Never expose your RP signing key to client-side code. `signRequest()` will + throw if called outside a server environment. + + +### Verify proofs + +The result object returned by `pollForUpdates()` is shaped exactly as the Developer Portal's [`/v4/verify`](/world-id/reference/api-v4) endpoint expects — just forward it directly: + +```typescript +app.post("/api/verify-proof", async (req, res) => { + const proof = req.body; + + const response = await fetch( + `https://developer.world.org/api/v4/verify/${RP_ID}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(proof), + }, + ); + + const result = await response.json(); + res.status(response.ok ? 200 : 400).json(result); +}); +``` + +## Presets + +Presets are the simplest way to create a request. Each preset configures the credential type and handles both v4 and v3 proof formats. + +| Preset | Credential | Function | +| ------------------------ | ----------------- | ----------------------------------- | +| Orb (Legacy) | `orb` | `orbLegacy({ signal? })` | +| Secure Document (Legacy) | `secure_document` | `secureDocumentLegacy({ signal? })` | +| Document (Legacy) | `document` | `documentLegacy({ signal? })` | + +```typescript +import { IDKit, orbLegacy, secureDocumentLegacy } from "@worldcoin/idkit-core"; + +// Orb verification with a signal +const request = await IDKit.request({ ...config }).preset( + orbLegacy({ signal: "user-123" }), +); + +// Secure document verification +const request2 = await IDKit.request({ ...config }).preset( + secureDocumentLegacy(), +); +``` + +## Request configuration + + + + Your application ID from the Developer Portal. + + + The action identifier. Should match what's configured in the Developer + Portal. + + + RP signature context generated by your backend via `signRequest()`. Contains + `rp_id`, `nonce`, `created_at`, `expires_at`, and `signature`. + + + Set to `true` to accept World ID v3 proofs alongside v4. Set to `false` for + v4-only. + + + Human-readable description shown to users in World App. Recommended for + dynamically created actions. + + + Override the default bridge URL. Only change this if running your own bridge + service. + + + +## Polling for results + +Once you have a request, display `connectorURI` as a QR code and poll for the proof: + +```typescript +// request.connectorURI — show this as a QR code +// request.requestId — unique ID for this verification + +// Option A: Poll continuously (recommended) +const result = await request.pollForUpdates({ + pollInterval: 2000, // default: 1000ms + timeout: 120_000, // default: 300_000ms (5 min) + signal: abortController.signal, // optional cancellation +}); + +// Option B: Manual polling +const status = await request.pollOnce(); +// status.type: "waiting_for_connection" | "awaiting_confirmation" | "confirmed" | "failed" +if (status.type === "confirmed") { + console.log(status.result); +} +``` + +## Response format + +The result from `pollForUpdates()` can be forwarded directly to the Developer Portal [`/v4/verify`](/world-id/reference/api-v4) endpoint — no transformation needed. + +The response shape depends on which World ID protocol version the user's World App uses. + +### World ID v3 vs v4 + +World ID **v3** is the current live protocol. World ID **v4** is the new protocol shipping with this SDK. During the preview period, you will receive **v3 proofs** since World ID v4 has not launched yet. Once v4 rolls out, users with updated World Apps will produce v4 proofs instead. + +Set `allow_legacy_proofs: true` so your app works with both — the Developer Portal `/v4/verify` endpoint handles either format transparently. + +### V3 response (current — World ID 3.0) + +```typescript +interface IDKitResultV3 { + protocol_version: "3.0"; + nonce: string; + action?: string; + responses: ResponseItemV3[]; +} + +interface ResponseItemV3 { + identifier: string; // e.g. "orb", "face" + proof: string; // ABI-encoded proof (hex) + merkle_root: string; // Merkle root (hex) + nullifier: string; // nullifier hash (hex) + signal_hash?: string; // included if signal was provided +} +``` + +### V4 response (upcoming — World ID 4.0) + +```typescript +interface IDKitResultV4 { + protocol_version: "4.0"; + nonce: string; + action: string; + responses: ResponseItemV4[]; +} + +interface ResponseItemV4 { + identifier: string; // e.g. "orb", "face", "document" + proof: string[]; // compressed Groth16 proof + Merkle root + nullifier: string; // RP-scoped nullifier (hex) + signal_hash?: string; // included if signal was provided + issuer_schema_id: number; // 1=orb, 2=face, 3=secure_document, 4=document, 5=device + expires_at_min: number; // credential expiration (unix seconds) +} +``` + +You can check `protocol_version` on the result to determine which format you received. + + + The `signal_hash` field is returned as a convenience — it's the hash of the signal you + provided in the preset. You can also compute it yourself with `hashSignal()`: + +```typescript +import { hashSignal } from "@worldcoin/idkit-core"; +const hash = hashSignal("user-123"); // 0x-prefixed hex string +``` + + diff --git a/world-id/reference/api-v4.mdx b/world-id/reference/api-v4.mdx new file mode 100644 index 0000000..32f006b --- /dev/null +++ b/world-id/reference/api-v4.mdx @@ -0,0 +1,243 @@ +--- +title: "API Reference (v4 Preview)" +description: "Developer Portal v4 API: verify World ID proofs with support for both v3 (cloud) and v4 (on-chain) proof formats." +"og:image": "/images/docs/docs-meta.png" +"twitter:image": "/images/docs/docs-meta.png" +--- + + + The v4 verify endpoint is in **preview**. It is the verification backend for + [IDKit v4](/world-id/id/idkit-v4-preview). + + +## Base URL + +``` +https://developer.world.org +``` + + + All requests must include `Content-Type: application/json` and a valid JSON body. + + +## Verify Proof + + + https://developer.world.org/api/v4/verify/\{rp_id\} + + +Verifies a World ID proof returned by IDKit v4. Accepts both `rp_id` (`rp_xxx`) and `app_id` (`app_xxx`) as the route parameter. + +The request body from `pollForUpdates()` can be forwarded directly to this endpoint — no transformation needed. + +This endpoint handles both World ID v3 (cloud) and v4 (on-chain) proof formats. During the preview period, users will produce **v3 proofs** since World ID v4 has not launched yet. The endpoint verifies either format transparently. + +### Request Body + + + The protocol version. Must be `"3.0"` or `"4.0"`. IDKit sets this automatically. + + + + The nonce used in the RP signature, as returned by IDKit. + + + + The action identifier. Must match the action passed to `IDKit.request()`. + + + + Human-readable description of the action. Used when creating the action for the first time. + + + + Array of proof response items (at least one). The shape of each item depends on `protocol_version`. + + +#### Response item fields (protocol 3.0) + + + Credential type: `"orb"`, `"device"`, or `"face"`. + + + + ABI-encoded zero-knowledge proof (hex string). + + + + Merkle root hash (hex string). + + + + Nullifier hash (hex string, optional `0x` prefix). + + + + Hash of the signal. Defaults to the keccak256 hash of an empty string. + + + + Maximum age of the Merkle root in seconds. Range: 3600 (1 hour) to 604800 (7 days). + + +#### Response item fields (protocol 4.0) + + + Credential type: `"orb"`, `"face"`, `"secure_document"`, `"document"`, or `"device"`. + + + + Array of exactly 5 hex strings: 4 compressed Groth16 proof elements + Merkle root. + + + + RP-scoped nullifier (hex string, optional `0x` prefix). + + + + Credential issuer schema ID: 1=orb, 2=face, 3=secure_document, 4=document, 5=device. + + + + Minimum credential expiration timestamp (unix seconds). + + + + Hash of the signal. Defaults to `"0x0"`. + + +### Request Examples + + +```bash cURL +curl -X POST "https://developer.world.org/api/v4/verify/rp_xxxxx" \ + -H "Content-Type: application/json" \ + -d '{ + "protocol_version": "3.0", + "nonce": "0x1234...", + "action": "my-action", + "responses": [ + { + "identifier": "orb", + "proof": "0x1aa8b8f3b2d2de5ff452c0e1a83e29d6bf46fb83ef35dc5957121ff3d3698a11...", + "merkle_root": "0x2264a66d162d7893e12ea8e3c072c51e785bc085ad655f64c10c1a61e00f0bc2", + "nullifier": "0x2bf8406809dcefb1486dadc96c0a897db9bab002053054cf64272db512c6fbd8", + "signal_hash": "0x00c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4" + } + ] + }' +``` + +```javascript JavaScript +const result = await request.pollForUpdates(); // IDKit result + +const response = await fetch( + `https://developer.world.org/api/v4/verify/${RP_ID}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(result), // forward directly + }, +); +``` + + +### Possible Responses + +- `200 OK` — At least one proof was successfully verified. +- `400 Bad Request` — All proofs failed verification, or the user has already verified for this action. +- `404 Not Found` — App not found or no longer active. + +### Response Examples + + + + ```json + { + "success": true, + "action": "my-action", + "nullifier": "0x2bf8406809dcefb1486dadc96c0a897db9bab002053054cf64272db512c6fbd8", + "created_at": "2025-02-18T11:20:39.530041+00:00", + "environment": "production", + "results": [ + { + "identifier": "orb", + "success": true, + "nullifier": "0x2bf8406809dcefb1486dadc96c0a897db9bab002053054cf64272db512c6fbd8" + } + ], + "message": "Proof verified successfully" + } + ``` + + + ```json + { + "success": false, + "code": "all_verifications_failed", + "detail": "All proof verifications failed.", + "results": [ + { + "identifier": "orb", + "success": false, + "code": "verification_error", + "detail": "On-chain proof verification failed." + } + ] + } + ``` + + + ```json + { + "success": false, + "code": "max_verifications_reached", + "detail": "This person has already verified for this action." + } + ``` + + + ```json + { + "success": false, + "code": "app_not_migrated", + "detail": "This app has not been migrated to World ID 4.0. Please use the v2 verify endpoint." + } + ``` + + + ```json + { + "success": false, + "code": "not_found", + "detail": "App not found. App may be no longer active." + } + ``` + + + +### Response Fields + + + Whether at least one proof was verified successfully. + + + + The action identifier (returned on success). + + + + The nullifier from the first successful proof (returned on success). Use this to track unique users per action. + + + + ISO 8601 timestamp of when the nullifier was recorded. + + + + `"production"` or `"staging"`. In staging, nullifier reuse is allowed. + + + + Per-response verification results. Each entry includes `identifier`, `success`, and optionally `nullifier` (on success) or `code`/`detail` (on failure). +