
Payment is authorization

I can’t tell you how many times I’ve needed to do something simple like remove the background from an image or convert a file from one type to another but wasn’t able to do it. While these feel like simple, common things your OS should just handle, they require a service to do them. Luckily, I’m usually able to Google the problem, find a website dedicated to solving that one problem and do it.
It’s super nice because I land on a website like remove.bg, drag my image onto the page, and the background is removed. I don’t have to log in, it’s not tracking me (that I’m aware of), and it just works. The API version of this is something I’d call a utility API. It simply does a thing and gives you a result.
Compare a utility API with what we’re growing more and more accustomed to. I click on a marketing site, sign up, pick a plan, confirm my email address, then start making requests. You know what I call that? Friction.
And in a world where AI agents are becoming increasingly important consumers of APIs, friction is holding us back.
There’s a lot of attention right now on agent identity, and for good reason. Agents that read private data, modify infrastructure, operate business systems, or act on behalf of people absolutely need durable identity, delegated authority, auditability, and policy.
But I think we’re going a little too far with it.
For a lot of utility APIs, identity doesn’t need to be part of the authorization decision. Instead, it just becomes who we charge. Let’s dig into that a little bit.
Bring your own access
To perform a basic utility, it’s likely that an agent will discover your API at runtime, perform a single operation, and never call it again. So requiring it to create an account and establish a permanent relationship with your service before doing 2 cents of work feels wrong. What if instead, your API provided a pay-as-you-go on-demand model (something my readers should be familiar with). x402 is an open protocol for payments over HTTP, led by the x402 Foundation under the Linux Foundation.
The flow looks like this:
sequenceDiagram
autonumber
participant Agent
participant API
participant Facilitator
Agent->>API: POST /generate
API-->>Agent: 402 Payment Required<br/>(price, network, wallet)
Note over Agent: Sign payment authorization
Agent->>API: POST /generate + payment signature
API->>Facilitator: Verify authorization
Facilitator-->>API: Valid
Note over API: Run handler
API->>Facilitator: Settle
Facilitator-->>API: Settlement confirmed
API-->>Agent: 200 OK + settlement details
The caller doesn’t necessarily need to create an account first. It requests the resource, gets back machine-readable payment details, pays, and retries with proof of payment.
Which, when you think about it, means that payment alone can be the authorization decision. While yes, you could still add a layer of AuthN/AuthZ on top of the API, the x402 protocol doesn’t require it. This removes that friction we were talking about earlier and lets agents pay and continue (as long as they have access to a wallet).
This is a very different access model than we’re used to and makes many developers feel a little uneasy because it goes against the paradigm we’ve been handcuffing ourselves to for years. But if you ask yourself five whys, what we’re really after is payment. And this lets us cut out a lot of extra work to get to it.
What does this look like?
My favorite way to learn is by doing, so let’s get into what implementing x402 in a serverless API would look like. Because I love the idea, but this sounds tricky to implement once you actually get down to it. If payment is part of authorization, do you put that in a Lambda authorizer?
You shouldn’t. Lambda authorizers are built around the idea that an authorization decision can be reused. API Gateway evaluates an identity source, decides whether that caller may invoke the API (and specifically which endpoints inside of it), and can cache the result for later requests.
Lambda authorizers work well for things like bearer tokens because the claim being evaluated usually authorizes users for an entire session. This makes the generated policy cacheable and an easy deciding factor for where it lives.
Of course, you can disable the cache with a 0 second TTL, but the authorizer sits at the wrong point of the request lifecycle. It runs before a request hits your API. For payment authorization, we need to go a little further down.
A payment is generally going to be for a single call. If you want to call it 10 times, then you need to pay for it 10 times. Nothing here is cacheable. For this architecture, that leaves us with one option, really. Bringing payment logic into the app code 😬. Well, technically you can also use AWS WAF AI traffic monetization, but more on those limitations in a bit.
The API controller
For years, the serverless community had a debate on whether you should build your APIs as a “lambdalith” where a single Lambda function acts as your API controller and serves your entire API, vs a single Lambda function per endpoint. I was team “function per endpoint” for years, but have come around to the idea that I was wrong.
I won’t jump back into the philosophical debate here, just know that we’re designing a single Lambda function to serve the entire API using the Lambda Powertools HTTP event handler to define our routes and do input/output transformations for us.
It also sets us up for treating x402 as middleware. Lambda Powertools is great at a lot of things, and middleware is where it’s golden. Since we don’t really want payment processing logic littered through the app code, surfacing it as middleware is the right abstraction. Doing it this way gives us some handy conveniences as well.
Route pricing
If you’ve ever built an API with Express, Hono, or Lambda Powertools, you’ll be familiar with the syntax:
app.post('/generate', async () => {
// Generate something
return { message: 'success!'}
});
This defines a POST /generate endpoint that does some work and returns a success message. But let’s pretend like we wanted to charge money to call it. All we need to do is add some middleware to the route:
app.post(
'/generate',
[ x402.paid({ price: '$0.05', description: 'Create an image of something' })],
async () => {
// Generate something
return { message: 'success!'}
}
);
Isn’t that slick? The handler has no payment logic, nor does it parse payment headers, talk to a facilitator, or know anything about wallets.
The middleware verifies that the request has acquired the right to execute, runs the handler, and handles settlement around the request lifecycle.
The coolest part of the whole thing is, declared this way, the price is essentially route metadata. It just kinda fits in with method, path, validation, and rate limits as properties of a route. Looking at what that implies for documentation, that could easily turn into something documented in your OpenAPI spec or tool definitions so agents don’t need to receive that first 402 Payment Required status code.
If an agent discovers a capability and can see its price as part of that discovery, then the access model becomes much easier to reason about. Instead of discovering an API and immediately starting a human onboarding flow, the agent can evaluate the cost as part of deciding whether to use it at all.
NOTE - the middleware I used in the example above is an open-source project I built that’s available via npm. It’s specifically designed to work with Lambda Powertools.
Error handling
You know what would be bad? If a caller paid for an operation, it failed, and returned a receipt saying you paid for the failure. Luckily, the payment flow we’re using is designed to prevent that from happening, but it’s not bulletproof.
If we jump back to the sequence diagram from earlier, you can see an important order of operations that should prevent paying for failures.
verify payment
→ run handler
→ handler succeeded?
→ yes: settle
→ no: do not settle
This is how the AI monetization feature in AWS WAF approaches the problem, but it’s not perfect. WAF runs at the edge, and pragmatically it is inspecting a status code. That’s (generally) fine for selling a document, but not robust enough for selling an operation. An empty generation, a model refusal, or a search that found nothing could all still return a 200 OK, which means that payment settled and the caller got nothing.
Middleware runs in-process, so it can decide on the actual result:
x402.paid({
price: '$0.05',
billable: (result) => result.imageUrl != null
})
In the above example, if the response body doesn’t contain an imageUrl property, they would still get a 200 OK, but they wouldn’t be charged. This is a meaningful improvement over status code checking and gives you endless levers for fairly charging your callers.
But what happens if settlement succeeds and the response fails to return to the caller? The agent retries. The payment-identifier extension is your best friend here. The caller can reuse the same payment id, allowing the API to return the cached result without charging or executing the operation again. Of course, that means you have to persist the result somewhere durable and associate the id to the original request (sounds like idempotency to me). It’s still your job to implement this correctly. 🤷♂️
This is the next frontier in AI
Our industry has done a phenomenal job the past several years rocketing us through uncharted territories. In what feels like no time at all, we’ve gone from pioneering chatbots to agents to memory to security to multi-agent collaboration and everything in between. But we’re just starting to scratch the surface on agentic payments.
There are still many hard problems to solve, like transaction latency and reputation tracking (among other things). Not to mention the elephant in the room when it comes to humans handing over control: trust. Anecdotally, I’ve already heard stories about how people are implementing spiked prices via x402 specifically for bots (by reading User-Agent headers), like raising the price from $200 for a pair of sneakers to $1,200 in hopes of surpassing allowed price limits without a human in the loop.
Not to mention the discovery problem. Updating your APIs to accept x402 is step one. But with software, the adage “if you build it, they will come” is unfortunately not true. At all.
Coinbase has taken a stab at a registry with their Bazaar catalog, which gives us a great starting ground. But for long-term success, I think we’ll need something a bit more built-out. More on this in a blog post to come. 😉
For now though, what I want you to take away is that API consumption has fundamentally changed. And how we look at it going forward needs to change as well. Maybe we rethink the multi-step authentication mechanism and opt for a more streamlined “auth by payment” protocol (when applicable).
It’s already relatively easy to retrofit your APIs with x402 if you’re using Lambda Powertools. Honestly, the core x402 package makes it easy no matter what you’re using. But the more important part here is for you to start thinking about your APIs differently.
Do you have a utility API? Is your current auth flow absolutely necessary? Are you able to charge on an operation level for your API capabilities?
Try it out. Tell me what you think. Let’s pioneer something new.
Happy coding!
Join the Ready, Set, Cloud Picks of the Week
Thank you for subscribing! Check your inbox to confirm.
View past issues. | Read the latest posts.

