Skip to main content

Step 1: Create an app in the Developer Portal

Create your app in the Developer Portal. Keep these values:
  • app_id
  • rp_id
  • signing_key

Step 2: Generate an RP signature in your backend

Never generate RP signatures on the client and never expose your RP signing key. If the key leaks, attackers can forge requests as your app.
import { signRequest } from "@worldcoin/idkit-core/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 Response.json({
    sig,
    nonce,
    createdAt,
    expiresAt,
  });
}

Step 3: Generate the connect URL and collect proof

  • action: Use one stable string per intent. Examples: "event_march_2026" or "claim". Keep it identical in signing, request creation, and verify.
  • signal: Set this when you need to bind app context into the proof. Examples: user ID, wallet address. Your backend should enforce the same value.
  • constraints: Define which credentials are accepted, then pass that node to .constraints. Use theany or all operators to create complex constraint logic.
import { IDKit, CredentialRequest, any } 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 orbCredential = CredentialRequest("orb", { signal: "user-123" });

const request = await IDKit.request({
  app_id: "app_xxxxx",
  action: "my-action",
  rp_context: {
    rp_id: "rp_xxxxx",
    nonce: rpSig.nonce,
    created_at: rpSig.createdAt,
    expires_at: rpSig.expiresAt,
    signature: rpSig.sig,
  },
  allow_legacy_proofs: false,
  environment: "production",
}).constraints(any(orbCredential));

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

Step 4: 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.post("/api/verify-proof", async (req, res) => {
  const { rp_id, devPortalPayload } = 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(devPortalPayload),
    },
  );

  const payload = await response.json();
  res.status(response.ok ? 200 : response.status).json(payload);
});

Next pages