Inspiration
Shopping agents are fun right up until you ask them to spend real money. That is where people (including us) start thinking about worst cases: card details leaking, a sketchy checkout page, a silent price jump, or an agent buying from the wrong domain. We wanted a setup where the agent can do the boring work, but the user still feels safe saying, "Yeah, go ahead and buy it."
What it does
Paytato lets an AI agent shop and then hand off payment in a way that keeps card details out of the agent and out of the merchant.
The flow is simple:
- The agent browses, builds the cart, and submits a payment intent.
- The user approves or declines from an email.
- After approval, PayFill starts on a trusted machine, opens the checkout link, decrypts the stored card data locally, pays, and shuts down.
Card details are encrypted in the browser before they ever reach Paytato, and only PayFill can decrypt them.
How we built it
We split the system into three roles and kept their privileges tight.
The agent:
- Starts a run with the Paytato Agent API.
- Turns the user's one-time message into a structured shopping plan.
- Shops and builds the intent payload (items, totals, merchant, checkout link).
- Submits the intent and then stops completely.
Paytato:
- Tracks the run and intent lifecycle.
- Sends the approval email and enforces deadlines and constraints.
- Holds encrypted payment data (ciphertext only) tied to the user's account.
PayFill:
- Does not run until Paytato triggers it after approval.
- Receives the checkout job plus encrypted card payload.
- Verifies domain and total, then decrypts locally and completes checkout with Selenium.
- Calls the Paytato completion endpoint right after a successful payment so the budget hold converts to spend.
Challenges we ran into
The hardest part was the handoff. Once the agent submits the payment intent, it is gone by design. That means Paytato and PayFill have to carry the rest of the process without the agent ever learning approval status or touching payment data.
We also had to be careful about timing. After an intent is approved, there is a short window to finish and acknowledge the purchase, so PayFill has to start fast, behave predictably, and fail safely.
Client-side encryption was another one. Getting the browser to encrypt card data before it hits the server sounds straightforward. In practice, you have to make sure nothing logs it, nothing caches it, and you never accidentally reintroduce plaintext on the backend.
Finally, checkout pages can change under you. Prices update, shipping options change, and redirects happen. We added a last-second check in PayFill to confirm the domain and total before decrypting anything.
Accomplishments that we're proud of
- The agent never sees card data and never sees approval status. It submits the intent and exits.
- Paytato does not need plaintext card data on the server. The browser encrypts it first.
- PayFill only starts after approval, decrypts only when it is ready to submit payment, and then shuts down.
- The approval flow is async through email, so the user does not have to babysit a live chat while shopping happens.
What we learned
Security is mostly about boundaries, not cleverness. The best feeling in this project came from realizing we could make the "dangerous" part small and short-lived: a local executor that runs only when it has to, with as little surface area as possible.
We also learned that "trust" is concrete. People do not want promises. They want to know who can see what, when, and why.
What's next for Paytato
- Replace the hardcoded PayFill public key MVP setup with proper key registration and rotation.
- Better merchant policies: domain allowlists, per-merchant limits, and clearer failure reasons.
- More resilient PayFill execution: retries for safe errors, better handling of login and verification flows.
- A cleaner audit trail for users so they can see exactly what was attempted and what happened.
Built With
- browser-side-encryption
- convex
- environment
- execution
- javascript
- keywords-ai
- local
- node.js
- paytato-agent-api
- playwright
- resend
- rest-apis
- selenium
- typescript
- websockets
- x25519-xsalsa20-poly1305-(libsodium)
Log in or sign up for Devpost to join the conversation.