Further Reading
Protocol Internals
Documentation of certain protocol elements.
Universal Link
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
tis equal towld, the request is using the Wallet Bridge, and the following requirements apply:iis a required query string, which contains therequestId, a UUIDv4 used to identify the requestkis a required query string, which contains the URL Base64 encoded AES-GCM keybis an optional query string, which contains the URL of the Wallet Bridge. This defaults tohttps://bridge.worldcoin.org/.
- Coming Soon: If the query string
tis equal towmobile, the request is passed directly from a native mobile app to World App, and the following requirements apply:app_idis a required query string. This is theapp_idfrom the Developer Portal, or is equal toself_hosted.actionis a required query string, which contains the action IDcredential_typesis an optional query string, which is a comma-separated string of accepted credentials.signalis an optional query string, which is the signal to be used during proof generation.- Either
return_toorcallback_urlis a required query string, which is the URL to return to after the proof is generated.return_tois the URL to return to after the proof is generated. This is used for web-based applications.callback_urlis the URL to which the proof will be submitted directly. This is used for native mobile applications.
- If the query string
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:

All requests to the Wallet Bridge must include a Content-Type: application/json header, a User-Agent header, and a valid JSON body.
- IDKit initiates the proof request session on the Wallet Bridge.
- An app configures and opens IDKit with the required parameters
app_idandaction, and the optional parameterssignal,credential_types, andaction_description.- If an application is self-hosted, the
app_idmust be equal toself_hosted. See more details about self-hosted applications here.
- If an application is self-hosted, the
- IDKit generates an AES-GCM
keyandiv, used to encrypt the verification request. IDKit temporarily stores thekeyin memory. - IDKit encrypts the above parameters with the
keyandiv, forming the encrypted body of the request to the Wallet Bridge. Theivis 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 UUIDv4requestIdto IDKit, which is used to identify the request. - The universal link is formed as described in the above section, with the
requestIdand AES-GCMkeyincluded in the query string.
- An app configures and opens IDKit with the required parameters
- 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
requestIdandkey. Thekeyis used to decrypt the payload, and therequestIdis 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. A200response indicates a payload is available, while a404response indicates no payload is available. The payload is not returned, nor removed from the bridge if it is present.
- The client can optionally send a HEAD request to check if a payload is available for the given
- The client parses the payload to get the AES-GCM
ivand encrypted body. Thekeyfrom the universal link andivare used to decrypt the body, which contains theapp_idandactionfor the request, as well as the optionalsignal,credential_types, andaction_descriptionparameters. - The client calculates the
externalNullifierfrom theapp_idandaction, 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
keyand a newly-generatediv, 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.
- 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 astatusfield 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
keyit generated earlier and theivincluded 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.
- IDKit sends a GET request to
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 thenullifier_hashfrom the World ID ZKP)aud: The identifier of the requesting application. This is always theapp_idfrom the Developer Portal or/registerendpointjti: A unique identifier for this ID token, only used onceiat: The timestamp of the token issuanceexp: The timestamp of the token's expirationalg: The algorithm used to sign the ID token, only RS256 is supportedscope: The scopes requested by the application. Must always containopenid. Theprofileandemailscopes are also supported for compatibility, but use should be avoided.https://worldcoin.org/beta: Describes claims specific to World ID. Deprecated and replaced byhttps://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 astrongvalue.credential_type:orbordevice, 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:orbordevice, 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:
Staging
- Orb credential:
https://signup-batching.stage-crypto.worldcoin.org - Device credential:
https://phone-signup.stage-crypto.worldcoin.org
Production
- Orb credential:
https://signup.crypto.worldcoin.org - Device credential:
https://phone-signup.crypto.worldcoin.org
Endpoints
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.