Verifier Server
The TLSNotary Verifier Server is a Rust-based HTTP/WebSocket server that acts as the verifier party in Multi-Party Computation TLS (MPC-TLS). It validates cryptographic proofs generated by the browser extension without ever seeing the user's private data.
Why a Verifier Server?
TLSNotary uses Multi-Party Computation (MPC) to generate cryptographic proofs of TLS data. This requires two parties:
- Prover (Browser Extension) — the user generating the proof
- Verifier (This Server) — an independent party that validates the proof
What the Verifier Does
- Participates in MPC-TLS handshake — co-signs the TLS handshake without seeing plaintext data
- Validates transcript integrity — ensures the HTTP request/response hasn't been tampered with
- Generates proof — creates cryptographic proof that data came from a genuine TLS connection
- Forwards requests — acts as a WebSocket proxy to route TLS traffic to target servers
What the Verifier Does NOT See
- User credentials — authentication tokens, cookies, and API keys remain encrypted
- Hidden data — only data marked with
action: 'REVEAL'in handlers is visible - Committed data — data marked with
action: 'PEDERSEN'is sent as a hash commitment, not plaintext
Architecture
Key components:
- Session Management — Thread-safe HashMap stores session configuration
- WebSocket Endpoints —
/session,/verifier, and/proxyhandle different protocol stages - MPC-TLS Verification — Uses
tlsncrate for cryptographic verification - Built-in Proxy — WebSocket-to-TCP bridge forwards requests to target servers
- Webhook System — Fire-and-forget POST notifications with redacted transcripts
Built-in WebSocket Proxy
The recommended approach is to run a WebSocket-to-TCP proxy on the prover's device for maximum control and privacy. However, this requires setup effort from the user. For convenience, the verifier includes a built-in proxy that works out-of-the-box. Any WebSocket-to-TCP proxy can be used — you can run your own, use a third-party service, or use the verifier's built-in proxy.
The verifier includes a built-in proxy that eliminates the need for external proxy services.
Endpoint: ws[s]://<host>/proxy?token=<target-hostname>
| Environment | URL |
|---|---|
| Local | ws://localhost:7047/proxy?token=api.x.com |
| Production | wss://demo.tlsnotary.org/proxy?token=api.x.com |
The token parameter specifies the target server hostname. The proxy:
- Parses hostname and port from the
tokenparameter (defaults to port 443) - Establishes a TCP connection to the target server
- Bridges bidirectionally: WebSocket messages ↔ TCP stream
- Cleans up automatically when either side disconnects
- Logs total bytes forwarded for debugging
API Endpoints
Health Check
GET /health
curl http://localhost:7047/health
# ok
Create Session
WebSocket /session
Creates a new MPC-TLS verification session.
Message (JSON):
{
"maxRecvData": 16384,
"maxSentData": 4096,
"sessionData": {
"userId": "user_123",
"purpose": "twitter_verification"
}
}
| Field | Type | Default | Description |
|---|---|---|---|
maxRecvData | number | 16384 | Maximum bytes the prover can receive |
maxSentData | number | 4096 | Maximum bytes the prover can send |
sessionData | object | — | Custom metadata, included in webhook notifications |
Response:
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000"
}
Session lifecycle:
- Extension opens WebSocket to
/sessionand sends configuration - Server generates UUID, stores config, returns
sessionId - Extension opens second WebSocket to
/verifier?sessionId=<id> - After verification completes, session is cleaned up
Verifier Connection
WebSocket /verifier?sessionId=<session-id>
Connects to an existing session as the verifier party. Validates the sessionId, retrieves session config, spawns the MPC-TLS verification task, and returns redacted transcripts.
Sessions are single-use and automatically removed after the WebSocket closes.
WebSocket Proxy
WebSocket /proxy?token=<host>
See Built-in WebSocket Proxy above.
Session Flow
Complete flow for generating a TLS proof:
Webhook System
The verifier can send proof data to external services via HTTP POST. This enables backend integration for proof verification, storage, or business logic.
Configuration
Webhooks are configured in config.yaml:
webhooks:
# Per-server webhooks (matched by target hostname)
"api.x.com":
url: "https://your-backend.example.com/webhook/twitter"
headers:
Authorization: "Bearer your-secret-token"
X-Source: "tlsn-verifier"
Content-Type: "application/json"
"api.github.com":
url: "https://your-backend.example.com/webhook/github"
headers:
Authorization: "Bearer another-token"
# Wildcard: catch-all for any unmatched server
"*":
url: "https://your-backend.example.com/webhook/default"
headers:
X-Source: "tlsn-verifier"
Matching logic: exact hostname match first, then wildcard "*" fallback, then skip (no error).
Payload
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"sessionData": {
"userId": "user_123",
"requestId": "req_abc",
"purpose": "account_verification"
},
"server_name": "api.x.com",
"redactedTranscript": {
"sent": "<base64-encoded request with redactions>",
"recv": "<base64-encoded response with redactions>"
},
"revealConfig": [
{ "type": "SENT", "part": "START_LINE", "action": "REVEAL" },
{ "type": "RECV", "part": "BODY", "action": "REVEAL", "params": { "type": "json", "path": "screen_name" } }
]
}
The redactedTranscript contains only data marked as revealed in the handlers. Data with action: 'PEDERSEN' or unmarked data is not included.
Example Backend Handler
// Express.js webhook endpoint
app.post('/webhook/twitter', async (req, res) => {
const { sessionId, sessionData, server_name, redactedTranscript, revealConfig } = req.body;
// Decode base64 transcripts
const sentData = Buffer.from(redactedTranscript.sent, 'base64').toString('utf-8');
const recvData = Buffer.from(redactedTranscript.recv, 'base64').toString('utf-8');
console.log('Session:', sessionId);
console.log('User ID:', sessionData.userId);
console.log('Revealed request:', sentData);
console.log('Revealed response:', recvData);
await db.proofs.insert({
sessionId,
userId: sessionData.userId,
server: server_name,
sentData,
recvData,
timestamp: new Date(),
});
res.status(200).json({ received: true });
});
Behavior
Webhooks are fire-and-forget:
- Sent asynchronously in a separate
tokio::spawntask - Errors are logged but do not affect the proof result
- No retry logic (single attempt)
- Default HTTP client timeout (~30 seconds)
If the webhook endpoint is down, the proof still succeeds. Check verifier logs for webhook errors.
Deployment
Local Development
cd packages/verifier
cargo run
# Server starts on http://0.0.0.0:7047
Production Build
cargo build --release
./target/release/tlsn-verifier-server
Docker
For a complete Docker deployment example with docker-compose, see the official docker-compose.yml in the repository.
Quick start with docker-compose:
git clone https://github.com/tlsnotary/tlsn-extension.git
cd tlsn-extension/packages/demo
docker-compose up verifier
# Server starts on http://0.0.0.0:7047
Configuration
Server Settings
- Host:
0.0.0.0(all interfaces) - Port:
7047 - Source:
packages/verifier/src/main.rs
Webhook Configuration
Location: packages/verifier/config.yaml
- Server loads
config.yamlfrom the current working directory - If not found, webhooks are disabled (no error)
- Invalid YAML causes startup failure
CORS
Default: permissive (allows all origins). For production, restrict to specific origins:
let cors = CorsLayer::new()
.allow_origin("https://demo.tlsnotary.org".parse::<HeaderValue>().unwrap())
.allow_methods([Method::GET, Method::POST])
.allow_headers(Any);
Logging
Set via RUST_LOG environment variable:
RUST_LOG=info cargo run # Recommended for production
RUST_LOG=debug cargo run # Detailed logs
RUST_LOG=trace cargo run # Very verbose
Development
Project Structure
packages/verifier/
├── src/
│ ├── main.rs # HTTP server, routing, WebSocket handlers
│ ├── verifier.rs # MPC-TLS verification logic
│ ├── axum_websocket.rs # WebSocket-to-AsyncRead/Write bridge
│ └── tests/ # Integration tests
├── Cargo.toml
├── Cargo.lock
├── config.yaml # Webhook configuration
└── Dockerfile
Testing
cargo test # Run all tests
cargo test -- --nocapture # With output
cargo test test_name # Specific test
Testing with the Extension
-
Start the verifier:
cd packages/verifier
RUST_LOG=debug cargo run -
Build and load the extension:
cd packages/extension
npm run dev
# Load from packages/extension/build/ in chrome://extensions/ -
Open the Developer Console (right-click extension icon), paste plugin code, and click Execute.
-
Check verifier logs:
[Session] New session created: 550e8400-...
[Proxy] New proxy request for host: api.x.com
[550e8400] Successfully proxied 1234 bytes
Verification complete
Common Issues
| Issue | Solution |
|---|---|
Address already in use (os error 48) | Kill existing process: lsof -ti:7047 | xargs kill -9 |
failed to load config.yaml | Create config.yaml or run from the directory containing it |
| WebSocket upgrade fails | Ensure client sends Upgrade: websocket and Connection: Upgrade headers |
| Proxy connection timeout | Check target server is reachable: nc -zv api.x.com 443 |