Skip to main content
IDKit is our solution for integrating World ID. You can use the React SDK for a pre-built widget or the JS and mobile SDKs for a custom integration. To familiarize yourself with the core concepts of World ID, check out this page.
Ensure you are using IDKit v4.0.0 or later.

Step 1: Install IDKit

npm i @worldcoin/idkit-core

Step 2: Create an app in the Developer Portal

Create your app in the Developer Portal. If you’re migrating from an old app you have to go through RP registration by clicking the Enable World ID 4.0 banner. Keep these values:
  • app_id
  • rp_id
  • signing_key - this should be stored as a secret.

Step 3: Generate an RP signature in your backend

Signatures verify that proof requests genuinely come from your app, preventing attackers from performing impersonation attacks.
app/api/rp-signature/route.ts
import { NextResponse } from "next/server";
import { signRequest } from "@worldcoin/idkit/signing";

export async function POST(request: Request): Promise<Response> {
  const { action } = await request.json();
  const signingKey = process.env.RP_SIGNING_KEY!;

  const { sig, nonce, createdAt, expiresAt } = signRequest(action, signingKey);

  return NextResponse.json({
    sig,
    nonce,
    created_at: createdAt,
    expires_at: expiresAt,
  });
}
For RP signature generation, use @worldcoin/idkit-server in JavaScript/TypeScript backends. This is currently only available in JS. If you need support in another language, please reach out on Telegram.
Never generate RP signatures on the client and never expose your RP signing key. If the key leaks, attackers can forge requests from your app.

Step 4: Generate the connect URL and collect proof

You can test during development using the simulator.
import { IDKit, orbLegacy } from "@worldcoin/idkit-core";

const rpSig = await fetch("/api/rp-signature", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ action: "my-action" }),
}).then((r) => r.json());

const request = await IDKit.request({
  // App ID: `app_id` from the Developer Portal
  app_id: "app_xxxxx",
  // Action: Context that scopes what the user is proving uniqueness for
  // e.g., "verify-account-2026" or "claim-airdrop-2026".
  action: "my-action", 
  rp_context: {
    rp_id: "rp_xxxxx", // Your app's `rp_id` from the Developer Portal
    nonce: rpSig.nonce,
    created_at: rpSig.created_at,
    expires_at: rpSig.expires_at,
    signature: rpSig.sig,
  },
  allow_legacy_proofs: true,
  environment: "production", // Only set this to staging for testing with the simulator
  // Signal (optional): Bind specific context into the requested proof. 
  // Examples: user ID, wallet address. Your backend should enforce the same value.
}).preset(orbLegacy({ signal: "local-election-1" }));

const connectUrl = request.connectorURI;
const response = await request.pollUntilCompletion();

IDKit response

After the user completes the verification flow, IDKit returns one of the following response shapes depending on the protocol version and proof type.
{
  "protocol_version": "3.0",
  "nonce": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "action": "my-action",
  "environment": "production",
  "responses": [
    {
      "identifier": "orb",
      "signal_hash": "0x00c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4",
      "proof": "0x1a2b3c...encoded_proof",
      "merkle_root": "0x0abc123...root_hash",
      "nullifier": "0x04e5f6...nullifier_hash"
    }
  ]
}

Step 5: Verify the proof in your backend

After successful completion, send the returned payload to your backend and forward it directly to: POST https://developer.world.org/api/v4/verify/{rp_id}
Forward the IDKit result payload as-is. No field remapping is required.
app/api/verify-proof/route.ts
import { NextResponse } from "next/server";
import type { IDKitResult } from "@worldcoin/idkit";

export async function POST(request: Request): Promise<Response> {
  const { rp_id, idkitResponse } = (await request.json()) as {
    rp_id: string;
    idkitResponse: IDKitResult;
  };

  const response = await fetch(
    `https://developer.world.org/api/v4/verify/${rp_id}`,
    {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(idkitResponse),
    },
  );

  const payload = await response.json();
  return NextResponse.json(payload, { status: response.status });
}

Architecture overview

Next pages