not.bot Verify Web SDK Reference

1. Overview

The not.bot Verify Web SDK (julia_web_sdk) is a server-side library that runs in an application backend. It connects the backend to the not.bot Verify signature servers and handles the verification flow: starting signature requests, proxying the cryptographic exchange between the not.bot app and the operator's signature servers, and delivering results back to the application through callbacks.

The SDK is the final integration layer in the not.bot Verify stack. It sits between the operator's application backend and the signature servers deployed via the not.bot Verify Deployment Checklist. No user data or verification traffic ever reaches Julia Social. The entire exchange stays within the operator's infrastructure and the user's device.

Repository: https://github.com/julia-social/julia_web_sdk

Version: 1.0.0 (all languages)

Languages: Rust (reference implementation), JavaScript (Express), Python (FastAPI), Java (Spring MVC), Dart (shelf)


2. Repository Layout

julia_web_sdk/
├── rust/                    # Rust crate, reference implementation
│   ├── Cargo.toml
│   └── src/
│       ├── lib.rs           # ServiceBuilder + route handlers + WebSocket proxy
│       ├── signature_client.rs
│       ├── models.rs
│       ├── claims.rs
│       └── error.rs
├── javascript/              # JavaScript, Express adapter
│   ├── package.json
│   └── src/
│       ├── index.js         # Re-exports all public API
│       ├── models.js        # ClaimProperties enum + Bytes32/ByteArray validators
│       ├── client/
│       │   └── signatureClient.js   # SignatureClient + QR code generation
│       └── server/
│           └── expressAdapter.js    # Express router + WebSocket proxy
├── python/                  # Python, FastAPI adapter
│   ├── pyproject.toml
│   └── julia_web_sdk/
│       ├── __init__.py
│       ├── claims.py        # CLAIM_PROPERTIES dict
│       ├── models.py        # Pydantic models for all request/response types
│       ├── client.py         # SignatureClient + factory function
│       └── server_fastapi.py # FastAPISignatureAdapter + WebSocket proxy
├── java/                    # Java, Spring MVC adapter
│   ├── pom.xml
│   └── src/main/java/social/julia/sdk/
│       ├── ClaimProperties.java
│       ├── JuliaWebSdkException.java
│       ├── client/
│       │   └── SignatureClient.java
│       ├── model/
│       │   └── SignatureModels.java
│       └── server/
│           ├── SignatureAdapterConfig.java     # Builder for adapter configuration
│           ├── SpringSignatureController.java  # REST controller
│           ├── JuliaWebSocketConfig.java       # WebSocket registration
│           └── JuliaWebSocketProxyHandler.java # WS frame proxy
├── dart/                    # Dart, shelf adapter
│   ├── pubspec.yaml
│   └── lib/
│       ├── julia_web_sdk.dart
│       └── src/
│           ├── claims.dart
│           ├── models.dart
│           ├── signature_client.dart
│           └── shelf_adapter.dart
├── examples/                # Runnable examples per language
│   ├── javascript/
│   │   ├── express_server.mjs
│   │   └── client_usage.mjs
│   ├── python/
│   │   ├── fastapi_server.py
│   │   └── client_usage.py
│   ├── java/
│   │   ├── SpringSdkConfig.java
│   │   └── SignatureClientExample.java
│   ├── dart/
│   │   ├── shelf_server.dart
│   │   └── client_usage.dart
│   └── rust/
│       ├── server_integration.rs
│       └── client_usage.rs
└── shared/
    ├── api_contract.md       # Canonical endpoint contract (v1.0.0)
    └── claim_properties.txt  # All claim property URIs

Each language directory also contains a README.md and its lock file. The repository root holds README.md and LICENSE.


3. Two Integration Paths

Configure the adapter with verification requirements and callbacks; it mounts /signature/* routes into the web framework. The adapter handles session tracking, request-to-session mapping, and WebSocket proxying. The operator writes only the callbacks that decide what to do with verified results.

3.2 SignatureClient (lower-level)

An HTTP client that calls signature server endpoints with no adapter layer. Use it for manual control over the flow, automation scripts, or when the framework has no matching adapter.


4. Environment Variables

All languages read the same three environment variables:

Variable Default Purpose
SIGNATURE_HOSTNAME localhost Hostname of the load balancer in front of signature servers
SIGNATURE_PORT 8080 Port of the load balancer
SIGNATURE_API_KEY CHANGE_ME API key that authenticates the backend to signature servers

The SDK uses HTTPS when SIGNATURE_HOSTNAME is anything other than localhost.


5. Adapter Configuration

All language adapters accept the same six configuration options:

Option Type Default Purpose
requestedClaims list of claim property URIs [] Credentials to request from the user (see Section 9)
requireSitePass boolean false When true, generates a per-user-per-site identifier
messageGenerator function → string () => "" Text the user sees in the not.bot app when approving
onSuccess callback(verifyResponse, session) stores in session Called when verification succeeds
onFailure callback(error) no-op Called when verification fails
expireTimeSeconds integer (seconds) 3600 How long a verification request stays valid

Option names appear here in JavaScript form. Python and Rust use snake_case equivalents: requested_claims, require_site_pass, message_generator, on_success, on_failure, and expire_time_seconds (Rust: expire_time).

5.1 onSuccess Callback

The onSuccess callback receives two arguments:

  1. verifyResponse: contains the user's alias DID, any requested claims, the site pass (if requested), a timestamp, and the cryptographic presentation.
  2. session: the originating user's session, looked up via the request-to-session mapping. The adapter resolves this even when the verification response arrives on a different HTTP connection (the not.bot app is a separate client).

5.2 Session Behavior

The adapter maps each request_id to the session_id that initiated it. This mapping lives in memory in the adapter process. If running multiple backend instances behind a load balancer, sticky sessions or a shared session store are required so the verification response routes back to the adapter instance holding the mapping.


6. SDK-Exposed Routes (Adapter Routes)

Once mounted, the adapter serves these routes from the operator's backend:

HTTP Routes

Route Method Called By Purpose
/signature/notbot GET Frontend Starts a verification request. Returns a request_id. Frontend uses this to build the URL or QR code.
/signature/status GET Frontend Returns true if the current session has completed verification. Frontend polls this to update UI.
/signature/notbot/{request_id} POST not.bot app Accepts { "nonce": "<Bytes32>" }. Adapter proxies to upstream, returns { "compressed_presentation": [...] }.
/signature/verify/{request_id} POST not.bot app Accepts { "presentation": [...] }. Adapter proxies verification to upstream, fires onSuccess or onFailure. Returns 204 on success.

WebSocket Routes

Route Header Forwarded Purpose
/signature/honestbot x-presentation-hash Proxied to upstream signature server for honest.bot™ credential verification
/calculate_site_pass x-site-pass Proxied to upstream signature server for site pass computation

WebSocket proxy safety: The adapter's upstream base URL must target the external signature service, not the same host serving SDK adapter routes. Pointing both to the same /signature/* host creates proxy-to-self loops on /signature/honestbot.


7. Upstream Signature Server Endpoints (Called by SDK)

These are the endpoints the SDK calls on the signature servers (via the load balancer):

Endpoint Method Request Body Response
/signature/start POST StartSignatureRequest (see 7.1) { "request_id": "..." }
/signature/presentation POST { "request_id": "...", "nonce": "<Bytes32>" } { "compressed_presentation": [...] }
/signature/verify POST { "request_id": "...", "presentation": [...] } VerifySignatureResponse (see 7.2)
/signature/honestbot WS n/a Binary/text frame proxy
/calculate_site_pass WS n/a Binary/text frame proxy

All upstream HTTP calls include the api-key header with the value from SIGNATURE_API_KEY.

7.1 StartSignatureRequest

{
  "requested_credentials": ["julia://./v1/pii/age_over_18"],
  "require_site_pass": true,
  "required_alias_launcher": null,
  "requested_message": [86, 101, 114, 105, 102, 121],
  "expires": 1730000000
}

Fields:

  • requested_credentials: list of claim property URI strings
  • require_site_pass: boolean
  • required_alias_launcher: optional Bytes32 (null for most flows)
  • requested_message: UTF-8 bytes of the message the user sees in the not.bot app
  • expires: Unix UTC timestamp for request expiry

7.2 VerifySignatureResponse

{
  "alias_did": {
    "launcher_id": [0, 1, 2, ...]
  },
  "site_pass": "0x00112233...",
  "claims": [
    {
      "property": "julia://./v1/pii/age_over_18",
      "value": [1]
    }
  ],
  "timestamp": 1730000000,
  "presentation": [1, 2, 3, ...]
}

Fields:

  • alias_did.launcher_id: 32-byte array identifying the user's alias DID
  • site_pass: Bytes32 hex string, present when require_site_pass was true
  • claims: list of verified claims with property URI and byte-encoded value
  • timestamp: Unix UTC timestamp of verification
  • presentation: the full cryptographic presentation bytes

8. Data Encoding Conventions

Type Encoding Example
Bytes32 0x-prefixed, 64-char lowercase hex "0x00112233..."
Vec<u8> / byte arrays JSON array of integers 0–255 [86, 101, 114]
Timestamps Unix UTC seconds 1730000000

The byte-array encoding matches Rust serde defaults and is consistent across all language implementations.


9. Claim Properties

Claims are identified by URI strings. The full list is in shared/claim_properties.txt. Each claim property maps a short name to a URI.

9.1 Bot Detection

Name URI Category
Notbot0 notbot://./v1/notbot0 Basic bot detection
Notbot1 notbot://./v1/notbot1 Enhanced bot detection
Notbot2 notbot://./v1/notbot2 Highest bot detection

9.2 Honest.bot & Licensing

Name URI Category
Honestbot0 notbot://./v1/honestbot0 Honest.bot credential
SignatureLicense notbot://./v1/signature_license Signature license

9.3 Site & Account

Name URI Category
SitePass julia://./v1/site_pass Per-user-per-site pseudonym
AccountName sha2-256|CBOR://./v1/account_name Account name (hashed)
DomainName sha2-256|CBOR://./v1/domain_name Domain name (hashed)

9.4 PII (Personal Identifiable Information)

Name URI
FirstName julia://./v1/pii/first_name
GivenNames julia://./v1/pii/given_names
FamilyName julia://./v1/pii/family_name
Gender julia://./v1/pii/gender
Nationality julia://./v1/pii/nationality
BirthDate julia://./v1/pii/date_of_birth
BirthDay julia://./v1/pii/birth_day
BirthMonth julia://./v1/pii/birth_month
BirthYear julia://./v1/pii/birth_year

9.5 Age Verification

Threshold claims: AgeOver13 through AgeOver25, plus AgeOver100. Each proves the user is at or above the specified age without revealing the actual age.

Name URI
Age julia://./v1/pii/age
AgeOver13 julia://./v1/pii/age_over_13
AgeOver14 julia://./v1/pii/age_over_14
AgeOver15 julia://./v1/pii/age_over_15
AgeOver16 julia://./v1/pii/age_over_16
AgeOver17 julia://./v1/pii/age_over_17
AgeOver18 julia://./v1/pii/age_over_18
AgeOver19 julia://./v1/pii/age_over_19
AgeOver20 julia://./v1/pii/age_over_20
AgeOver21 julia://./v1/pii/age_over_21
AgeOver22 julia://./v1/pii/age_over_22
AgeOver23 julia://./v1/pii/age_over_23
AgeOver24 julia://./v1/pii/age_over_24
AgeOver25 julia://./v1/pii/age_over_25
AgeOver100 julia://./v1/pii/age_over_100

Range claims: Five-year and ten-year age brackets.

Five-year brackets: AgeRange20To24, AgeRange25To29, AgeRange30To34, AgeRange35To39, AgeRange40To44, AgeRange45To49, AgeRange50To54, AgeRange55To59, AgeRange60To64, AgeRange65To69, AgeRange70To74, AgeRange75To79, AgeRange80To84, AgeRange85To89, AgeRange90To94, AgeRange95To99.

Ten-year brackets: AgeRange20To29, AgeRange30To39, AgeRange40To49, AgeRange50To59, AgeRange60To69, AgeRange70To79, AgeRange80To89, AgeRange90To99.

All age range URIs follow the pattern julia://./v1/pii/age_range_{low}_{high}.


10. Verification Flow (Step by Step)

This is the complete flow as implemented in the SDK adapter code:

  1. Frontend calls GET /signature/notbot on the SDK adapter.
  2. The adapter clears any existing verification from the session.
  3. The adapter calls POST /signature/start on the upstream signature server (via load balancer) with the configured claims, site pass requirement, message bytes, and expiry timestamp.
  4. The signature server returns a request_id.
  5. The adapter stores a request_id → session_id mapping in memory and returns the request_id to the frontend.
  6. The frontend builds an App Link URL from the request_id: https://not.bot/s1/{request_id}/{hostname}/{port} and presents it as a tappable link (mobile) or QR code (desktop).
  7. The not.bot app opens, connects to the SDK adapter endpoints, and begins the cryptographic exchange:
    • POST /signature/notbot/{request_id} with a nonce → adapter proxies to POST /signature/presentation upstream → returns compressed_presentation.
    • POST /signature/verify/{request_id} with the signed presentation → adapter proxies to POST /signature/verify upstream.
    • WebSocket connections on /signature/honestbot and /calculate_site_pass are proxied to upstream.
  8. If upstream verification succeeds, the adapter looks up the originating session via the request_id → session_id mapping and calls onSuccess(verifyResponse, session).
  9. The frontend polls GET /signature/status to detect completion and update the UI.

Network Path

During the verification exchange, traffic flows: user's phone → SDK adapter → signature servers. No packets reach Julia Social at any point. The entire exchange stays within the operator's infrastructure and the user's device.


11. Language-Specific Implementation Details

11.1 Rust

Framework: portfu (custom framework from GalactechsLLC)

Key dependencies:

  • dg_xch_core: Chia blockchain primitives (Bytes32, BLS types)
  • portfu: Web framework with macros, sessions, WebSocket support
  • reqwest: HTTP client with cookie store
  • tokio: Async runtime
  • time: Timestamp handling

Architecture: The ServiceBuilder uses the builder pattern to configure claims, callbacks, and settings, then .build() converts it into a ServiceGroup (portfu's route group). Routes are defined with #[get(...)], #[post(...)], and #[websocket(...)] macros. Session state uses Arc<RwLock<Session>> with portfu's SessionManager.

Notable implementation detail: The Rust implementation uses portfu::wrappers::sessions::Session which stores typed data via session.data.insert(response) / session.data.get::<VerifySignatureResponse>(). The WebSocket proxy uses tokio_tungstenite and runs a polling loop that alternates reads between the client and upstream sockets.

Error handling: Uses a custom Error type with ErrorCode enum covering client errors (4xx range), server errors (5xx range), and Chia-specific errors (6xx range: CLVM, Garbler, Evaluator, BLS signature/credential/proof).

11.2 JavaScript

Framework: Express 5.x

Key dependencies:

  • express ^5.1.0
  • express-session ^1.18.2
  • qrcode ^1.5.4 (QR code generation for getSignatureRequestId())
  • ws ^8.18.3 (WebSocket)

Node.js requirement: >=18

Package type: ES Module ("type": "module")

Exports:

import {
  ClaimProperties,             // Frozen object of all claim URIs
  createSignatureClient,       // Factory function → SignatureClient
  createExpressSignatureAdapter, // Factory → { router, attachWebsocketHandlers }
  SignatureClient,             // Class for direct endpoint calls
  JuliaWebSdkError,           // Custom error with status + body
  bytes32,                    // Validator for Bytes32 hex strings
  byteArray                   // Validator for byte arrays
} from "julia_web_sdk";

Integration pattern:

const signatureAdapter = createExpressSignatureAdapter({
  signatureClient: createSignatureClient(),
  requestedClaims: [ClaimProperties.Notbot0, ClaimProperties.AgeOver18],
  requireSitePass: true,
  messageGenerator: () => "Verifying my identity with example.com",
  onSuccess: async (verifyResponse, session) => {
    session.juliaSignatureVerification = verifyResponse;
  },
  onFailure: async (error) => {
    console.error("verification failure", error);
  },
});

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

Session handling: Uses req.sessionID and req.sessionStore for cross-connection session resolution. The adapter's requestToSignatureSession is an in-memory Map.

WebSocket proxy: Uses the ws library's WebSocketServer with noServer: true. Upgrades are intercepted on the HTTP server's upgrade event. The proxy forwards x-presentation-hash and x-site-pass headers.

SignatureClient extras: The JS client includes getSignatureRequestId() which calls /signature/notbot, builds the https://not.bot/ App Link URL, generates a QR code via the qrcode library, and returns { request_id, qr_code, url }.

11.3 Python

Framework: FastAPI

Key dependencies:

  • fastapi >=0.115.0
  • httpx >=0.27.0 (async HTTP client)
  • pydantic >=2.8.0 (data models)
  • websockets >=13.1 (WebSocket client)

Python requirement: >=3.10

Exports:

from julia_web_sdk import (
    CLAIM_PROPERTIES,                  # Dict[str, str] of all claim URIs
    FastAPISignatureAdapter,          # Adapter class
    create_fastapi_router,            # Shorthand factory
    SignatureClient,                  # Direct HTTP client
    SignatureClientConfig,            # Dataclass for client config
    create_signature_client_from_env, # Factory from env vars
    JuliaWebSdkError,                # Custom exception
)

Integration pattern:

from julia_web_sdk import FastAPISignatureAdapter, create_signature_client_from_env, CLAIM_PROPERTIES

adapter = FastAPISignatureAdapter(
    signature_client=create_signature_client_from_env(),
    requested_claims=[CLAIM_PROPERTIES["Notbot0"], CLAIM_PROPERTIES["AgeOver18"]],
    require_site_pass=True,
    message_generator=lambda: "Verifying my identity with example.com",
)

app.include_router(adapter.router)

Session handling: The Python adapter uses session_cookie_name (default: "session") to read the session cookie value. The session-to-request mapping (session_signatures) is an in-memory dict. An optional resolve_session callback allows custom session resolution for cross-connection flows.

Data models: All request/response types are Pydantic v2 BaseModel subclasses: StartSignatureRequest, StartSignatureResponse, GeneratePresentationRequest, GeneratePresentationResponse, VerifySignatureRequest, VerifySignatureResponse, Claim, DIDInfo, ServerPresentation, ClientPresentation, SignatureRequest.

WebSocket proxy: Uses the websockets library (websockets.connect). Runs two async tasks (from_client and from_upstream) in parallel via asyncio.wait(FIRST_COMPLETED), canceling the other when one completes.

11.4 Java

Framework: Spring MVC 6.x + Spring WebSocket

Key dependencies:

  • jackson-databind 2.17.2
  • spring-webmvc 6.1.12
  • spring-websocket 6.1.12
  • jakarta.servlet-api 6.0.0

Java requirement: 17+

Architecture: Four classes comprise the server adapter:

  • SignatureAdapterConfig: Builder pattern for adapter configuration with functional interfaces OnSuccess, OnFailure, and SessionResolver.
  • SpringSignatureController: @RestController with @GetMapping/@PostMapping handlers for the four HTTP routes.
  • JuliaWebSocketConfig: WebSocketConfigurer that registers the two WebSocket proxy handlers.
  • JuliaWebSocketProxyHandler: AbstractWebSocketHandler that proxies frames in both directions using java.net.http.WebSocket.

Session handling: Uses ConcurrentHashMap<String, String> for request_id → session_id mapping. The SessionResolver functional interface allows custom session lookup.

SignatureClient: Uses java.net.http.HttpClient with async sendAsync() returning CompletableFuture. JSON serialization via Jackson ObjectMapper with PropertyNamingStrategies.SNAKE_CASE.

Integration pattern:

@Bean
public SignatureAdapterConfig signatureAdapterConfig() {
    return SignatureAdapterConfig.builder()
        .requestedClaims(List.of(ClaimProperties.NOTBOT0, ClaimProperties.AGE_OVER_18))
        .requireSitePass(true)
        .messageGenerator(() -> "Verifying my identity with example.com")
        .onSuccess((response, session) -> {
            session.setAttribute("juliaSignatureVerification", response);
        })
        .build();
}

11.5 Dart

Framework: shelf + shelf_router

Key dependencies:

  • http ^1.2.2
  • shelf ^1.4.1
  • shelf_router ^1.1.4
  • shelf_web_socket ^2.0.0
  • web_socket_channel ^3.0.1

Dart SDK requirement: >=3.4.0 <4.0.0

Architecture: Follows the same pattern as other languages: SignatureClient for HTTP calls, ShelfSignatureAdapter for mounting routes. WebSocket proxying uses web_socket_channel for upstream connections and shelf_web_socket for incoming connections.


12. SignatureClient API (All Languages)

The SignatureClient provides both upstream (signature server) and SDK adapter endpoint methods.

12.1 Upstream Methods (require API key)

Method Calls Returns
startSignature(request) POST /signature/start { request_id }
generatePresentation(request) POST /signature/presentation { compressed_presentation }
verifyPresentation(request) POST /signature/verify VerifySignatureResponse

12.2 SDK Adapter Methods (no API key, uses cookies/credentials)

Method Calls Returns
getSignatureRequestId() GET /signature/notbot request_id string (JS also returns QR + URL)
getSignatureStatus() GET /signature/status boolean
generateSignaturePresentation(requestId, nonce) POST /signature/notbot/{id} { compressed_presentation }
verifySignaturePresentation(requestId, presentation) POST /signature/verify/{id} void (204)

12.3 Constructor Options

Option Default Purpose
baseUrl / host + port from env vars Signature server address
apiKey SIGNATURE_API_KEY env var Authentication key
timeout 180 seconds Request timeout
secure true unless localhost Use HTTPS

13. Error Handling

All language implementations throw/raise a custom error type when API calls fail:

Language Error Type Properties
Rust error::Error message: String, code: ErrorCode
JavaScript JuliaWebSdkError message, status, body
Python JuliaWebSdkError message, status_code, body
Java JuliaWebSdkException message, statusCode, body
Dart JuliaWebSdkException message, statusCode, body

The Rust ErrorCode enum is the most detailed: a retry band (300-301), client errors (400-499), server errors (500-518), and Chia/MPC/BLS errors (600-605), plus a catch-all Other (999).

13.1 Adapter Error Responses

Scenario HTTP Status Handler
Upstream signature server unreachable 502 onFailure called
Missing nonce or presentation in request body 400 Direct error response
Upstream verification rejects the presentation 422 onFailure called
onSuccess callback throws 500 onFailure called

14. Quick Start Per Language

JavaScript

cd javascript && npm install
# Set env vars: SIGNATURE_HOSTNAME, SIGNATURE_PORT, SIGNATURE_API_KEY
node examples/javascript/express_server.mjs

Python

cd python && pip install -e .
# Set env vars
uvicorn examples.python.fastapi_server:app

Java

cd java && mvn -q package
# Integrate SignatureAdapterConfig + SpringSignatureController into Spring Boot app

Dart

cd dart && dart pub get
dart run examples/dart/shelf_server.dart

Rust

cd rust && cargo check
# Integrate ServiceBuilder into portfu application

15. Operational Considerations

15.1 Session Stickiness

The request_id → session_id mapping lives in memory. When running multiple backend instances behind a load balancer, either use sticky sessions or a shared session store. Without this, the verification response may arrive at a different adapter instance than the one holding the mapping, and the onSuccess callback will not find the correct session.

15.2 WebSocket Proxy Loops

Configure SIGNATURE_HOSTNAME to point at the signature server load balancer, not at the host running the SDK adapter. If both point at the same host, the WebSocket proxy will loop back to itself on /signature/honestbot.

15.3 Timeout

All language clients default to a 180-second timeout on all HTTP requests to the signature server. This accommodates the time a user may take to approve the verification in the not.bot app.

The JavaScript SignatureClient builds App Links in this format:

https://not.bot/s1/{request_id}/{hostname}/{port}

The https://not.bot/ form is an App Link (iOS Universal Link / Android App Link). On mobile, it opens the not.bot app. On desktop, the URL renders as a QR code the user scans with a phone.


16. Relationship to Other Components

The SDK is the final layer in the not.bot Verify deployment stack:

For the full deployment topology, including PostgreSQL, Keycloak, the operator's edge, and the two hourly aggregate counts to Julia Social, see the Architecture and Privacy Guide §4.

The SDK depends on:

  • Signature servers being deployed and healthy (Architecture and Privacy Guide §3, §6)
  • Signature DID pool being minted (Architecture and Privacy Guide §6, "Scaling")
  • Admin service running (Architecture and Privacy Guide §3)
  • Load balancer / ingress configured to route to signature servers (Architecture and Privacy Guide §4)
  • API key obtained from the admin interface (Deployment Checklist, Phase 9)

The SDK does NOT communicate with Julia Social. All traffic stays within the operator's infrastructure.