Agent quickstart (5 minutes)
Build an agent that makes a test payment against a sandbox merchant. By the
end you have a working signed-Offer + PAR + token + KB-JWT + charge round
trip against sandbox.oid4pay.com.
Prerequisites
- Node 20+ (Web Crypto Ed25519 is required).
- A sandbox merchant to charge. The default is
shop.alpacanica.comon testmode. - A pair of Ed25519 keypairs (one for DPoP proofs, one for client assertion).
Step 1: install the MCP server
npm install @oid4pay/oid4pay-mcpThe MCP server wraps the wire shapes so you do not need to implement DPoP
proof generation, KB-JWT minting, or PAR posting yourself. The same
underlying SDK is available standalone at @oid4pay/oid4ac-merchant;
see node-merchant.
Step 2: generate keypairs
node --eval "
import('node:crypto').then(async ({ webcrypto }) => {
const dpop = await webcrypto.subtle.generateKey({ name: 'Ed25519' }, true, ['sign']);
const pkj = await webcrypto.subtle.generateKey({ name: 'Ed25519' }, true, ['sign']);
for (const [name, k] of [['dpop', dpop], ['pkj', pkj]]) {
const pub = await webcrypto.subtle.exportKey('jwk', k.publicKey);
const prv = await webcrypto.subtle.exportKey('jwk', k.privateKey);
process.stdout.write(JSON.stringify({ name, pub, prv }) + '\n');
}
});
"Save both JWKs. The dpop key signs DPoP proofs; the pkj key signs private_key_jwt assertions.
They MUST be distinct keys.
Step 3: register the agent (RFC 7591 DCR)
curl -sS https://sandbox.oid4pay.com/oauth/register \
-H "content-type: application/json" \
-d '{
"client_name": "my-test-agent",
"token_endpoint_auth_method": "private_key_jwt",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"redirect_uris": ["http://localhost:8080/callback"],
"jwks": {"keys": [<pkj pub JWK>, <dpop pub JWK>]},
"dpop_jwk_thumbprint": "<sha-256 thumbprint of dpop pub JWK>"
}'The response carries your client_id. Store it.
Step 4: pick an offer
curl -sS https://shop.alpacanica.com/.well-known/oid4ac-catalog | jq '.items[0]'The catalog is RFC 9421 signed. The merchant SDK verifies the signature for
you in the next step; the offer_url + offer_digest are what you pass downstream.
Step 5: run the MCP agent_payment_initiate tool
import { createClient } from "@oid4pay/oid4pay-mcp";
const client = createClient({
asOrigin: "https://sandbox.oid4pay.com",
clientId: process.env.OID4PAY_CLIENT_ID,
dpopJwk: JSON.parse(process.env.OID4PAY_DPOP_PRIVATE_JWK),
pkjJwk: JSON.parse(process.env.OID4PAY_PKJ_PRIVATE_JWK),
});
const result = await client.agent_payment_initiate({
store_url: "https://shop.alpacanica.com",
offer_url: "/products/test-pinata",
max_amount_minor: 1500,
currency: "EUR",
});
console.log(result);The MCP tool walks the full path: fetches the signed Offer, verifies the RFC 9421 signature against the merchant's JWKS, posts a PAR, opens the wallet consent screen (or auto-consents on the kill-switch test mode), exchanges the code for a JWT-AT and SD-JWT VC mandate, mints the KB-JWT, presents the mandate to the merchant, and triggers the charge.
Step 6: read the result
The response shape:
{
"charge_id": "ch_test_1abc...",
"mandate_id": "mandate_test_xyz",
"stripe_payment_intent_id": "pi_test_2def...",
"offer_digest": "sha-256:<base64url>",
"settled_at": "2026-05-14T10:23:01Z"
}Next steps
- Read the four-signature trail for the wire-level explanation of what just happened.
- Move from sandbox to production by re-running the same flow against
as.oid4pay.comwith production keys (see sandbox for the boundary). - For a non-MCP agent, use @oid4pay/oid4ac-merchant directly.