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
t
is equal towld
, the request is using the Wallet Bridge, and the following requirements apply:i
is a required query string, which contains therequestId
, a UUIDv4 used to identify the requestk
is a required query string, which contains the URL Base64 encoded AES-GCM keyb
is an optional query string, which contains the URL of the Wallet Bridge. This defaults tohttps://bridge.worldcoin.org/
.
- Coming Soon: If the query string
t
is equal towmobile
, 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 theapp_id
from the Developer Portal, or is equal toself_hosted
.action
is a required query string, which contains the action IDcredential_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
orcallback_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.
- 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_id
andaction
, and the optional parameterssignal
,credential_types
, andaction_description
.- If an application is self-hosted, the
app_id
must be equal toself_hosted
. See more details about self-hosted applications here.
- If an application is self-hosted, the
- IDKit generates an AES-GCM
key
andiv
, used to encrypt the verification request. IDKit temporarily stores thekey
in memory. - IDKit encrypts the above parameters with the
key
andiv
, forming the encrypted body of the request to the Wallet Bridge. Theiv
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 UUIDv4requestId
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-GCMkey
included 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
requestId
andkey
. Thekey
is used to decrypt the payload, and therequestId
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
. A200
response indicates a payload is available, while a404
response 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
iv
and encrypted body. Thekey
from the universal link andiv
are used to decrypt the body, which contains theapp_id
andaction
for the request, as well as the optionalsignal
,credential_types
, andaction_description
parameters. - The client calculates the
externalNullifier
from theapp_id
andaction
, 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-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 astatus
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 theiv
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.
- 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_hash
from the World ID ZKP)aud
: The identifier of the requesting application. This is always theapp_id
from the Developer Portal or/register
endpointjti
: 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
. Theprofile
andemail
scopes 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 astrong
value.credential_type
:orb
ordevice
, 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
ordevice
, 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.