Bring your own agent
The MCP server is a convenience, not a requirement. OID4Pay never inspects what an agent is: the Authorization Server authenticates a registered client by its key, and authorises a payment against a mandate the human approved. Any runtime that can sign and make HTTP requests can hold that client identity, whatever framework it is written in.
This page is the wire contract. If you are building on Node and want the flow driven for you, start at the agent quickstart instead.
What your runtime has to be able to do
- Generate and store two Ed25519 keypairs, and sign compact JWS with them.
- Build DPoP proofs and attach them to every call.
- Do PKCE with the S256 challenge method.
- Follow redirects manually. The authorisation step answers with a redirect carrying the code, or with a pending state you poll; both need inspecting rather than following.
- Serialise JSON canonically: keys sorted at every nesting level, no whitespace, UTF-8, and characters outside ASCII written as themselves rather than escaped. Getting this wrong is the single most common integration failure, because the digest you compute has to match the one the merchant computes.
No secret is shared with OID4Pay. Both keypairs are generated on your side and only the public halves are registered. Store the private halves the way you would any signing key.
Step 1: pair the agent with a human
An agent is meaningless on its own. It becomes somebody's agent when it registers with a one-shot setup token that the human minted from their wallet session. The wallet shows this token as a code and a QR payload; carry it to your agent however suits your runtime.
Register with the token, your public keys, and the redirect URIs you will use. The token is single-use and expires.
POST /oauth/register HTTP/1.1
Host: as.oid4pay.com
Content-Type: application/json
{
"setup_token": "<code from the wallet>",
"client_name": "My agent",
"token_endpoint_auth_method": "private_key_jwt",
"redirect_uris": ["http://127.0.0.1:8765/callback"],
"jwks": { "keys": [ { "kty": "OKP", "crv": "Ed25519", "x": "..." } ] },
"dpop_jwk": { "kty": "OKP", "crv": "Ed25519", "x": "..." }
}The response carries your client_id. The Authorization Server
binds it to the human who minted the token, which is what makes later
mandates theirs and not yours.
Redirect URI rules worth knowing before you get a 400:
- HTTPS URLs are accepted. Plain HTTP is accepted only for the loopback
literals
127.0.0.1and[::1], on any port. localhostis not accepted, and neither is the rest of the 127.0.0.0/8 range. A name can be rebound; a literal cannot.- A private-use scheme has to be reverse-DNS, for example
com.example.myagent:/callback.
Step 2: ask for a mandate
Every payment starts by pushing the request to the Authorization Server.
Authenticate the call with a private_key_jwt assertion, attach
a DPoP proof, and describe the payment you want authorised.
{
"type": "oid4ac_mandate",
"amount_minor": 499,
"currency": "EUR",
"merchant": "https://shop.example.com",
"offer_digest": "<canonical digest of the signed offer>",
"line_items": [{ "sku": "socks-01", "qty": 1 }],
"consent_mode_hint": "scan"
}Send that as the authorization_details entry alongside response_type=code, code_challenge_method=S256,
a state, the resource naming the merchant, and
the openid oid4ac:payment scope. The narrower payment:initiate scope is not enough to settle: the merchant
refuses the payment call without oid4ac:payment.
The reply is a request_uri with a short lifetime. Move on to
the authorisation step promptly; a stale request URI is gone.
Step 3: let the human approve
Call the authorisation endpoint with the request URI. What comes back depends on the consent mode the deployment and the human's policy resolve to. With cross-device approval, which is the mode that suits an agent with no browser, you get a pending response carrying an approval code and the wallet URL to show.
Show the code or its QR. The human opens their wallet, sees the amount, currency, merchant, and line items, and approves or declines. Meanwhile, poll the request URI's status endpoint until it reports approval, then continue. Poll at a human pace, a couple of seconds apart, and give up after a bounded wait rather than spinning.
Step 4: exchange the code
Exchange the authorisation code with a fresh DPoP proof whose key matches
the one you used at the push step, your PKCE verifier, and another private_key_jwt assertion. You get back an access token bound
to your DPoP key, a refresh token, and the mandate.
The mandate is a selectively-disclosable credential. It always carries the merchant audience, the validity window, and a confirmation claim naming your DPoP key. The amount, currency, merchant, line items, and offer digest are disclosures you present alongside it.
Step 5: pay
Presenting the mandate to the merchant takes four things:
- The offer. Fetch the merchant's signed offer and compute its canonical
digest. This has to equal the
offer_digestyou bound at step 2, which means you fetch the offer before asking for the mandate, not after. - A single-use nonce from the merchant.
- A key-binding JWT over the presentation, carrying that nonce and signed with the same DPoP key the mandate names.
- The presentation itself: the issued credential, the disclosures you are
revealing, and the key-binding JWT, joined by
~.
The merchant re-verifies all of it against the Authorization Server's published keys before it charges anything: the issuer signature, the audience, the window, the offer digest, the nonce, and the key binding. None of those checks are advisory.
What the mandate does and does not bound
A mandate authorises one amount, at one merchant, for one holder key, inside one time window. It is not a card, a balance, or a standing authority. Revoking it, or the token family it belongs to, stops the agent immediately; the human can do that from their wallet at any time.
Note what that does not cover. A mandate is not by itself a spending budget across purchases, and per-deployment limits and policy checks are separate controls an operator may or may not have enabled. If you are designing an agent that spends repeatedly, put your own ceiling in your runtime rather than assuming the mandate is one.
Next steps
- The four-signature trail explains what each signature in the exchange is for.
- Sandbox endpoints for a place to try this without moving money.
- The merchant SDKs if you are on the other side of the exchange and need to verify what an agent presents.