Skip to main content

Hello, OID4Pay

This page walks a merchant author through a complete OID4Pay testmode flow in under ten minutes. By the end you have:

Sandbox: the public sandbox at sandbox.oid4pay.com is being finalised. When it opens, sandbox tokens are throwaway and rate limited per client.

Step 1: install an SDK

Pick whichever SDK matches your stack:

# Node / TypeScript
npm install @oid4pay/oid4ac-merchant

# Python
pip install oid4pay-oid4ac

# Go
go get github.com/oid4pay/oid4ac-go

Step 2: register a sandbox client

Sandbox client registration uses RFC 7591 dynamic registration. POST a minimal JSON body to /oauth/register:

curl -sS https://sandbox.oid4pay.com/oauth/register \
  -H "content-type: application/json" \
  -d '{
    "client_name": "my-agent-prototype",
    "token_endpoint_auth_method": "private_key_jwt",
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "jwks": {"keys": [/* your client public JWK */]}
  }'

The response carries your client_id. Store it; the secret counterpart is the private key whose public JWK you just published.

Step 3: request a pushed authorization (PAR)

Every authorization request goes through PAR. The response carries a request_uri with a one minute TTL. See the PAR response for the exact shape.

curl -sS https://sandbox.oid4pay.com/oauth/par \
  -H "content-type: application/x-www-form-urlencoded" \
  -d "client_id=$CLIENT_ID" \
  --data-urlencode "client_assertion=$CLIENT_ASSERTION_JWT" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "response_type=code" \
  -d "scope=oid4ac:payment" \
  -d "code_challenge=$PKCE_S256" \
  -d "code_challenge_method=S256" \
  --data-urlencode "redirect_uri=https://my-agent.example.com/callback"

The private_key_jwt assertion is the only client authentication method; the algorithm whitelist pins EdDSA as the only accepted algorithm. The PAR response shape is on the spec page.

Step 4: redirect the principal

Send the principal to the wallet portal at https://wallet.oid4pay.com/authorize?request_uri=$REQUEST_URI. The wallet authenticates the principal, displays the mandate-binding consent screen, and returns to your redirect_uri with a one-time authorization code.

Step 5: exchange the code for a DPoP-bound JWT-AT

curl -sS https://sandbox.oid4pay.com/oauth/token \
  -H "DPoP: $DPOP_PROOF_JWT" \
  -H "content-type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=$CODE" \
  -d "code_verifier=$PKCE_VERIFIER" \
  -d "client_id=$CLIENT_ID" \
  --data-urlencode "client_assertion=$CLIENT_ASSERTION_JWT" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  --data-urlencode "redirect_uri=https://my-agent.example.com/callback"

The response carries an access_token with typ=at+jwt (see the JWT-AT claim set) and an id_token if the OIDC scope was requested. The DPoP proof binds the token to your keypair via the cnf.jkt claim.

Step 6: verify the offer and the mandate

The merchant SDK is verify-only. Verify your signed offer body against your own published JWKS, then verify the mandate presentation against your merchant audience. Both calls are local cryptographic checks; neither one moves money.

// Node / TypeScript
import { verifyOffer, verifyMandate } from "@oid4pay/oid4ac-merchant";

// Verify the RFC 9421 signature over your offer body.
const offer = await verifyOffer(offerBody, signedOfferHeaders, merchantJwks, {
  expectedTargetUri,
});

// Verify the mandate presentation (the whole compact SD-JWT VC + KB-JWT
// string) against your merchant audience.
const mandate = await verifyMandate(presentation, merchantAudience, {
  jwksUrl: "https://sandbox.oid4pay.com/.well-known/jwks.json",
  expectedOfferDigest: offer.bodyDigest,
});

console.log(mandate.mandateId, mandate.amountMinor, mandate.currency);

A verified mandate exposes mandateId, agentClientId, principalId, amountMinor, currency, merchant, lineItems, and the offerDigest it was bound to. The SDK stops here.

Settlement is not an SDK call. Charging the verified mandate is the Authorization Server's job. Once verification passes, your storefront calls the Authorization Server over HTTPS with the DPoP-bound access token to settle. The merchant SDK never holds funds and never settles.

Next steps