APTOGON
ManifestSolutionsDevelopersPricingBlogDonateVerify
← Blog·Developer Guide

Integrating APTOGON in 30 Minutes

A step-by-step walkthrough: from zero to verified human in your web app. Includes React, plain JavaScript, and server-side verification in Node.js and Python.

2025-12-19 · 10 min read

This is a practical guide — no theory, just code. By the end you'll have a working verification flow: the user opens your page, proves they're human, and your server receives a confirmed `human: true` signal with a `trust_band` and anonymous `did_hash`. Total integration time: 20–30 minutes.

Prerequisites

  • An APTOGON account and API keys (get them at /console)
  • A web application — any framework works; examples use React and vanilla JS
  • A server-side component — examples in Node.js and Python

Step 1: Get Your Keys

In the APTOGON console, create an API key pair. You'll receive two keys:

  • `pk_live_…` — the publishable key. Safe to include in browser code.
  • `sk_live_…` — the secret key. Server-side only. Shown once at creation — save it immediately.

Also register your domain in the console. Verification requests from unregistered origins are rejected.

Step 2: Add the Widget (Browser)

The fastest integration is the drop-in script. Add one script tag and one div:

html
<!-- Add before </body> -->
<script src="https://homosapience.org/embed/v1/aptogon.js"
        data-aptogon-key="pk_live_YOUR_KEY"></script>

<!-- Place where you want the button -->
<div
  data-aptogon-verify
  data-on-success="handleVerified"
></div>

<script>
  // Called when user completes verification
  function handleVerified({ token, trust_band, did_hash }) {
    // Send the token to your server for confirmation
    fetch('/api/confirm-human', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token }),
    }).then(r => r.json()).then(result => {
      if (result.human) {
        // Unlock the protected action
        document.getElementById('protected-content').style.display = 'block'
      }
    })
  }
</script>

Step 2 (Alternative): React Component

tsx
import { useEffect, useRef } from 'react'

declare global {
  interface Window { Aptogon: { verify: (opts: object) => Promise<VerifyResult> } }
}

type VerifyResult = {
  token: string
  trust_band: 'newcomer' | 'community' | 'trusted'
  did_hash: string
}

export function HumanVerifyButton({ onVerified }: { onVerified: (r: VerifyResult) => void }) {
  const ref = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const script = document.createElement('script')
    script.src = 'https://homosapience.org/embed/v1/aptogon.js'
    script.setAttribute('data-aptogon-key', process.env.NEXT_PUBLIC_APTOGON_KEY!)
    document.head.appendChild(script)
    return () => { document.head.removeChild(script) }
  }, [])

  const handleClick = async () => {
    const result = await window.Aptogon.verify({
      publishableKey: process.env.NEXT_PUBLIC_APTOGON_KEY!,
    })
    onVerified(result)
  }

  return (
    <button ref={ref} onClick={handleClick}
      style={{ padding: '12px 28px', background: '#7c3aed', color: '#fff',
               borderRadius: 10, fontWeight: 700, border: 'none', cursor: 'pointer' }}>
      ✍️ Verify I'm human
    </button>
  )
}

Step 3: Confirm Server-Side (Node.js)

The token the browser receives is short-lived (60 seconds) and must be confirmed server-side with your secret key. Never skip this step — client-side signals can be forged.

javascript
// pages/api/confirm-human.js  (Next.js) or Express route

export default async function handler(req, res) {
  const { token } = req.body
  if (!token) return res.status(400).json({ error: 'token required' })

  const response = await fetch('https://homosapience.org/api/embed/verify', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.APTOGON_SECRET_KEY}`,
    },
    body: JSON.stringify({ token }),
  })

  if (!response.ok) {
    return res.status(400).json({ human: false, error: 'verification failed' })
  }

  const { human, did_hash, trust_band } = await response.json()

  if (human) {
    // Optional: store did_hash to enforce one-action-per-human
    // await db.recordHumanAction(did_hash, 'survey_response')
  }

  return res.json({ human, trust_band, did_hash })
}

Step 3 (Alternative): Python / FastAPI

python
import httpx
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import os

app = FastAPI()
APTOGON_SECRET = os.environ["APTOGON_SECRET_KEY"]

class VerifyRequest(BaseModel):
    token: str

@app.post("/api/confirm-human")
async def confirm_human(body: VerifyRequest):
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "https://homosapience.org/api/embed/verify",
            headers={
                "Authorization": f"Bearer {APTOGON_SECRET}",
                "Content-Type": "application/json",
            },
            json={"token": body.token},
        )

    if resp.status_code != 200:
        raise HTTPException(status_code=400, detail="verification failed")

    data = resp.json()
    # data = { "human": True, "did_hash": "sha3...", "trust_band": "community" }
    return data

Step 4: Using did_hash for One-Human-One-Action

The `did_hash` is an anonymous fingerprint — a one-way hash with no link to real identity. Store it alongside actions to enforce uniqueness across sessions:

javascript
// Enforce one survey response per human
const existing = await db.query(
  'SELECT id FROM survey_responses WHERE did_hash = $1 AND survey_id = $2',
  [did_hash, surveyId]
)
if (existing.rows.length > 0) {
  return res.status(409).json({ error: 'already_submitted' })
}
await db.query(
  'INSERT INTO survey_responses (did_hash, survey_id, data) VALUES ($1, $2, $3)',
  [did_hash, surveyId, responseData]
)

Step 5: Trust Band Gating (Optional)

Different actions can require different trust levels. Use `trust_band` to gate high-stakes actions:

javascript
// Require 'trusted' or 'community' for governance votes
// Allow 'newcomer' for forum posting
const TRUST_REQUIREMENTS = {
  '/vote': ['trusted', 'community'],
  '/post': ['trusted', 'community', 'newcomer'],
  '/airdrop': ['trusted'],
}

const allowed = TRUST_REQUIREMENTS[action] ?? ['trusted', 'community', 'newcomer']
if (!allowed.includes(trust_band)) {
  return res.status(403).json({
    error: 'trust_band_insufficient',
    required: allowed,
    current: trust_band,
    upgrade_hint: 'Complete more verifications and connect with verified humans to build your bond graph.',
  })
}

Testing in Development

Use `pk_test_…` and `sk_test_…` keys from the console for local development. Test keys return synthetic verification results without going on-chain. The verification flow works identically — same API shape, same trust bands — but `human` is always `true` for any completed gesture.

In production, always use live keys. Test keys accept any gesture including programmatically submitted ones — they are intentionally insecure for testing purposes.

Common Pitfalls

  • **Secret key in browser code**: if you see `sk_live_` in client-side JavaScript, you have a critical security issue. The secret key must only exist on your server.
  • **Skipping server-side verification**: the token returned by the widget must be verified server-side. A token alone does not prove verification — it must be redeemed with the secret key.
  • **Caching did_hash incorrectly**: `did_hash` is stable across sessions for the same device but can change if the user resets their device or browser. Do not use it as a permanent user identifier.
  • **Not registering your domain**: verification requests from unregistered origins return 403. Add all your domains (including `localhost` for development) in the console.

That's the full integration. The verification flow adds roughly 10 seconds to the user experience — the gesture challenge opens in a popup and closes automatically on success. Your application receives a cryptographic proof of humanity, an anonymous identity hash, and a trust band, with zero personal data stored anywhere in the chain.

Try APTOGON free

1,000 verifications/month at no cost. No credit card required.

More articles