Skip to main content

Overview

Verified Human is the credential the Billions Wallet issues after a user completes document-based verification, an NFC passport, Aadhaar, driving license, or national ID. Any backend can request proof of this credential from a user who holds it, then confirm the proof on its own servers before granting access, distributing a reward, or unlocking a feature. This guide covers the full verification flow: requesting the credential, the user completing it in the Billions Wallet, and confirming the resulting proof on your backend.
This is a reference example to get you running locally and to show the shape of the integration. For a production rollout, reach out to the Billions integration team — they’ll provide the issuer and schema values your verifier needs, and can walk you through rate limits and the persistent storage your deployment requires.

Verification Flow

A verification request is a standard DID-authentication request with one addition: a scope naming the exact credential and issuer you’ll accept as proof.
1

User requests verification

Once a user is signed into your app, your frontend asks the backend for a verification request scoped to that user’s session.
2

Backend builds a scoped authorization request

The backend generates an authorization request and attaches a proof request to it, naming the Verified Human credential it’s asking the user to present.
3

User opens the Billions Wallet

The request is encoded into a Universal Link. Opening it in the Billions Wallet shows the user exactly what’s being asked: proof that they hold a genuine Verified Human credential.
4

Wallet generates the proof

If the user holds the credential, the wallet builds a zero-knowledge proof against it and signs a token, without exposing the underlying document data.
5

Wallet posts to your callback

The wallet sends the signed token to your backend’s callback endpoint, tagged with the session ID from Step 1.
6

Backend verifies and checks for replay

Your backend verifies the token against the request it stored, then checks the proof’s nullifier, a privacy-preserving identifier unique to that person, to confirm the same human hasn’t already verified through a different session.
7

Session marked verified

Once the nullifier check passes, mark the session verified in your store. From here you can gate access, unlock a reward, or anything else that depends on the user being a document-verified human.

Verification Query Structure

This is the structure of the verification query used to make a verification request, naming the credential you’re asking the user to present:
  • circuitId: the circuit used to generate and verify the proof.
  • allowedIssuers: the DIDs allowed to have issued the credential. Verified Human accepts proofs from either of Billions Network’s issuers listed above.
  • context: the schema context for the BasicPerson type, hosted on IPFS.
  • type: the credential type being requested, always BasicPerson for Verified Human.
Need a custom query for your specific requirements, whether that’s a variation on Verified Human, Proof of Uniqueness, or a combination of the two? Reach out to the Billions integration team to discuss it.

Setup Instructions

Prerequisites

  • Public URL for callbacks (use ngrok for local development)
  • A Verifier DID

Example Repository

The steps below follow the same verifier scaffold used for Proof of Uniqueness verification — clone it to follow along. Swapping in the query above is what adapts it to Verified Human.

1. Server and Request Handler

Sets up the server and the endpoint that builds a scoped verification request for each new session. HOST_URL and AUDIENCE_DID are read from configuration; the Verified Human issuer DIDs and schema context are the same values shown in Verification Query Structure above.
Get Your Verifier DID: Sign in to your Billions Wallet and copy your profile DID to use during setup. This is your AUDIENCE_DID — it represents the entity doing the verification, which in this case is your application.
Generate nullifierSessionId fresh for every request, as shown here. Computing it once at server startup and reusing it across sessions breaks the anti-replay check every subsequent verification depends on.

2. Verification Callback

Verifies the proof the wallet sends back, and enforces that a given nullifier, a given real person, can only complete this verification once.
The nullifier is what makes this Sybil-resistant at the application layer: a stable, privacy-preserving identifier for the person behind the credential, not their DID or wallet address. Checking it against userVerificationMap before marking a session verified is what stops the same human from claiming a reward twice under two different accounts.
requestMap, statusMap, and userVerificationMap are all in-memory here and reset on every restart. Replace them with a persistent store (Postgres, Redis) before going to production, or every in-flight session and past verification is lost the moment the process restarts.

Testing Steps

  1. Start the server: node index.js
  2. Visit http://localhost:8080, the example ships a static demo page with a “Verify with Billions” button wired to the flow above.
  3. Open the generated link in the Billions Wallet and complete verification with a Verified Human credential.
  4. Poll GET /api/status/<sessionId> (or watch the demo page) until the status flips to success.

Troubleshooting

HOST_URL must be a publicly reachable URL, not localhost. Use ngrok for local development, and confirm the callback URI includes the sessionId query parameter.
The wallet returned a proof, but not one matching the circuitId and id your backend is looking for. This usually means the id on the proofRequest doesn’t match the sessionId used to build the callback URI. They must be the same value.
The nullifier from this proof already has a verified: true record, meaning this same human has completed verification through a different session already. This is the anti-Sybil check working as intended. If you see this unexpectedly during testing, it likely means you’re reusing the same Verified Human credential across test sessions, which is expected behavior, not a bug.
The resolvers map only knows about the networks it’s configured for (billions:main , privado:main). Confirm your RPC and contract configuration is correct, and that the RPC endpoint is reachable from your server.

Learn More

Verified Human

What the credential is, and how users claim it.