How I built a Stripe payment-link API for AI agents
My agents kept asking me for a credit card.
Not metaphorically. I run a handful of AI agents that do real work: buying domains, renewing certs, ordering test prints, topping up API credits. Every agent framework I looked at had the same answer for payments, which was no answer. The agent would cheerfully draft a purchase plan and then stall at the one step that mattered, because nobody had solved the part where money changes hands.
So I built Agent Checkout™. One API call mints a real Stripe payment link. The agent hands the URL to its human operator. The human clicks and pays. The agent never sees a card number, a CVV, or anything it could leak into a log file.
Here's the money-flow design. It's the part most people get wrong.
The core rule: the agent must never touch card data. This is not a nice-to-have. The moment card numbers flow through an agent's context window, they land in logs, transcripts, tool traces, and whatever observability stack you bolted on. PCI scope explodes. So the architecture keeps the agent at arm's length from the money by design, not by policy.
The flow works like this. The agent POSTs to the API with an amount, a description, and a merchant identifier. The API creates a Stripe Payment Link and returns the URL. That is the entire agent-facing surface. The agent's job is then social, not financial: it hands the link to its human operator. "Here's the link for the domain renewal, $14.98, expires in 24 hours." The human opens it, sees Stripe's own hosted checkout page, and pays with whatever method they trust. The agent polls the API or waits for a webhook to learn the outcome.
Operator-in-the-loop was the design from the start, not a limitation I tolerated. Humans are the fraud check. An agent with unsupervised spending authority is a story that ends with a $4,000 GPU invoice at 3 AM. The human click doubles as the approval gate and the audit trail. Every payment has a person who meant it.
Merchants connect their own Stripe account, either through Stripe Connect OAuth or by bringing their own restricted API key. Funds settle directly to the merchant. My service never holds balances, never touches payouts, never sits in the middle of the money. That was deliberate. Custody is a regulatory swamp and I have no interest in wading into it. The platform earns through application fees on Connect or a flat subscription. The money path stays boring: customer to merchant, end of story.
Now the part that will save you a debugging weekend: webhook verification.
Stripe signs every webhook with a secret unique to your endpoint. When a checkout.session.completed event arrives, you verify the signature before you believe a word of it. This matters because your agent is the least trustworthy actor in the system. Not maliciously. Agents hallucinate. An agent that wants a purchase to be done will sometimes tell you it is done. The only source of truth is the signed webhook, or a server-side retrieve of the session object from Stripe's API. Never trust a status the agent reports. Trust the signature.
A few specifics that bit me:
Idempotency keys on link creation. Agents retry. Networks flake. Without an idempotency key, one logical purchase becomes three payment links and a confused human staring at three identical $14.98 charges. Pass a client-generated idempotency key with every create call and return the existing link on a duplicate.
Expiry on every link. Default 24 hours. An open payment link is an open tab in someone's brain. Expire them, and give the agent a clean expired state it can explain: "That link expired, want me to make a new one?"
Metadata, not memory. Put the order context (what the agent was buying, which run, which operator) into Stripe's metadata fields on the payment link. When the webhook fires six hours later, the metadata is how you reconnect the payment to the agent's task. Agents forget. Metadata does not.
That brings me to llms.txt, because an API for agents needs documentation that agents can actually read.
Stripe's docs are written for humans, and they are excellent for humans. But an agent consuming your API does not browse. It fetches one file and expects the whole contract. My llms.txt is under 4KB and it carries the base URL, the one endpoint that matters (create link), the exact JSON shape in and out, the error codes with what each one means, and a complete curl example. No marketing prose. No getting-started narrative. The agent does not care about your founding story. It cares about field names.
Three practices I would defend:
One file, one contract. Everything the agent needs to call the API lives in llms.txt. If an agent has to follow three links to assemble a request, you have lost.
Examples over explanations. A full request and a full response teach faster than a paragraph about authentication. Show the 200, then show the 400s.
Version it with the API. llms.txt is code, not copy. When the request shape changes, the file changes in the same commit. A stale llms.txt is worse than none, because the agent will confidently send the old shape.
The free tier is 100 links a day, no card required, because the people building agents are experimenting and experiments should be cheap. The MCP server exposes exactly one tool, create_checkout_link, because one tool is all this needs to be.
The part I have not solved: what should happen when an agent mints a link and the human never clicks? I expire them at 24 hours today, and I am not convinced that is the right answer. If you have seen a good pattern for the unclicked-link problem, reminders, abandonment feedback into the agent's planner, or something else entirely, I would genuinely like to hear it.
Try it: https://checkout.ignitionfoundry.com — free tier, 100 links a day, no card required. The MCP server (one tool, create_checkout_link) is at https://github.com/oidsdev/agent-checkout-mcp
Top comments (0)