Human Presence
Human Presence (HP) is a security feature that requires explicit human verification for certain transactions. It uses WebAuthn/FIDO2 to cryptographically prove a human authorized the action.
Overview
The Human Presence flow:
- Agent Request — Agent submits a transaction requiring HP
- HP Required — AutopayOS determines HP verification is needed
- WebAuthn Challenge — User's browser receives a cryptographic challenge
- Signed Response — User authenticates (Touch ID, Face ID, security key)
- HP Verified — Transaction proceeds with proof of human authorization
When HP is required
HP verification is triggered by policy rules:
| Trigger | Example |
|---|---|
| Amount threshold | Transactions over $500 |
| New merchant | First purchase from a merchant |
| High-risk category | Luxury goods, cryptocurrency |
| Anomaly detected | Unusual spending pattern |
| Policy setting | Always require HP |
Configure in your policy:
{
"context": {
"presence": "optional",
"presence_threshold": 500.00,
"presence_on_new_merchant": true
}
}Register a credential
Before using HP, users must register a WebAuthn credential (passkey).
Step 1: Begin registration
<tabs> <tab title="TypeScript">// Request registration options from server
const response = await fetch('/ap2/hp/webauthn/register/begin', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
agentDid: 'did:key:z6Mk...',
deviceName: 'MacBook Pro',
}),
});
const options = await response.json();curl https://api.autopayos.com/ap2/hp/webauthn/register/begin \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"agentDid": "did:key:z6Mk...",
"deviceName": "MacBook Pro"
}'Response:
{
"publicKey": {
"challenge": "base64-encoded-challenge",
"rp": {
"name": "AutopayOS",
"id": "autopayos.com"
},
"user": {
"id": "base64-user-id",
"name": "[email protected]",
"displayName": "User"
},
"pubKeyCredParams": [
{ "type": "public-key", "alg": -7 },
{ "type": "public-key", "alg": -257 }
],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"userVerification": "required"
},
"timeout": 60000
}
}Step 2: Create credential
// Use browser WebAuthn API
const credential = await navigator.credentials.create({
publicKey: {
...options.publicKey,
challenge: base64ToArrayBuffer(options.publicKey.challenge),
user: {
...options.publicKey.user,
id: base64ToArrayBuffer(options.publicKey.user.id),
},
},
});Step 3: Complete registration
const completeResponse = await fetch('/ap2/hp/webauthn/register/complete', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
agentDid: 'did:key:z6Mk...',
credential: {
id: credential.id,
rawId: arrayBufferToBase64(credential.rawId),
type: credential.type,
response: {
clientDataJSON: arrayBufferToBase64(credential.response.clientDataJSON),
attestationObject: arrayBufferToBase64(credential.response.attestationObject),
},
},
}),
});
const result = await completeResponse.json();
console.log('Credential registered:', result.credentialId);Authenticate with HP
When HP is required, authenticate using the registered credential.
Step 1: Begin authentication
const response = await fetch('/ap2/hp/webauthn/auth/begin', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
agentDid: 'did:key:z6Mk...',
intentId: 'intent_abc123', // Optional: link to intent
}),
});
const options = await response.json();Response:
{
"publicKey": {
"challenge": "base64-encoded-challenge",
"rpId": "autopayos.com",
"allowCredentials": [
{
"type": "public-key",
"id": "base64-credential-id",
"transports": ["internal"]
}
],
"userVerification": "required",
"timeout": 60000
},
"challengeId": "hp_xyz789"
}Step 2: Get credential assertion
const assertion = await navigator.credentials.get({
publicKey: {
...options.publicKey,
challenge: base64ToArrayBuffer(options.publicKey.challenge),
allowCredentials: options.publicKey.allowCredentials.map(cred => ({
...cred,
id: base64ToArrayBuffer(cred.id),
})),
},
});Step 3: Complete authentication
const completeResponse = await fetch('/ap2/hp/webauthn/auth/complete', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
agentDid: 'did:key:z6Mk...',
challengeId: options.challengeId,
credential: {
id: assertion.id,
rawId: arrayBufferToBase64(assertion.rawId),
type: assertion.type,
response: {
clientDataJSON: arrayBufferToBase64(assertion.response.clientDataJSON),
authenticatorData: arrayBufferToBase64(assertion.response.authenticatorData),
signature: arrayBufferToBase64(assertion.response.signature),
userHandle: assertion.response.userHandle
? arrayBufferToBase64(assertion.response.userHandle)
: null,
},
},
}),
});
const hpProof = await completeResponse.json();Response (HP Proof):
{
"verified": true,
"hpProof": {
"type": "WebAuthnSignature2023",
"agentDid": "did:key:z6Mk...",
"challengeId": "hp_xyz789",
"credentialId": "cred_abc123",
"timestamp": "2025-12-17T10:00:00Z",
"signature": "base64-signature"
}
}Use HP proof in payment flow
Include the HP proof when verifying a cart:
// Step 1: Request permission
const permission = await client.requestPermission({
agentDid: 'did:key:z6Mk...',
principalDid: 'did:key:z6Mp...',
amount: 750.00, // High amount triggers HP
currency: 'USD',
});
// Step 2: Check if HP required
if (permission.hpRequired) {
console.log('Human presence required');
// Get HP proof from user
const hpProof = await getHumanPresenceProof(permission.hpChallenge);
// Step 3: Verify cart with HP proof
const verification = await client.verifyCart({
intentVc: permission.intentVc,
cartVc: merchantCartVc,
hpProof: hpProof, // Include HP proof
});
} else {
// Normal flow without HP
const verification = await client.verifyCart({
intentVc: permission.intentVc,
cartVc: merchantCartVc,
});
}HP proof structure
{
"type": "WebAuthnSignature2023",
"agentDid": "did:key:z6Mk...",
"challengeId": "hp_xyz789",
"credentialId": "cred_abc123",
"timestamp": "2025-12-17T10:00:00Z",
"freshnessSeconds": 300,
"signature": "base64-signature",
"authenticatorData": "base64-auth-data"
}| Field | Description |
|---|---|
type | Proof type (WebAuthnSignature2023) |
agentDid | Agent the proof is for |
challengeId | Challenge being responded to |
credentialId | WebAuthn credential used |
timestamp | When proof was created |
freshnessSeconds | How long proof is valid |
signature | Cryptographic signature |
Manage credentials
List credentials
const credentials = await client.listHpCredentials(agentDid);
for (const cred of credentials) {
console.log(`${cred.deviceName}: ${cred.credentialId}`);
console.log(` Created: ${cred.createdAt}`);
console.log(` Last used: ${cred.lastUsedAt}`);
}Revoke credential
await client.revokeHpCredential(agentDid, credentialId);React component example
import { useState } from 'react';
import { startRegistration, startAuthentication } from '@simplewebauthn/browser';
function HumanPresenceButton({
agentDid,
onProofObtained
}: {
agentDid: string;
onProofObtained: (proof: HpProof) => void;
}) {
const [isLoading, setIsLoading] = useState(false);
const handleClick = async () => {
setIsLoading(true);
try {
// Begin authentication
const optionsRes = await fetch('/api/hp/auth/begin', {
method: 'POST',
body: JSON.stringify({ agentDid }),
});
const options = await optionsRes.json();
// Get WebAuthn assertion
const assertion = await startAuthentication(options.publicKey);
// Complete authentication
const proofRes = await fetch('/api/hp/auth/complete', {
method: 'POST',
body: JSON.stringify({
agentDid,
challengeId: options.challengeId,
credential: assertion,
}),
});
const { hpProof } = await proofRes.json();
onProofObtained(hpProof);
} catch (error) {
console.error('HP verification failed:', error);
} finally {
setIsLoading(false);
}
};
return (
<button
onClick={handleClick}
disabled={isLoading}
className="hp-button"
>
{isLoading ? 'Verifying...' : '🔐 Verify Identity'}
</button>
);
}Policy configuration
Always require HP
{
"context": {
"presence": "required"
}
}HP above threshold
{
"context": {
"presence": "optional",
"presence_threshold": 500.00
}
}HP for new merchants
{
"context": {
"presence": "optional",
"presence_on_new_merchant": true
}
}HP for specific categories
{
"context": {
"presence": "optional",
"presence_categories": ["luxury", "crypto", "gambling"]
}
}Testing HP
Demo HP endpoint
For testing, use the demo HP proof endpoint:
const demoProof = await client.demoHpProof({
agentDid: 'did:key:z6Mk...',
freshnessSeconds: 300,
});
// Use in cart verification
const verification = await client.verifyCart({
intentVc: permission.intentVc,
cartVc: cartVc,
hpProof: demoProof.hpProof,
});Warning: Demo HP proofs are only accepted in sandbox mode. Production requires real WebAuthn credentials.
Best practices
1. Register multiple credentials
Encourage users to register backup credentials:
const credentials = await client.listHpCredentials(agentDid);
if (credentials.length < 2) {
showNotification('Consider adding a backup passkey for account recovery');
}2. Handle credential loss
If a user loses all credentials, provide account recovery:
// Send recovery email
await client.requestHpRecovery(agentDid, {
email: '[email protected]',
});3. Fresh proofs only
Reject stale HP proofs:
const proofAge = Date.now() - new Date(hpProof.timestamp).getTime();
const maxAge = 5 * 60 * 1000; // 5 minutes
if (proofAge > maxAge) {
throw new Error('HP proof expired, please re-authenticate');
}4. User-friendly prompts
Explain why HP is required:
{hpRequired && (
<div className="hp-notice">
<h3>🔐 Verification Required</h3>
<p>
This transaction requires identity verification because:
{reason === 'HIGH_AMOUNT' && 'The amount exceeds $500'}
{reason === 'NEW_MERCHANT' && 'This is your first purchase from this merchant'}
{reason === 'POLICY' && 'Your security settings require verification'}
</p>
<HumanPresenceButton agentDid={agentDid} onProofObtained={handleProof} />
</div>
)}Supported authenticators
| Type | Examples | Support |
|---|---|---|
| Platform | Touch ID, Face ID, Windows Hello | Full |
| Roaming | YubiKey, Titan Key | Full |
| Hybrid | Phone as authenticator | Full |
Next steps
- Policy configuration — Configure HP triggers
- Building agents — Handle HP in agents
- API Reference — HP endpoint documentation