# **Human Verification & not.bot™ Verify**

Your site has a bot problem. CAPTCHAs used to help. They stopped working when AI learned to solve them faster than humans. Behavioral fingerprinting works until the bot fingerprints a real browser session and replays it. Device attestation narrows the gap, but attestation proves a device exists, not that a person is using it.

not.bot Verify is server software you deploy inside your own infrastructure. It replaces these workarounds with a cryptographic answer: proof that an enrolled human, holding their own device, authenticated with their biometric at the moment of the request. No bot, script, or AI can produce one.

The user's not.bot app handles the cryptographic exchange with your Verify servers. Your site configures which claims to request. The user reviews the request and either approves it or declines. When verification succeeds, your backend receives a signed response containing the requested claims. The entire exchange stays within your infrastructure and the user's device. No traffic hits Julia Social.

## **Three problems, one integration**

Not.bot Verify can request any combination of three different kinds of data from a user:

### **Proof of humanity**

A valid not.bot verification requires a real passport-verified human, on their enrolled device, authenticating with FaceID, TouchID, or their device passcode at the moment your site sends the request. The response carries a `notbot0` credential proving the user passed identity proofing during enrollment.

The cryptographic proof is unforgeable. No bot can produce a valid signature from a key it does not hold, and each verification includes a fresh nonce tied to your specific request, preventing replay.

### **One person, one account**

One person creates ten email addresses and claims your new-customer discount ten times. Another runs a bot farm posting fake reviews. A scalper grabs forty pairs of limited-release sneakers through forty accounts. The common thread: one human pretending to be many, and your site has no way to tell. This problem is called a Sybil attack, and it is growing rapidly across the internet, enabled by AI.

Site Passes provide a strong defense against Sybil attacks. A Site Pass is a token unique to one human on your site. The same person always produces the same Site Pass for your site, regardless of which alias or account they use. A different person produces a different Site Pass on your site. You store the Site Passes you have seen; a duplicate means the same human is back. For each person / site pair, there can only be one Site Pass, guaranteed by math.

Promo-code abuse, fake reviews, free-trial cycling, scalping, airdrop farming, survey manipulation, and multi-account fraud all become impossible when your site can recognize that ten "different" accounts belong to one person. And bots can’t even get in at all.

The privacy properties matter here. Three parties compute the Site Pass through multiparty computation: the user's app, your Verify servers, and Julia Social. The protocol prevents Julia Social from learning which site generated the request, and prevents your site from learning the user's real identity. If two sites collude and compare their Site Passes, the comparison reveals nothing, because each site's passes are cryptographically distinct. Your users get one-account enforcement without cross-site tracking.

### **Age and attribute verification**

You can request specific claims about a user. The most common are age thresholds: `AgeOver18`, `AgeOver21`, or any threshold from 13 to 25\. The user's age credentials are derived from their passport through a three-party multiparty computation among the not.bot app, Julia Social, and the Escrow Server (operated by Praxis, an independent escrow agent). Neither Julia Social nor the Escrow Server learns the user's birthdate during this process. The credentials expire monthly and refresh through the same MPC.

Your site learns the answer to the question you asked. If you request `AgeOver18`, you receive a boolean: true or false. You do not learn the user's actual age, birthdate, or name. If you request `AgeOver21` and the user is 19, the credential value is false and the user can decline the request entirely.

Beyond age thresholds, you can request age range brackets (five-year and ten-year), birthdate information, nationality, gender, and name fields. The user sees the full set of claims you requested and decides whether to respond. Your site receives the claims you asked for, nothing more.

## **Verification levels**

The not.bot verification level tells you how much confidence to place in the person behind a set of claims. Each level represents a different strength of identity proofing during enrollment.

**Notbot0** (available now): The user scanned the NFC chip in a government-issued passport. The app read the chip, and a certified identity-verification partner validated the chip's digital signatures against the ICAO Public Key Directory and confirmed the passport was genuine and unexpired; the Escrow Server (operated by Praxis) confirmed the passport had not been used to create another not.bot identity. No liveness check is performed at this level.

**Notbot1** (planned): The user completed identity proofing at a third-party enrollment partner: a bank, hospital, or similar institution. The partner follows NIST SP 800-63A Identity Assurance Level 2 (IAL2, in-person) procedures under contractual obligation. Julia Social does not audit these partners. The enforcement mechanism is consequence-based: if Julia Social suspects substantial fraudulent issuance, it revokes all credentials that partner issued by melting the partner's issuer key singletons on the Chia blockchain. All affected users must re-enroll elsewhere.

**Notbot2** (planned): The user completed identity proofing at a first-party facility contracting directly with Julia Social.

The levels are cumulative. A Notbot1 holder also holds Notbot0. A Notbot2 holder holds all three. Each level is a distinct verifiable credential with its own URI (`notbot://./v1/notbot0`, `notbot://./v1/notbot1`, `notbot://./v1/notbot2`).

**Guidance for verifiers:** Request the lowest level your use case requires. Most verifiers will request notbot0. Requesting a higher level shrinks the pool of users who can interact with your site.

## **Available claims**

Every enrolled user, regardless of verification level, holds the same set of presentable claims. The verification level describes the strength of the identity proofing behind those claims; it does not gate access to claim types.

**Bot detection:** `Notbot0`, `Notbot1`, `Notbot2`. Request one or more to confirm the user's enrollment level.

**Age thresholds:** `AgeOver13` through `AgeOver25`, plus `AgeOver100`. Each proves the user meets or exceeds the specified age without revealing the actual age. The value is a boolean.

**Age ranges:** Five-year brackets (`AgeRange20To24` through `AgeRange95To99`) and ten-year brackets (`AgeRange20To29` through `AgeRange90To99`). The user's bracket credential is true for the bracket they fall within and false for all others.

**PII fields:** `FirstName`, `GivenNames`, `FamilyName`, `Gender`, `Nationality`, `BirthDate`, `BirthDay`, `BirthMonth`, `BirthYear`. These are derived from the user's passport data. The user sees which PII fields your site requested and can decline the entire request if they are uncomfortable sharing them.

**Site Pass:** `SitePass`. A per-user-per-site pseudonymous identifier. Enable it when you need one-person-one-account enforcement. The same human always produces the same Site Pass for your site.

**honest.bot™:** `honestbot`. Verifies the presenting process holds a valid honest.bot credential. Used when the counterparty is an AI agent rather than a human.

The full claim URI list is in `shared/claim_properties.txt` in the [Julia Web SDK repository](https://github.com/julia-social/julia_web_sdk).

## **The user's experience**

A visitor to your site encounters a verification request in one of two ways.

**On mobile:** Your site presents a universal link, which can be a button in your app or a tappable link in a mobile browser. The link opens the not.bot app. The app displays your site's domain name (cryptographically verified against your business DID's domain credential), the claims you requested, and any message you attached to the request. The user selects which alias to respond with, reviews the requested claims, and either approves or declines the entire request. Approval requires authentication (FaceID, TouchID, or device passcode). The app completes the cryptographic exchange with your Verify servers and returns the user to your site.

**On desktop:** Your site presents a QR code. The user scans it with their phone, which opens the not.bot app. The rest of the flow is the same.

If the user does not have the not.bot app installed, the link redirects to the appropriate app store.

The user sees what you are asking for before they respond. Approval is all-or-nothing: the user responds with the full set of requested claims, or declines the request. There is no partial approval of individual claims.

Alias selection gives the user control over which identity they present to your site. If the user has visited your site before, the app pre-selects the alias they used last time, even if that alias is hidden. If the user has never visited your site, the app defaults to creating a new alias. The user can override either default and pick any existing alias. Your verification request can also specify which alias the user must respond with; if the user does not want to use that alias, they can decline.  If the user does not have that alias, the app will auto-decline the request.

## **The verification flow**

Your backend integrates the Julia Web SDK, a server-side library available in Rust, JavaScript (Express), Python (FastAPI), Java (Spring MVC), and Dart (shelf). The SDK adapter mounts routes into your web framework and handles the cryptographic exchange between the user's app and your signature servers. You write the callbacks that decide what to do with the result.

The flow:

1. Your frontend calls `GET /signature/notbot` on the SDK adapter. The adapter starts a session with one of your signature servers and returns a request ID.  
2. Your frontend builds a universal link URL from the request ID and presents it as a button or tappable link (mobile) or QR code (desktop).  
3. The user's not.bot app opens and completes the cryptographic exchange with your signature server through the SDK adapter. Your backend code does not participate in this exchange.  
4. When verification succeeds, the adapter calls your `onSuccess` callback with the verified response and the user's session. The response contains the user's alias DID, the claims you requested, the Site Pass (if enabled), a timestamp, and the full cryptographic presentation.  
5. Your frontend polls for completion and updates the UI.

The exchange is direct between the user's phone and your servers. No packets reach Julia Social. Your signature servers use their own private keys (stored in your OpenBao instance) to sign the interaction.

Each signature you collect embeds a complete copy of the request that produced it: your signature server's DID, its domain-name credential, the nonce, and the claims you asked for. Your server signed that request, so every verified response countersigns the exact request it answered. This binds the user's signature and your request into one record that neither party can repudiate, and it lets you reconstruct, for any stored signature, which of your servers requested it and what it asked for. No tooling extracts this embedded request today; it rides inside every signature as a latent property.

The SDK is open source, available here:  
[https://github.com/julia-social/julia\_web\_sdk](https://github.com/julia-social/julia_web_sdk)

A minimal integration in JavaScript:

```javascript
const signatureAdapter = createExpressSignatureAdapter({
  signatureClient: createSignatureClient(),
  requestedClaims: [ClaimProperties.Notbot0, ClaimProperties.AgeOver18],
  requireSitePass: true,
  onSuccess: async (verifyResponse, session) => {
    session.juliaSignatureVerification = verifyResponse;
  },
});

app.use(signatureAdapter.router);
signatureAdapter.attachWebsocketHandlers(httpServer);
```

## **Your infrastructure, your data**

not.bot Verify runs inside your data center or cloud account. You deploy four components into your Kubernetes cluster: an admin service, one or more signature servers behind a load balancer, Chia blockchain nodes, and an OpenBao vault holding your DID keys. You provide PostgreSQL and Keycloak.

All four Verify components sit behind your WAF and within your existing security perimeter. They do not expose new public endpoints that you need to protect separately. The Julia Web SDK that connects your application backend to your signature servers is open source, so your security team can audit it before deployment.

No user data flows through Julia Social’s servers. The two data flows that reach Julia Social are aggregate integers: a monthly active user count (once per hour, for billing) and a successful-verification count (once per hour, for diagnostics). No user identifiers, IP addresses, request details, or signature payloads accompany either number.

### **Julia Social is not a supply-chain risk**

Three questions matter when you evaluate a vendor dependency: What happens to my site if their servers go down? What happens to my performance if their servers are slow? What happens to my data if they get breached?

**Availability.** Julia Social's servers going down does not affect your ability to verify users. Your signature servers handle all verification traffic. They contact Julia Social once at startup to acquire an honest.bot credential, then operate with no further Julia Social involvement. If Julia Social is unreachable, running signature servers continue to serve verification requests. Your site stays up. The only operation that blocks is starting new signature servers, which resumes as soon as connectivity returns.

**Performance.** Julia Social's server performance has no effect on your verification latency. The cryptographic exchange runs between the user's phone and your signature servers. Julia Social is not in the request path. Your signature servers support autoscaling behind a load balancer to meet your traffic demands, and the scale ceiling is the number of signature DIDs you mint in the admin interface.

**Breach.** If Julia Social's systems are compromised, no customer data is exposed. Julia Social does not hold your DID keys, your user records, your verification logs, or any content from your verification requests. Julia Social receives two integers per hour from your deployment. An attacker who breaches Julia Social gains access to aggregate usage counts. That is the full extent of the data at risk.

### **Key protection**

Your DID private keys live in OpenBao and never leave it. Keys are generated inside OpenBao through the Chiakeys plugin, are never written to disk in cleartext, and are never exported. All BLS signature operations execute inside OpenBao. Your signature servers send signing requests to OpenBao and receive signed results. The key material never touches your application code, a disk, or your network.

### **Site privacy**

Julia Social does not know which users visit your site. The verification exchange runs between the user's phone and your servers. Julia Social cannot see your verification requests or read the responses, and has no way to determine which not.bot users interact with your deployment. The Site Pass MPC protocol prevents Julia Social from learning which site generated a given request. Julia Social's visibility into your Verify operations is limited to the two hourly aggregate counts.

### **Regulated industries**

The customer-hosted deployment model matters for organizations that cannot route identity verification through a third party. Healthcare organizations bound by HIPAA, financial institutions subject to data residency requirements, and government agencies pursuing FedRAMP can deploy Verify within their own compliance perimeter. No Business Associate Agreement with Julia Social is required because no protected data flows to Julia Social.

Cloud-only verification services cannot serve these buyers. If your verification vendor intermediates every request through their infrastructure, your user data passes through their systems, their jurisdictions, and their security posture. Not.bot Verify eliminates that dependency.

## **Collect only the data you need**

You learn the minimum needed for your use case.

You learn the answers to the claims you asked for. If you asked whether the user is over 18, you learn yes or no. You do not learn their age, their birthdate, or their name, unless you included those claims in your request.

You cannot connect a user's alias on your site to their aliases on other sites. Aliases are cryptographically unlinkable. Site Passes are per-site; comparing Site Passes across sites reveals nothing.

You do not learn the user's verification level unless you asked for it. A user with Notbot1 credentials who receives a Notbot0 request can present Notbot0 without revealing they hold a higher level.

## **Further reading**

- [Overview](http://doc_01_overview.md) for how Verify fits into the not.bot product family.  
- [Content Provenance & Digital Signatures](http://doc_02_content_provenance.md) for the signature model that underpins verification.  
- [Privacy Architecture](http://doc_07_privacy_architecture.md) for the privacy assertions and data flow analysis.  
- [Security Model](http://doc_08_security_model.md) for the security assertions and known weaknesses.  
- The [not.bot Verify: Julia Web SDK Reference](http://doc_18_verify_web_sdk.md) for integration documentation.  
- The [Architecture & Privacy Guide](http://doc_17B_verify_architecture_privacy.md), [Deployment Checklist](http://doc_17D_verify_deployment.md), and [Operations & Reference Guide](http://doc_17E_verify_operations.md) are delivered to Verify subscribers.
