Further Reading

Protocol Internals

Documentation of certain protocol elements.

The universal link is used so that the World App can recognize a World ID request and handle it correctly. As more clients are built, the universal link will be extended to other wallets. The structure of the universal link is documented here. The source of truth function for this can be found on GitHub.

  • The universal link base is: https://worldcoin.org/verify.
    • If the query string t is equal to wld, the request is using the Wallet Bridge, and the following requirements apply:
      • i is a required query string, which contains the requestId, a UUIDv4 used to identify the request
      • k is a required query string, which contains the URL Base64 encoded AES-GCM key
      • b is an optional query string, which contains the URL of the Wallet Bridge. This defaults to https://bridge.worldcoin.org/.
    • Coming Soon: If the query string t is equal to wmobile, the request is passed directly from a native mobile app to World App, and the following requirements apply:
      • app_id is a required query string. This is the app_id from the Developer Portal, or is equal to self_hosted.
      • action is a required query string, which contains the action ID
      • credential_types is an optional query string, which is a comma-separated string of accepted credentials.
      • signal is an optional query string, which is the signal to be used during proof generation.
      • Either return_to or callback_url is a required query string, which is the URL to return to after the proof is generated.
        • return_to is the URL to return to after the proof is generated. This is used for web-based applications.
        • callback_url is the URL to which the proof will be submitted directly. This is used for native mobile applications.

Wallet Bridge

The Wallet Bridge acts as the intermediary between IDKit and the user's World ID wallet (e.g. World App). It is responsible for relaying a verification request to the user's identity wallet (e.g. World App), and returning the proof to IDKit. A functional diagram is shown here, with a step-by-step explanation to follow:

Wallet Bridge Diagram

All requests to the Wallet Bridge must include a Content-Type: application/json header, a User-Agent header, and a valid JSON body.

  1. IDKit initiates the proof request session on the Wallet Bridge.
    • An app configures and opens IDKit with the required parameters app_id and action, and the optional parameters signal, credential_types, and action_description.
      • If an application is self-hosted, the app_id must be equal to self_hosted. See more details about self-hosted applications here.
    • IDKit generates an AES-GCM key and iv, used to encrypt the verification request. IDKit temporarily stores the key in memory.
    • IDKit encrypts the above parameters with the key and iv, forming the encrypted body of the request to the Wallet Bridge. The iv is included unencrypted in the request body.

      Bridge POST Request

          "app_id": "app_2cde98081f4673c86082d418e6a59f58", // starts with "app_", or is equal to "self_hosted"
          "action": "verify-account",
          "signal": "@username", // optional, defaults to ""
          "credential_types": ["orb", "device"], // optional, defaults to ["orb"]
          "action_description": "Verify your account on an example social media website!", // optional
          "return_to": "https://your-application.com/world-id" // coming soon -- only used when deeplinking to mobile application. must be a valid https URL
    • IDKit sends a POST request to the Bridge: https://bridge.worldcoin.org/request, with the encrypted JSON body described above, thus initiating the session and sending the payload required to generate a proof. The Bridge returns a UUIDv4 requestId to IDKit, which is used to identify the request.
    • The universal link is formed as described in the above section, with the requestId and AES-GCM key included in the query string.
  2. World App or the Worldcoin Simulator (referred to going forward as the client) receives the payload from the bridge, generates the zero-knowledge proof, and returns the proof as a payload in a proof response session on the Bridge.
    • The client receives the universal link via QR code, mobile app deeplink, or pasting from the clipboard (only for the Worldcoin Simulator).
    • The client parses the query string to extract the requestId and key. The key is used to decrypt the payload, and the requestId is used to identify the request.
    • The client sends a GET request to the Wallet Bridge to fetch the payload: https://bridge.worldcoin.org/request/${requestId}. The payload is returned to the client and removed from the Bridge, thus ending the proof request session.
      • The client can optionally send a HEAD request to check if a payload is available for the given requestId. A 200 response indicates a payload is available, while a 404 response indicates no payload is available. The payload is not returned, nor removed from the bridge if it is present.
    • The client parses the payload to get the AES-GCM iv and encrypted body. The key from the universal link and iv are used to decrypt the body, which contains the app_id and action for the request, as well as the optional signal, credential_types, and action_description parameters.
    • The client calculates the externalNullifier from the app_id and action, and generates the zero-knowledge proof using the local Semaphore identity after receiving the user's consent.
    • The client forms the body, which either contains the successful proof or an error response.
    • The client encrypts the body with the key and a newly-generated iv, and sends a PUT request to the Wallet Bridge: https://bridge.worldcoin.org/response/${requestId}, with the new iv alongside the encrypted proof or error as the body.
    • The client must delete any information received from the Bridge, as this could be used to disclose which actions a user has performed.
  3. IDKit polls the Bridge for the response from the client, receives the encrypted response, decrypts it, and calls the callback with the proof or handles the returned error.
    The response will only be returned once, and will be deleted from the Bridge after being returned.
    • IDKit sends a GET request to https://bridge.worldcoin.org/response/${requestId} to check if the proof is available. The JSON returned will include a status field with one of the following values:
      • initialized: The verification request has been received by the Bridge, but not yet received by the client.
      • retrieved: The verification request has been received by the client, but the proof has not yet been returned.
      • completed: The proof (or an error) has been returned by the client, and will be included in this response.

      Bridge Response

          "status": "completed",
          "response": {
              "iv": "a2xhanNsbnJ2b2VzanJ2YTtvanNpZWZubGFp", // Base64 encoded
              "payload": "YWVvbmJybGFpc2VudWJybGFvbTtvdmNubGRrZmE..." // Base64 encoded encrypted body
    • IDKit decrypts the response using the key it generated earlier and the iv included in the response, and either handles the error or calls the configured callback function(s) with the proof.

      Decrypted Bridge Response

          "proof": "0x...",
          "merkle_root": "0x...",
          "nullifier_hash": "0x...",
          "credential_type": "orb", // <-- or "device"
      View the Errors Reference for more information on potential error codes.

External Nullifier

Within Semaphore, the zero knowledge protocol that World ID is based on, the external nullifier is a public 32-byte value that scopes the uniqueness of verifications. It is one of two inputs in the ZK circuit, the other being the secret identity nullifier. These two values are hashed to produce the nullifier hash, which identifies the user.

World ID actions (whether for Sign In or Incognito Actions) are identified by their external nullifiers. This value is derived from the app_id and stringified action passed by IDKit to the World app. Our implementation can be found here.

Generally you won't need to generate the external nullifier yourself. IDKit will handle this automatically, but for custom integrations it's helpful to keep this in mind. When performing a request to the /precheck endpoint, you must pass the valid external nullifier for the given app_id and action. You can accomplish this with the generateExternalNullifier function from IDKit:

import { IDKitInternal } from '@worldcoin/idkit'

const getExternalNullifier = (app_id: string, action: string) => {
	return IDKitInternal.generateExternalNullifier(app_id, action).digest

getExternalNullifier('app_staging_7550e829082fc558e112e0620c1c7a59', 'test action')

OpenID Connect Claims

Within the ID token returned by the World ID provider, a minimal number of OIDC claims are set. This is due to the privacy-focused nature of the protocol. The set claims are:

  • iss: The issuer of the token, always "https://id.worldcoin.org"
  • sub: The unique user identifier for the specific application (this is the nullifier_hash from the World ID ZKP)
  • aud: The identifier of the requesting application. This is always the app_id from the Developer Portal or /register endpoint
  • jti: A unique identifier for this ID token, only used once
  • iat: The timestamp of the token issuance
  • exp: The timestamp of the token's expiration
  • alg: The algorithm used to sign the ID token, only RS256 is supported
  • scope: The scopes requested by the application. Must always contain openid. The profile and email scopes are also supported for compatibility, but use should be avoided.
  • https://worldcoin.org/beta: Describes claims specific to World ID. Deprecated and replaced by https://id.worldcoin.org/v1.
    • likely_human: "strong" or "weak", corresponding to whether the user has strong sybil protection or likelihood of being human. Biometrically verified users have a strong value.
    • credential_type: orb or device, represents the credential the user used to authenticate. In general, for Sign in with World ID, the highest credential available to the user will be used.
  • https://worldcoin.org/v1: Describes claims specific to World ID.
    • verification_level: orb or device, represents the verification level of the user. In general, for Sign in with World ID, the highest verification level available to the user will be used.

Signup Sequencer

World ID utilizes a sequencer to insert identity commitments on-chain, generate inclusion proofs, and track the state of the contract Merkle trees. This is done to simplify the amount of state applications need to handle to work with World ID.

The sequencer utilizes a batching process to reduce gas costs and improve performance. When verifying a proof from the World app, the Developer Portal will query the sequencer to determine if the proof is valid, since the user could be a part of the current batch (which is not yet on-chain). To interact with the sequencer, you can use these endpoints:


  • Orb credential: https://signup-batching.stage-crypto.worldcoin.org
  • Device credential: https://phone-signup.stage-crypto.worldcoin.org


  • Orb credential: https://signup.crypto.worldcoin.org
  • Device credential: https://phone-signup.crypto.worldcoin.org


The sequencer exposes the following endpoints:

  • /inclusionProof: Fetches the inclusion proof for a given identity commitment
  • /insertIdentity: Inserts an identity commitment into the current sequencer batch
  • /verifySemaphoreProof: Verifies the given ZK proof (from the World app) is valid, even if it is not yet on-chain

More details about these endpoints can be found in our Swagger documentation.