Worldcoin

Anonymous Actions

Verifying Proofs via API

This section describes how to verify proofs via the Developer Portal API.

You must first pass the proof to your backend when verifying proofs via the API. The user can manipulate information in the frontend, so you the proof must be verified in a trusted environment.

Send the Proof to Your Backend

The proof returned from IDKit includes the merkle_root, nullifier_hash, proof, and credential_type fields:

ISuccessResult

{
    "credential_type": "orb",
    "merkle_root": "0x1f38b57f3bdf96f05ea62fa68814871bf0ca8ce4dbe073d8497d5a6b0a53e5e0",
    "nullifier_hash": "0x0339861e70a9bdb6b01a88c7534a3332db915d3d06511b79a5724221a6958fbe",
    "proof": "0x063942fd7ea1616f17787d2e3374c1826ebcd2d41d2394..."
}

Send these fields to your backend along with the action name. It's recommended to do this in the handleVerify callback function. Any errors thrown in this function will be displayed to the user in IDKit.

Here's an example of how to send the proof to your backend. Assume a backend endpoint /api/verify:

pages/index.tsx

const handleVerify = async (result: ISuccessResult) => { // called by IDKit
  // build the request body
  const reqBody = {
    merkle_root: result.merkle_root,
    nullifier_hash: result.nullifier_hash,
    proof: result.proof,
    credential_type: result.credential_type,
    action: process.env.NEXT_PUBLIC_WLD_ACTION_NAME,
    signal: "",
  };
  const res: Response = await fetch("/api/verify", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(reqBody),
  })
  const data: VerifyReply = await res.json()
  if (res.status == 200) {
    console.log("Verification successful!")
  } else {
    throw new Error(`Error code ${res.status} (${data.code}): ${data.detail ?? "Unknown error."}`); // Throw an error if verification fails
  }
};

Verify the Proof in Your Backend

Here our backend receives the proof sent from the frontend, and sends it to the Developer Portal API for verification. The Developer Portal API will return a 200 response if the proof is valid, and a 400 response with extra error detail if the proof is invalid. We then pass the success or error messages back to our frontend after performing our own backend actions.

pages/api/verify.ts

export type VerifyReply = {
  code: string;
  detail?: string;
};

export default function handler(req: NextApiRequest, res: NextApiResponse<VerifyReply>) {
  fetch(`https://developer.worldcoin.org/api/v1/verify/${process.env.NEXT_PUBLIC_WLD_APP_ID}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    // note that we pass the body of the request directly to the World ID /verify endpoint
    body: JSON.stringify(req.body), 
  }).then((verifyRes) => {
    verifyRes.json().then((wldResponse) => {
      if (verifyRes.status == 200) {
        // this is where you should perform backend actions based on the verified credential
        // i.e. setting a user as "verified" in a database
        res.status(verifyRes.status).send({ code: "success" });
      } else {
        // return the error code and detail from the World ID /verify endpoint to our frontend
        res.status(verifyRes.status).send({ 
          code: wldResponse.code, 
          detail: wldResponse.detail 
        });
      }
    });
  });
}

Post-Verification

If handleVerify is does not throw an error, the user will see a success state and the onSuccess callback will be called when the modal is closed. The onSuccess callback should redirect a user to a success page, or perform any other actions you want to take after a user has been verified.

pages/index.tsx

const onSuccess = (result: ISuccessResult) => {
  // This is where you should perform frontend actions once a user has been verified
  window.alert(`Successfully verified with World ID!
    Your nullifier hash is: ` + result.nullifier_hash);
};

For more information on configuration, see the IDKit and Cloud API reference pages.