Hello, OID4Pay
This page walks an agent author through a complete OID4Pay testmode payment in under ten minutes. By the end you have:
- An SDK installed (Node, Python, or Go).
- A sandbox client registered against
sandbox.oid4pay.com. - A testmode mandate, a signed offer, and a settled charge.
sandbox.oid4pay.com is being finalised. When it opens, sandbox
tokens are throwaway and capped at 100 charges per hour 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-goStep 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=payment.charge" \
-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: settle a testmode charge
Send the signed offer + the DPoP-bound JWT-AT to the merchant's verify- and-charge endpoint. The merchant SDK wraps both calls in a single helper:
// Node / TypeScript
import { verifyOffer, charge } from "@oid4pay/oid4ac-merchant";
const verification = await verifyOffer(offerBody, signedOfferHeaders, merchantJwks, {
expectedTargetUri,
});
const result = await charge({
accessToken,
dpopKey,
offerDigest: verification.bodyDigest,
idempotencyKey: crypto.randomUUID(),
});Successful charges return a charge_id, a stripe_payment_intent_id (the current settlement rail), and the mandate_id the charge debited.
Next steps
- Read the wire reference end-to-end so you know which wire fields are mandatory and which are extensible.
- Add the merchant SDK to your storefront and register a verification keypair.
- Subscribe to the changelog for breaking
wire-version bumps. The current version is
0.1.0-beta.