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

Official SDKs

Install an official SDK to start verifying assets in minutes. All SDKs provide the same features: typed clients, automatic retries with exponential backoff, polling helpers, and structured error handling.

LanguagePackageInstall
JavaScript/TypeScript@proof/sdknpm install @proof/sdk
Pythonproof-sdkpip install proof-sdk
PHPproof/sdkcomposer require proof/sdk
Gogithub.com/ProofHoldings/sdk-gogo get github.com/ProofHoldings/sdk-go

Quick Start

JavaScript / TypeScript

typescript
import { Proof } from '@proof/sdk';

const proof = new Proof('pk_live_your_key');

// Create a phone verification
const verification = await proof.verifications.create({
  type: 'phone',
  channel: 'whatsapp',
  identifier: '+37069199199',
});

console.log(verification.challenge.deep_link);
// "https://wa.me/...?text=X7K2M9"

// Poll until verified (or timeout)
const result = await proof.verifications.waitForCompletion(verification.id);
console.log(result.proof.token);
// "eyJhbGciOiJSUzI1NiI..."

Python

python
import asyncio
from proof_sdk import Proof

async def main():
    async with Proof("pk_live_your_key") as proof:
        # Create a phone verification
        verification = await proof.verifications.create(
            type="phone",
            channel="telegram",
            identifier="+37069199199",
        )

        print(verification["challenge"]["deep_link"])

        # Poll until verified
        result = await proof.verifications.wait_for_completion(verification["id"])
        print(result["proof"]["token"])

asyncio.run(main())

PHP

php
use Proof\Proof;

$proof = new Proof('pk_live_your_key');

// Create a phone verification
$verification = $proof->verifications->create([
    'type' => 'phone',
    'channel' => 'sms',
    'identifier' => '+37069199199',
]);

echo $verification['challenge']['code'];

// Poll until verified
$result = $proof->verifications->waitForCompletion($verification['id']);
echo $result['proof']['token'];

Go

go
package main

import (
    "context"
    "fmt"
    proof "github.com/ProofHoldings/sdk-go"
)

func main() {
    client, _ := proof.NewClient("pk_live_your_key")

    // Create a phone verification
    verification, _ := client.Verifications.Create(context.Background(), map[string]any{
        "type":       "phone",
        "channel":    "whatsapp",
        "identifier": "+37069199199",
    })

    fmt.Println(verification["challenge"].(map[string]any)["deep_link"])

    // Poll until verified
    result, _ := client.Verifications.WaitForCompletion(
        context.Background(),
        verification["id"].(string),
        nil,
    )
    fmt.Println(result["proof"].(map[string]any)["token"])
}

Resources

All SDKs expose the same five resources that map to the REST API:

ResourceMethods
verificationscreate, retrieve, list, verify, submit, testVerify, waitForCompletion
sessionscreate, retrieve, waitForCompletion
verificationRequestscreate, retrieve, list, cancel, waitForCompletion
proofsvalidate, revoke, getRevocationList, checkRevocation
webhookDeliverieslist, retrieve, retry

Test Mode (Sandbox)

Use pk_test_ keys to test your integration without sending real messages or consuming quota. All SDK methods work identically — the only addition is testVerify for auto-completing verifications.

typescript
// JavaScript — full test flow
const proof = new Proof('pk_test_your_key');

const verification = await proof.verifications.create({
  type: 'phone',
  channel: 'whatsapp',
  identifier: '+37069199199',
});

// Auto-complete without waiting for user action
const result = await proof.verifications.testVerify(verification.id);
console.log(result.proof_token); // Valid JWT, same as production
python
# Python — full test flow
async with Proof("pk_test_your_key") as proof:
    verification = await proof.verifications.create(
        type="phone", channel="telegram", identifier="+37069199199",
    )
    result = await proof.verifications.test_verify(verification["id"])
    print(result["proof_token"])

Note: Test mode webhooks include "test": true in the payload. Proof tokens are valid signed JWTs with identical format to production.


Create a Verification

All verification types follow the same pattern: create a challenge, present it to the user, then poll or listen for completion.

Request Parameters

ParameterTypeRequiredDescription
typestringYesphone, email, domain, social, wallet
channelstringYesChannel for verification
identifierstringYesThe asset to verify
external_user_idstringNoYour reference ID for the user
callback_urlstringNoWebhook URL for status updates
client_metadataobjectNoCustom metadata

JavaScript

typescript
const verification = await proof.verifications.create({
  type: 'email',
  channel: 'email',
  identifier: '[email protected]',
  external_user_id: 'usr_123',
  callback_url: 'https://yourapp.com/webhook',
});

Python

python
verification = await proof.verifications.create(
    type="email",
    channel="email",
    identifier="[email protected]",
    external_user_id="usr_123",
    callback_url="https://yourapp.com/webhook",
)

PHP

php
$verification = $proof->verifications->create([
    'type' => 'email',
    'channel' => 'email',
    'identifier' => '[email protected]',
    'external_user_id' => 'usr_123',
    'callback_url' => 'https://yourapp.com/webhook',
]);

Go

go
verification, err := client.Verifications.Create(ctx, map[string]any{
    "type":              "email",
    "channel":           "email",
    "identifier":        "[email protected]",
    "external_user_id":  "usr_123",
    "callback_url":      "https://yourapp.com/webhook",
})

Polling

Every resource with a lifecycle (verifications, sessions, verification requests) provides a waitForCompletion method that polls until a terminal status is reached.

Default Settings

SettingDefaultDescription
Interval3 secondsTime between polls
Timeout10 minutesMaximum wait time

Customizing Poll Options

JavaScript:

typescript
const result = await proof.verifications.waitForCompletion('ver_123', {
  interval: 1000,    // 1 second
  timeout: 30000,    // 30 seconds
});

Python:

python
result = await proof.verifications.wait_for_completion(
    "ver_123",
    interval=1.0,   # 1 second
    timeout=30.0,    # 30 seconds
)

PHP:

php
$result = $proof->verifications->waitForCompletion(
    'ver_123',
    interval: 1.0,   // 1 second
    timeout: 30.0,    // 30 seconds
);

Go:

go
result, err := client.Verifications.WaitForCompletion(ctx, "ver_123", &proof.WaitOptions{
    Interval: 1 * time.Second,
    Timeout:  30 * time.Second,
})

Terminal States

ResourceTerminal States
Verificationsverified, failed, expired, revoked
Sessionsverified, failed, expired
Verification Requestscompleted, expired, cancelled

If the timeout is reached before a terminal state, a PollingTimeoutError is thrown.


Error Handling

All SDKs throw typed errors that map to HTTP status codes. Every error includes code, message, statusCode, and optional details and requestId.

HTTP StatusError TypeMeaning
400ValidationErrorInvalid request parameters
401AuthenticationErrorInvalid or missing API key
403ForbiddenErrorInsufficient permissions
404NotFoundErrorResource not found
409ConflictErrorResource conflict
429RateLimitErrorToo many requests
500+ServerErrorServer-side error

JavaScript

typescript
import { ValidationError, NotFoundError } from '@proof/sdk';

try {
  await proof.verifications.retrieve('invalid_id');
} catch (err) {
  if (err instanceof NotFoundError) {
    console.log('Not found:', err.message);
  } else if (err instanceof ValidationError) {
    console.log('Validation:', err.details);
  }
}

Python

python
from proof_sdk.errors import NotFoundError, ValidationError

try:
    await proof.verifications.retrieve("invalid_id")
except NotFoundError as e:
    print(f"Not found: {e.message}")
except ValidationError as e:
    print(f"Validation: {e.details}")

PHP

php
use Proof\Exceptions\NotFoundException;
use Proof\Exceptions\ValidationException;

try {
    $proof->verifications->retrieve('invalid_id');
} catch (NotFoundException $e) {
    echo "Not found: " . $e->getMessage();
} catch (ValidationException $e) {
    echo "Validation: " . print_r($e->details, true);
}

Go

go
import "errors"

result, err := client.Verifications.Retrieve(ctx, "invalid_id")
if err != nil {
    var nfErr *proof.NotFoundError
    var valErr *proof.ValidationError
    if errors.As(err, &nfErr) {
        fmt.Println("Not found:", nfErr.Message)
    } else if errors.As(err, &valErr) {
        fmt.Println("Validation:", valErr.Details)
    }
}

Automatic Retries

All SDKs automatically retry on server errors (5xx) and rate limits (429) with exponential backoff.

SettingDefaultDescription
Max retries2Number of retry attempts
Initial backoff1 secondFirst retry delay
Max backoff10 secondsMaximum retry delay
Backoff formulamin(1s * 2^attempt, 10s)Exponential with cap

Rate limit responses with a Retry-After header are respected automatically.

Configuring Retries

JavaScript:

typescript
const proof = new Proof('pk_live_...', {
  maxRetries: 5,
  timeout: 60000,
});

Python:

python
proof = Proof(
    "pk_live_...",
    max_retries=5,
    timeout=60.0,
)

PHP:

php
$proof = new Proof(
    'pk_live_...',
    maxRetries: 5,
    timeout: 60.0,
);

Go:

go
client, _ := proof.NewClient("pk_live_...",
    proof.WithMaxRetries(5),
    proof.WithTimeout(60 * time.Second),
)

Proof Validation

After a verification succeeds, you receive a signed JWT proof token. You can validate it via the API or offline.

Online Validation

typescript
// JavaScript
const result = await proof.proofs.validate('eyJhbGciOiJSUzI1NiI...');
console.log(result.valid);       // true
console.log(result.verification); // { type, channel, verified_at, ... }
python
# Python
result = await proof.proofs.validate("eyJhbGciOiJSUzI1NiI...")
print(result["valid"])  # True
php
// PHP
$result = $proof->proofs->validate('eyJhbGciOiJSUzI1NiI...');
echo $result['valid'];  // true
go
// Go
result, _ := client.Proofs.Validate(ctx, "eyJhbGciOiJSUzI1NiI...")
fmt.Println(result["valid"])  // true

Offline Validation

Proof tokens are RS256-signed JWTs. For offline validation without API calls:

  1. Fetch public keys from https://api.proof.holdings/.well-known/jwks.json (cache 24h)
  2. Fetch revocation list from /api/v1/proofs/revoked (cache 5min)
  3. Verify JWT signature, check expiry, and check revocation status

See the API Reference for detailed offline verification examples.


Verification Requests

Verification requests let you bundle multiple asset verifications into a single flow. Define required assets and let the user complete them at their own pace.

typescript
// JavaScript
const request = await proof.verificationRequests.create({
  assets: [
    { type: 'phone', required: true },
    { type: 'email', required: true },
    { type: 'domain', required: false },
  ],
  reference_id: 'onboarding_user_123',
});

// Poll for completion
const completed = await proof.verificationRequests.waitForCompletion(request.id);
python
# Python
request = await proof.verification_requests.create(
    assets=[
        {"type": "phone", "required": True},
        {"type": "email", "required": True},
        {"type": "domain", "required": False},
    ],
    reference_id="onboarding_user_123",
)

completed = await proof.verification_requests.wait_for_completion(request["id"])

Webhooks

Monitor webhook delivery status and retry failed deliveries.

typescript
// List failed deliveries
const failed = await proof.webhookDeliveries.list({ status: 'failed' });

// Retry a delivery
await proof.webhookDeliveries.retry('del_123');

Source Code

All SDKs are open source:

  • JavaScript/TypeScript SDK
  • Python SDK
  • PHP SDK
  • Go SDK

Related

API ReferenceMCP ServerIntegrations
Last updated February 6, 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