x401
Protect your endpoints from agentic traffic with Digital Credentials.
AI agents now browse, buy, sign, and call APIs on behalf of people. Bot detection can only tell you a request is not human. x401 tells you which person stands behind it, through Digital Credentials.
x401 is an open protocol that extends the HTTP 401 flow. When a user agent requests a protected resource without identity, the endpoint responds 401 with an x401 PROOF-REQUEST header: a Digital Credentials Presentation request.
The user agent delegates the request to the user's Wallet. The user authenticates to their Wallet and approves the credential presentation. The user agent retries the protected resource with a PROOF-RESPONSE header: a Digital Credentials Presentation response.
After cryptographic verification of the presented Verifiable Credential, the user gets access to the protected resource. x401 lets you seamlessly harness the power of Proof's Digital Credentials through Verify Identity and Sign Transactions.
x401 is built on top of Digital Credentials and is wallet-agnostic; see the specification. Proof's Cloud Wallet supports x401 through OpenID for Verifiable Presentations (OID4VP).
Proof Cloud Wallet is also available as a Web Component for your website!
Getting started
Implement x401 with Proof's Cloud Wallet and start verifying identities immediately.
Ask your agent to protect your endpoints behind x401 using Proof's Cloud Wallet:
Protect my API endpoints behind the x401 protocol, using Proof's Cloud Wallet, following the instructions at https://api.proof.com/.well-known/agent-skills/setup-x401-verifier/SKILL.md
Or follow the step-by-step guide.
1. Install dependencies
npm install express @proof.com/x401-node @proof.com/proof-vc-server2. Generate a key pair
The key pair signs your OID4VP requests and your enrollment. It uses P-256.
openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem3. Host an OAuth Client ID Metadata Document
Create an OAuth Client ID Metadata Document (CIMD) and host it at an endpoint you own, publicly accessible over HTTPS. The CLIENT_ID is the exact URL of the document.
import express from "express";
import { readFileSync } from "node:fs";
import { createPrivateKey, createPublicKey } from "node:crypto";
import { agentsTrustListUrl, createClientIdMetadataDocument } from "@proof.com/proof-vc-server";
// Your domain host
const domain = "";
// The exact URL at which the Client ID Metadata Document is hosted.
const CLIENT_ID = `https://${domain}/.well-known/proof-client.json`;
const privateKey = createPrivateKey(readFileSync("private-key.pem"));
const publicJwk = createPublicKey(privateKey).export({ format: "jwk" });
// "sandbox" for testing, "production" for production.
const environment = "sandbox";
const app = express();
const document = await createClientIdMetadataDocument({
environment,
clientId: CLIENT_ID,
// Your business name, shown to users when they approve
// a credential presentation for your endpoints.
clientName: "Acme Corporation",
// The agents allowed to use x401 with Proof's Cloud Wallet at your endpoints:
// your own agents (by URL) or Proof's trusted list with `agentsTrustListUrl`.
redirectUris: [agentsTrustListUrl(environment)],
jwks: [publicJwk],
});
app.get("/.well-known/proof-client.json", (req, res) => {
res.json(document);
});
export { app, CLIENT_ID };
Agents trust list
agentsTrustListUrldefines which trusted agents use x401 and Proof's Cloud Wallet at your endpoints. Proof publishes its list at https://api.proof.com/.well-known/agents-trust-list. See the OID4VP over the Digital Credentials APIexpected_originsparameter for details.Proof's Cloud Wallet accepts an OID4VP request only when its
expected_originsare listed in the CIMD'sredirectUris.
4. Register your OAuth Client ID Metadata Document
Enroll on Proof as an x401 Verifier by registering your CIMD, then follow the instructions received by email to complete your enrollment.
npx @proof.com/proof-vc-server enroll https://example.com/.well-known/proof-client.json --email [email protected] --key private-key.pem --environment sandbox
Email addressThe email address must be on the registrable domain of your CIMD's host.
www.is treated as the apex domain. For example,[email protected]works forhttps://example.com,https://www.example.comandhttps://app.example.com.
5. Protect your endpoint
Create an x401Middleware that inspects the request's PROOF-RESPONSE header and cryptographically verifies the presented vp_token containing the Verifiable Credential.
import { app, CLIENT_ID } from "./client-metadata.mjs";
import { readFileSync } from "node:fs";
import { createPrivateKey, randomUUID } from "node:crypto";
import { verifier as x401, HEADER, DC_API_PROTOCOL } from "@proof.com/x401-node";
import { agentsTrustListUrl, createClient, createVerifier } from "@proof.com/proof-vc-server";
const privateKey = createPrivateKey(readFileSync("private-key.pem"));
const environment = "sandbox";
const proofClient = createClient({
environment,
clientId: CLIENT_ID,
// Signs your OID4VP requests.
useSecuredAuthorizationRequest: true,
privateKeyFactory: () => privateKey,
});
const proofVerifier = createVerifier({
environment,
clientId: CLIENT_ID,
// Fetches the detached signatures of the vp_token from Proof. See step 6.
privateKeyFactory: () => privateKey,
});
// Nonces of pending OID4VP requests. Use a shared store in production.
const nonces = new Set();
async function x401Middleware(req, res, next) {
const response = req.get(HEADER.PROOF_RESPONSE);
if (response === undefined) {
// Prevents replay attacks: each nonce is accepted once.
const nonce = randomUUID();
nonces.add(nonce);
// A signed OID4VP request over the Digital Credentials API.
const request = await proofClient.signedDcApiRequest({
// See https://dev.proof.com/docs/verify-a-credential#scopes-and-claims
scope: "urn:proof:params:scope:verifiable-credentials:basic",
nonce,
// A subset of the redirectUris of your CIMD.
expectedOrigins: [agentsTrustListUrl(environment)],
});
// The PROOF-REQUEST header, following the x401 protocol.
const payload = x401.buildPayload({
credentialRequirements: {
digital: {
requests: [{ protocol: DC_API_PROTOCOL.SIGNED, data: { request } }],
},
},
});
return res
.status(401)
.set(HEADER.PROOF_REQUEST, x401.encodePayload(payload))
.send("x401 PROOF-RESPONSE required");
}
try {
const artifact = x401.decodeResultArtifact(response);
// The credential result is inline, or behind a URL your endpoint fetches.
let credentialResult = null;
if (artifact.credential_result_uri === undefined) {
credentialResult = artifact.credential_result;
} else {
const response = await fetch(artifact.credential_result_uri);
credentialResult = await response.json();
}
// Verifies the vp_token, issued by Proof's Certificate Authority.
// See https://dev.proof.com/docs/proof-certificate-authority
const presentation = await proofVerifier.verifyVPToken({
encodedVPToken: credentialResult.data.vp_token,
aud: CLIENT_ID,
});
req.credential = presentation.proof_id_default[0];
if (!nonces.delete(req.credential.getNonce())) {
return res.status(401).send("x401 PROOF-RESPONSE replayed");
}
} catch {
return res.status(401).send("x401 PROOF-RESPONSE invalid");
}
next();
}
app.get("/protected", x401Middleware, (req, res) => {
// Reachable by user agents presenting a valid Verifiable Credential.
res.json({ message: "Access granted", claims: req.credential.toJSON() });
});
app.listen(3000);
Prevent replay attacksVerifiers MUST compare
req.credential.getNonce()with the nonce of their OID4VP request and accept each nonce only once.
Claims and scopesLearn about the claims supported by Proof's Verifiable Credentials and how to request them with scopes.
6. (Optional) Use with x402
You can decide to pay for x401 transactions with x402. When paying with x402, Proof delivers x401 presentations with detached signatures: the vp_token is readable, but not verifiable until your endpoint fetches its signatures from Proof, authenticated with the key pair of step 2:
POST https://api.proof.com/verifiable-credentials/v1/x401-signatures
verifyVPToken fetches the signatures for you. The endpoint responds 402 Payment Required when the presentation is not paid yet, and accepts x402 payments in USDC on Base (version 2, scheme exact). Pass an x402-capable fetch to the verifier to pay automatically:
npm install @x402/fetch @x402/evm viemimport { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
// Your wallet, funded with USDC on Base (Base Sepolia in sandbox).
const wallet = privateKeyToAccount(process.env.EVM_PRIVATE_KEY);
// Replaces proofVerifier from step 5.
const proofVerifier = createVerifier({
environment,
clientId: CLIENT_ID,
privateKeyFactory: () => privateKey,
// Pays 402 Payment Required responses with x402.
fetch: wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: "eip155:*", client: new ExactEvmScheme(wallet) }],
}),
});
Paying without x402Verifiers who do not use x402 can add a credit card or link their bank account to their Proof account. Presentations are delivered with their signatures and can be verified immediately. They are charged to the default payment method.
Learn how the signatures are fetched and paid in Use with x402.
7. Test your endpoint
The endpoint returns HTTP 401 with a proof-request header. Inspect the Digital Credentials Presentation request, then the OID4VP request it carries:
curl -so /dev/null -w '%header{proof-request}' https://example.com/protected \
| jq -R 'gsub("-";"+") | gsub("_";"/") | @base64d | fromjson'curl -so /dev/null -w '%header{proof-request}' https://example.com/protected \
| jq -R 'gsub("-";"+") | gsub("_";"/") | @base64d | fromjson
| .credential_requirements.digital.requests[0].data.request
| split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson'Then ask your agent to fetch the protected resource with Proof's Cloud Wallet:
Please install the Proof MCP https://mcp.proof.com/mcp, follow its instructions, then fetch
<your protected URL>
With a valid PROOF-RESPONSE, the endpoint returns 200 and the verified claims.
Go to production
Set environment to "production" and enroll:
npx @proof.com/proof-vc-server enroll https://example.com/.well-known/proof-client.json --email [email protected] --key private-key.pemCloudflare Workers
x401 also ships as a Cloudflare Workers library, @proof.com/x401-workers. Install it and plug x401 on top of your routes, with Hono or a plain Worker.
npm install @proof.com/x401-workersimport { Hono } from "hono";
import { agentsTrustListUrl, d1NonceStore } from "@proof.com/x401-workers";
import { x401, type X401Variables } from "@proof.com/x401-workers/hono";
type Env = { DB: D1Database; SIGNING_JWK: string };
const app = new Hono<{ Bindings: Env; Variables: X401Variables }>();
// Every route under /protected now requires a Verifiable Credential.
app.use(
"/protected/*",
x401<{ Bindings: Env }>((c) => ({
clientId: CLIENT_ID,
privateKeyFactory: () => JSON.parse(c.env.SIGNING_JWK),
nonceStore: d1NonceStore(c.env.DB),
scope: "urn:proof:params:scope:verifiable-credentials:basic",
expectedOrigins: [agentsTrustListUrl(environment)],
})),
);
app.get("/protected", (c) => c.json(c.var.x401.proof_id_default[0]?.toJSON()));
export default app;Find out more in Use on Cloudflare Workers.
Resources
@proof.com/proof-vc-server@proof.com/x401-node@proof.com/x401-workers- Digital Credentials overview
- Integration guide
- OAuth Client ID Metadata Document (draft 02)
- OpenID for Verifiable Presentations (OID4VP) 1.0
- Use on Cloudflare Workers
- Use with x402
- x401 specification
- x402 protocol
Updated 6 days ago