Open Payments and GNAP: an agent-payment audit of Interledger's rail
Most agent-payment rails are twelve-month-old repos with a token and a demo. Interledger's Open Payments is a seven-year-old banking standard built on two published RFCs. So we pointed a throwaway key at its live testnet and asked a simpler question: can an autonomous agent actually pay with it?
The answer is more interesting than yes or no. Open Payments works — we signed real grant requests, created a live incoming payment, listed other people's payments, and rotated an access token, all against interledger-test.dev with an Ed25519 key generated seconds earlier. But the moment an agent wants to spend, the protocol puts a human in the loop by design. This is an audit of where that line sits, and what it means for a stablecoin-native agent stack.
It follows our other empirical protocol audits: L402, the Lightning flavour of HTTP 402; x402's facilitator surface; and the ERC-8004 registries on-chain. Same method: clone the spec, drive the live endpoint, report only what the wire returns.
What Open Payments actually is
Open Payments is an API standard for moving money between wallets on the web. It is not a blockchain and not a token. It is the application layer that sits on top of the Interledger Protocol, and it is what wallet-provider software like Rafiki exposes. The open-payments repo was created on 13 September 2019, carries 542 stars and an Apache-2.0 licence, and was last pushed on 18 August 2026. The normative OpenAPI specs live in a separate repo, open-payments-specifications, currently at version 1.4.0 with tagged releases through v1.4.x.
The surface is small and deliberate. Three servers:
- Wallet Address server —
GET /resolves a wallet address to its metadata;GET /jwks.jsonserves its public keys. - Resource server —
incoming-payments,outgoing-payments,quotes, plus reads and a/completeaction. - Authorization server — a single
POST /for grants, plus/continue/{id}and/token/{id}for continuation and token management.
Authorization is not OAuth. It is GNAP — the Grant Negotiation and Authorization Protocol, published as RFC 9635 in October 2024 (Proposed Standard, authors J. Richer and F. Imbault). Requests are signed with HTTP Message Signatures, RFC 9421, February 2024. Both are finished IETF standards. That already sets Open Payments apart from the draft-soup we found auditing KYAPay and the never-merged BLIP behind L402: the identity and authorization primitives here are not proposals, they are RFCs with IANA registries. RFC 9421's algorithm registry lists ed25519 among its six entries — the exact scheme the testnet uses.
Resolving a live wallet address
The entry point for any agent is a wallet address — an HTTPS URL that doubles as a payment identifier. We resolved Alice's demo address on the testnet:
# GET https://ilp.interledger-test.dev/alice (Accept: application/json)
{
"id": "https://ilp.interledger-test.dev/alice",
"publicName": "Alice Summit Demo",
"assetCode": "ZAR",
"assetScale": 2,
"authServer": "https://auth.interledger-test.dev/f537…822c",
"resourceServer": "https://ilp.interledger-test.dev/f537…822c"
}
The resolution took 577 ms and returned HTTP 200. The record points at the two servers an agent needs — where to ask for a grant, where to spend it — and the asset it settles in. Note the currency: South African rand, scale 2. This is the first structural point. Open Payments is currency-agnostic and settlement-agnostic. The asset is whatever the wallet provider runs. It can be a stablecoin; it can be a bank balance in ZAR. The protocol carries an amount as a { value, assetCode, assetScale } triple and never assumes crypto. That is a strength for reach and a cost for the machine-native, single-currency world x402 optimises for.
The address also serves jwks.json — a JSON Web Key Set with an EdDSA public key. This is the persistent-identity path: the authorization server fetches your public key from your wallet address to verify your signatures. Hold that thought.
Signing a grant with a throwaway key
We generated a fresh Ed25519 keypair in Node and built RFC 9421 signatures by hand — covering content-type, content-digest (a SHA-512 body hash), content-length, @method and @target-uri. An unsigned grant request is rejected immediately:
# POST to the authorization server, no signature
HTTP 401
{ "error": { "code": "invalid_client",
"description": "invalid signature headers" } }
Good. Now the interesting move. GNAP lets a client identify itself two ways: by a wallet-address string (the AS fetches your key from /jwks.json), or by directed identity — you inline your public key as a jwk in the request itself. The spec is explicit that directed identity "enhances privacy by not requiring the client to expose a persistent wallet address identifier," and that it "can only be used for non-interactive grant requests (i.e.: incoming payments)."
We took the directed-identity path with our throwaway key. Signed, inline jwk, asking for an incoming-payment grant:
# POST auth server, signed, client = { jwk }, access = incoming-payment
HTTP 200 (726 ms)
{
"access_token": {
"access": [{ "type": "incoming-payment",
"actions": ["create","read","list"] }],
"value": "6B668D92C208CB849428",
"manage": "https://auth.…/token/05b2…7510",
"expires_in": 600
},
"continue": { … }
}
A quote grant came back the same way in 177 ms. So a key that has never existed before, tied to no wallet, no account, no human, can get valid access tokens for creating incoming payments and quotes. That is the privacy property working as designed — and it is exactly the shape an autonomous agent wants.
The wall: you cannot spend without a human
Then we asked for an outgoing-payment grant — the one that moves money out — with the same directed-identity key and an interact block declaring a redirect:
# POST auth server, signed, client = { jwk }, access = outgoing-payment
HTTP 400
{ "error": { "code": "invalid_client",
"description": "JWK client identifier cannot be used for interactive grants" } }
This is the finding. Spending in Open Payments is an interactive grant. Interactive grants require a persistent, resolvable client identity — a wallet address serving jwks.json with client display information — because a human resource owner has to be shown who is asking and approve it in a browser. The privacy-preserving throwaway key is refused precisely where it matters most for autonomy.
And the interaction itself is a redirect. The interact-response schema is unambiguous: the AS returns a redirect URI "to direct the end user to" plus a finish secret. The only enumerated start method is redirect; the only finish method is redirect. After the human approves, the AS calls the client back with a hash the client must recompute from four values — the client nonce, the interaction nonce, the interact_ref, and the authorization-server URI — as SHA-256, base64url, no padding. Only then does /continue yield the spending token.
End to end, and a privacy note
To confirm the non-interactive path is real and not a stub, we ran a full incoming flow. Grant (521 ms) → POST /incoming-payments on Alice's wallet with a 500-ZAR amount → HTTP 201 with a payment id and an ILP address plus shared secret for the actual money movement. Then we listed incoming payments on Alice's wallet and got 20 records back — including their metadata descriptions: "payment-1", "Hi", "aaa", and our own "audit probe".
Worth flagging: a brand-new anonymous key with only the list action read the descriptions of other parties' incoming payments on a public wallet address. That is by design — incoming payments are receiver-facing and the demo wallet is open — but it is a reminder that metadata on an incoming payment is not private, and agents writing free-text descriptions are writing them where a stranger with a fresh key can read them. We also rotated an access token via its manage URL (HTTP 200, new value, fresh 600-second lifetime) — token rotation and revocation are first-class, which the throwaway-bearer designs we audited elsewhere lack.
The agent layer is one repo with zero stars
If the spec is silent on agents, who is building the bridge? We found exactly one serious attempt: open-payments-mcp, an MCP server wrapping the Open Payments SDK as ten tools — get wallet address, request grant, continue grant, create/read incoming payment, create/read quote, create/read outgoing payment, and an "execute peer-to-peer payment" orchestrator. Two commits, created 10 June 2026, zero stars.
Its author documented the exact friction we measured. In a write-up titled "Agentic Payments over Interledger Protocol — I tried it, and it works," he calls the callback hash verification "easily the hardest part" and notes that in an early version he "worked around it by manually extracting the interact_ref" rather than verifying the hash. The tool that requests an outgoing grant is described as one that "return[s] an approval URL" — i.e. the agent hands a link back to a human. The current source does implement the SHA-256 hash check with a constant-time compare, so the workaround was closed. But the structural point stands: the "autonomous" agent stops and waits for a browser.
Open Payments versus x402, honestly
These are different animals and the contrast is the lesson. x402 is one HTTP round-trip: 402 with a price, retry with a payment header, resource delivered, stablecoin settled — no accounts, no redirect, no human. Open Payments is a multi-step, multi-resource dance: resolve address, get grant, quote, create payment, and for spending, a human approval in the middle. x402 assumes a machine paying a machine in USDC. Open Payments assumes a person delegating a bounded mandate to software that then runs unattended within limits.
That mandate model is genuinely strong. An outgoing grant carries limits — a debit or receive amount cap plus an ISO 8601 repeating interval like R11/2026-08-24T14:15:22Z/P1M, meaning "eleven times, monthly." Approve once, and the agent can spend within that envelope without asking again. That is closer to how a treasury actually wants to delegate to an agent than a per-request 402. The cost is the setup: a persistent hosted identity and one human-in-the-loop redirect before the envelope opens.
What it means for LLM4Agents
Open Payments is not a competitor to the x402 rail our gateway speaks — it is a complement aimed at a different buyer. Where x402 gives an agent instant, unattended, per-call settlement in stablecoins, Open Payments gives a human a way to hand an agent a bounded, revocable spending mandate that reaches any wallet provider on the network, in any currency, including a bank balance. Those are two legitimately different jobs, and a serious agent operator will want both.
The threat, if there is one, is narrow. Open Payments will not eat machine-to-machine micropayments; its redirect-and-mandate flow is too heavy for a sub-cent per-request call, and its own agent tooling is a single zero-star MCP server. But it could become the preferred rail for agent-to-human and treasury-delegated payments — the case where a person funds an agent and wants hard, auditable caps with real token rotation and revocation, backed by finished RFCs rather than a bearer token that never expires. That is a story a bank or a regulated fintech will trust, and it is one x402 does not tell as cleanly.
The concrete fit: the mandate primitive. Open Payments' limits plus interval is a clean, standardised expression of "this agent may spend up to X per month." That is exactly the spending-cap contract our platform already enforces internally around reserve, proxy, settle. Speaking it in Open Payments' vocabulary would let a customer fund an LLM4Agents agent from an Interledger wallet with the same bounded-mandate semantics they would use for any other counterparty.
Staying on the frontier
Four moves, in order.
1. Resolve wallet addresses as first-class payment identifiers. A wallet address is a GET-able HTTPS URL that returns auth server, resource server, and asset. Teaching our gateway to resolve one — the way it already resolves an x402 accepts block — costs almost nothing and lets an agent accept "here is my Interledger wallet address" as a funding source. It is the lowest-risk interop point.
2. Speak the mandate, not just the payment. Adopt Open Payments' limits/interval shape as a canonical way to express agent spending caps, and map it onto our internal reserve logic. A cap that can be expressed identically in x402 escrow terms and in an Open Payments outgoing grant is a cap a customer can reason about once and apply everywhere.
3. Standardise on RFC 9421 request signing. Open Payments, and increasingly the wider agent-identity conversation, converge on HTTP Message Signatures for proof-of-possession — the same primitive that KYAPay's companion draft reaches for. Making RFC 9421 signing a supported client-auth mode on our gateway is investment that pays off across every rail that adopts it, not just this one.
4. Own the missing agent layer. The Open Payments agent bridge today is one MCP server with zero stars and a documented human-in-the-loop gap. The unsolved problem — how a fully autonomous agent completes an interactive grant without a browser, via a pre-approved mandate or a delegated interaction — is exactly the kind of infrastructure primitive worth building and contributing upstream. Whoever closes the redirect gap cleanly, without weakening the human-consent guarantee, defines how bounded delegation works on this rail.
Pay per call, in stablecoins, no redirect
LLM4Agents is an OpenAI-compatible gateway where agents settle per request over x402 and EIP-3009 — and we track every rail, from Interledger to Lightning, so your agents stay on the frontier.
Register an agent