Proof
Proofof Holdings
How It Works
PricingDocsFAQ
Log InGet Started

Documentation

Browse all docs

Documentation

Learn how to integrate proof.holdings

Core Primitives
The mental model in one page
API Reference
Complete API documentation
SDKs
Official client libraries
Smart Reuse
Skip re-verification with existing proofs
Multi-Profile System
Multiple public profiles per account
Message Templates
Custom branding and message templates per project
Comparison
vs SMS OTP, TOTP, WebAuthn
Pricing
Plans and pricing tiers
Security
Threat model and guarantees
MCP Server
131 tools for AI agents
Integrations
n8n, Zapier, Make, and custom integrations
Resources
GitHub Docs
API Status

proof.holdings Primitives

The complete mental model in one page


One Sentence

proof.holdings answers: "Does this person control this digital asset right now?"

Not identity. The control.


The Abstraction

text
┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│    ASSET ──────► CHALLENGE ──────► USER ACTION ──────► PROOF               │
│                                                                             │
│    "phone"       "Send X7K2M9     User sends         Signed JWT            │
│    "+37069199199" via WhatsApp"   the message        (offline-verifiable)  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

That's it. Four steps. Everything else is implementation detail.


Three Primitives

Your entire integration uses three functions:

text
createChallenge(asset)  →  challenge
verifyProof(proof)      →  { valid, claims }
revokeProof(proofId)    →  { success }

Test mode: All three primitives work identically with pk_test_ keys. No real messages are sent, and you can auto-complete verifications with POST /api/v1/verifications/:id/test-verify. See API Reference — Test Mode.

1. createChallenge

Ask proof.holdings to create a verification challenge.

bash
POST /api/v1/verifications
{
  "type": "phone",
  "channel": "whatsapp",
  "identifier": "+37069199199"
}

Response:

json
{
  "id": "abc123",
  "type": "phone",
  "channel": "whatsapp",
  "status": "pending",
  "identifier": "+37069199199",
  "challenge": {
    "code": "X7K2M9",
    "instruction": "Send X7K2M9 via WhatsApp"
  },
  "deep_link": "https://wa.me/37069199199?text=X7K2M9"
}

2. verifyProof

Verify a proof token. Works offline with JWKS public key.

bash
# Online (via API)
POST /api/v1/proofs/validate
{ "proof_token": "eyJhbG..." }

# Offline (with public key)
jwt.verify(token, publicKey, { algorithms: ['RS256'] })

Response:

json
{
  "valid": true,
  "verification": {
    "type": "phone",
    "channel": "whatsapp",
    "identifier": "+37069199199",
    "verified_at": "2026-02-04T10:00:00Z",
    "expires_at": "2026-03-06T10:00:00Z"
  }
}

3. revokeProof

Invalidate a proof (GDPR deletion, fraud, etc).

bash
POST /api/v1/proofs/{id}/revoke
{ "reason": "user_request" }

Asset Types

TypeChannelsExample
phonewhatsapp, telegram, sms+37069199199
emailemail[email protected]
domaindns, http, email, autoexample.com
socialgithub, google, facebook, x, linkedin, instagram, youtube, tiktok@username
walletethereum, solana, bitcoin0xAb5801...
accountcoinbase, krakenuser@exchange
telegram_bottelegram_bot_token@mybusinessbot

The Innovation: Reverse OTP

Traditional OTP:

text
Server sends SMS to user  →  User enters code  →  Verified
     ($0.05-0.40)              (can be phished)

Reverse OTP (proof.holdings):

text
Server shows code  →  User sends message TO server  →  Verified
     (low cost)          (user-initiated = secure)

Why it matters:

  • Low cost (no per-message fees — you pay per proof, not per SMS)
  • Secure (user initiates, can't be phished)
  • Reliable (no carrier delivery issues for messaging apps)
  • Challenge code can be pre-populated via deep link (e.g., https://wa.me/37069199199?text=X7K2M9)

Proof Token Structure

json
{
  "iss": "proof.holdings",
  "sub": "verification_id",
  "iat": 1707048000,
  "exp": 1709640000,

  "proof_schema_version": "1.0",
  "type": "phone",
  "channel": "whatsapp",
  "identifier_hash": "sha256:1a84d7ea...",
  "verified_at": "2026-02-04T10:00:00Z",
  "user_id": "owner_id"
}

Key properties:

  • identifier_hash — Privacy: raw identifier not exposed
  • exp — Time-bounded: proofs expire
  • Signed with RS256 — Offline-verifiable via JWKS

Offline Verification

Proofs can be verified without calling our API:

javascript
// 1. Fetch public key (cache for 24h)
const jwks = await fetch('https://api.proof.holdings/.well-known/jwks.json');
const publicKey = jwks.keys[0];

// 2. Check revocation list (cache for 5min)
const revoked = await fetch('https://api.proof.holdings/api/v1/proofs/revoked');

// 3. Verify locally
const payload = jwt.verify(token, publicKey, { algorithms: ['RS256'] });

// 4. Check not revoked
const isRevoked = revoked.revoked.some(r => r.proof_id === payload.sub);

// 5. Done - no API call needed
return !isRevoked && payload.exp > Date.now() / 1000;

What proof.holdings IS

  • ✅ Proof-of-control primitive
  • ✅ Verification infrastructure
  • ✅ Passwordless authentication building block
  • ✅ Multi-asset verification API

What proof.holdings is NOT

  • ❌ Identity provider (we don't know WHO)
  • ❌ KYC solution (we don't verify documents)
  • ❌ Fraud prevention (we provide signals)
  • ❌ Permanent ownership (proofs expire)

Complete Flow Diagram

text
┌──────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│  YOUR APP                        PROOF.HOLDINGS                    USER      │
│  ────────                        ──────────────                    ────      │
│                                                                              │
│     │                                  │                             │       │
│     │  POST /verifications             │                             │       │
│     │  { type: "phone",                │                             │       │
│     │    channel: "whatsapp",          │                             │       │
│     │    identifier: "+370..." }       │                             │       │
│     │ ─────────────────────────────►   │                             │       │
│     │                                  │                             │       │
│     │  { challenge: { code: "X7K2M9" },│                             │       │
│     │    deep_link: "wa.me/..." }      │                             │       │
│     │ ◄─────────────────────────────   │                             │       │
│     │                                  │                             │       │
│     │                                  │                             │       │
│     │  Show: "Send X7K2M9             │                             │       │
│     │         via WhatsApp"            │                             │       │
│     │ ─────────────────────────────────────────────────────────────► │       │
│     │                                  │                             │       │
│     │                                  │   User sends "X7K2M9"       │       │
│     │                                  │   via WhatsApp              │       │
│     │                                  │ ◄─────────────────────────  │       │
│     │                                  │                             │       │
│     │                                  │  (Webhook to your app)      │       │
│     │ ◄─────────────────────────────   │                             │       │
│     │  { event: "verified",            │                             │       │
│     │    proof_token: "eyJ..." }       │                             │       │
│     │                                  │                             │       │
│     │                                  │                             │       │
│     │  Verify proof (offline)          │                             │       │
│     │  jwt.verify(token, publicKey)    │                             │       │
│     │                                  │                             │       │
│     │  ✓ DONE                          │                             │       │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

Endpoints Summary

PrimitiveEndpointAuth
createChallengePOST /api/v1/verificationsAPI Key
pollStatusGET /api/v1/verifications/:idAPI Key
verifyProofPOST /api/v1/proofs/validateAPI Key
verifyProof (offline)JWKS + local verifyNone
revokeProofPOST /api/v1/proofs/:id/revokeAPI Key
getRevocationListGET /api/v1/proofs/revokedNone
getPublicKeyGET /.well-known/jwks.jsonNone

Quick Start

bash
# 1. Create verification
curl -X POST https://api.proof.holdings/api/v1/verifications \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"type":"phone","channel":"whatsapp","identifier":"+37069199199"}'

# 2. User sends challenge code via WhatsApp (or clicks deep_link)

# 3. Poll for completion (or use webhook)
curl https://api.proof.holdings/api/v1/verifications/{id} \
  -H "Authorization: Bearer pk_live_..."

# 4. Verify proof offline
curl https://api.proof.holdings/.well-known/jwks.json
# Then: jwt.verify(proof_token, publicKey, {algorithms: ['RS256']})


Proof of Delegation

Proof of Delegation lets users authorize AI agents and automations to contact them. Before an agent can send approval requests via Telegram or WhatsApp, the user must explicitly consent.

Authorization Flow

text
┌──────────────────────────────────────────────────────────────────┐
│                                                                  │
│  YOUR APP               PROOF.HOLDINGS              USER        │
│                                                                  │
│     │  POST /authorizations    │                      │         │
│     │  { channel, scope }      │                      │         │
│     │ ──────────────────────►  │                      │         │
│     │                          │  "Allow approvals?"  │         │
│     │                          │ ──────────────────►  │         │
│     │                          │                      │         │
│     │                          │  User taps "Allow"   │         │
│     │                          │ ◄──────────────────  │         │
│     │                          │                      │         │
│     │  { status: "active",     │                      │         │
│     │    proof_token: "eyJ..." }                      │         │
│     │ ◄──────────────────────  │                      │         │
│     │                          │                      │         │
│     │  ✓ Agent can now send    │                      │         │
│     │    approval requests     │                      │         │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

Authorization Endpoints

ActionEndpointAuth
Request authorizationPOST /api/v1/authorizationsAPI Key
Get authorizationGET /api/v1/authorizations/:idAPI Key
List authorizationsGET /api/v1/authorizationsAPI Key
Revoke authorizationDELETE /api/v1/authorizations/:idAPI Key
Export authorizationsGET /api/v1/authorizations/exportAPI Key

Key Properties

  • Consent-based — Users must explicitly allow agents to contact them
  • Revocable — Users or API consumers can revoke authorization at any time
  • Scoped — Authorizations define what the agent is allowed to do
  • Auditable — Every authorization produces a proof token for compliance
  • GDPR-friendly — Exportable consent records with revocation support

Proof of Action (HITL)

Proof of Action is the human-in-the-loop (HITL) primitive. AI agents and automations request human approval before taking action. The human receives a message via Telegram or WhatsApp, decides to approve or deny, and every decision is cryptographically signed.

HITL Flow

text
┌──────────────────────────────────────────────────────────────────┐
│                                                                  │
│  AI AGENT              PROOF.HOLDINGS              HUMAN        │
│                                                                  │
│     │  POST /confirmations     │                      │         │
│     │  { hitl_id, message,     │                      │         │
│     │    timeout: 43200 }      │                      │         │
│     │ ──────────────────────►  │                      │         │
│     │                          │  "Deploy to prod?"   │         │
│     │                          │  [Approve] [Deny]    │         │
│     │                          │ ──────────────────►  │         │
│     │                          │                      │         │
│     │                          │  Human taps Approve  │         │
│     │                          │  + optional comment   │         │
│     │                          │ ◄──────────────────  │         │
│     │                          │                      │         │
│     │  { status: "approved",   │                      │         │
│     │    proof_token: "eyJ...",│                      │         │
│     │    response: {           │                      │         │
│     │      action: "approve",  │                      │         │
│     │      text: "Ship it!" } }│                      │         │
│     │ ◄──────────────────────  │                      │         │
│     │                          │                      │         │
│     │  ✓ Agent proceeds        │                      │         │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

Setup: HITL Config

Before sending approval requests, create a HITL config that defines the channels and timeout:

bash
POST /api/v1/hitl
{
  "name": "Production Deploys",
  "channels": [
    { "type": "telegram", "config": { "chat_id": "123456789" } }
  ],
  "timeout_seconds": 43200
}

WhatsApp channels accept a phone number, a Business-Scoped User ID (BSUID), or both:

bash
POST /api/v1/hitl
{
  "name": "Wire Approvals",
  "channels": [
    { "type": "whatsapp", "config": { "phone_number": "+15551234567" } }
  ]
}

# Or using a BSUID (Meta's privacy-preserving identifier):
{
  "channels": [
    { "type": "whatsapp", "config": { "bsuid": "US.abcdef1234567890" } }
  ]
}

Request Approval

bash
POST /api/v1/confirmations
{
  "hitl_id": "YOUR_HITL_CONFIG_ID",
  "message": "Deploy v2.1 to production?"
}

Confirmation Endpoints

ActionEndpointAuth
Create HITL configPOST /api/v1/hitlAPI Key
List HITL configsGET /api/v1/hitlAPI Key
Get HITL configGET /api/v1/hitl/:idAPI Key
Update HITL configPATCH /api/v1/hitl/:idAPI Key
Archive HITL configDELETE /api/v1/hitl/:idAPI Key
Request approvalPOST /api/v1/confirmationsAPI Key
Get confirmationGET /api/v1/confirmations/:idAPI Key
List confirmationsGET /api/v1/confirmationsAPI Key

Key Properties

  • Async — Approvals via Telegram (inline keyboards) or WhatsApp (interactive buttons)
  • Configurable timeout — Default 1 hour, max 24 hours; confirmation expires if no response
  • Comments — Humans can add a comment with their approve/deny decision
  • Cryptographic proof — Every decision produces an RS256-signed JWT proof token
  • First-response-wins — Atomic resolution prevents race conditions
  • Webhooks — confirmation.approved and confirmation.denied events fire on resolution

That's It

Three primitives. Offline-verifiable proofs for everything.

text
Proof of Control:    createChallenge → user action → verifyProof
Proof of Delegation: requestAuthorization → user consents → proof token
Proof of Action:     createConfirmation → human decides → proof token

Everything else is implementation detail.


Resources

  • API Reference — Complete endpoint documentation
  • OpenAPI Spec — Machine-readable OpenAPI 3.0 specification
  • JWKS Public Keys — For offline proof verification
  • API Status — Health check endpoint
  • GitHub Docs — Source of truth for all documentation
  • Security & Threat Model — Guarantees, non-guarantees, and attack vectors

Related

API ReferenceSDKsPhone Verification
Last updated March 19, 2026
Proof
Proofof Holdings

Trust infrastructure for humans and AI agents. Verify control, delegate authority, get human approval — with cryptographic proof.

XGitHubLinkedIn
© 2026 Proof of Holdings

A service of LT Telecom (Uždaroji akcinė bendrovė "LT telekomunikacijos")

Product

  • How It Works
  • Verification Types
  • Human Approvals
  • FAQ

Developers

  • Docs
  • MCP Server
  • Integrations
  • OpenAPI Spec
  • GitHub Docs
  • API Status

Company

  • Brand Kit
  • About
  • Privacy
  • Terms