Inspiration
What it does
How we built it
Challenges we ran into
Accomplishments that we're proud of
What we learned
What's next for Tendero
Inspiration
Across Latin America, a huge number of small businesses sell mostly through chat: workshops, bakeries, makers who ship abroad. I've spent ~10 years in tech and I integrate PayPal and Mercado Pago with verified webhooks on a real platform, so I see the same pattern every week. The owner answers every message by hand, types totals from memory, sends a payment link, and then opens the PayPal app trying to work out which payment belongs to which chat. Sales are lost at night, totals get mistyped, and "I already paid" screenshots are trusted because checking each one takes too long.
Most AI shopping agents stop at "recommend" or "checkout". The hard part for a small merchant is after the click: knowing the money is real, that it matches the order, what they actually keep after fees, and getting the parcel out on time.
What it does
Tendero puts an agent in the shop's chat and closes the whole loop: sell → collect → verify → reconcile → fulfill → notify.
- Customer agent. Searches the catalog, builds the cart (prices and shipping come from the server), and asks the customer to confirm the total. Then it creates a PayPal Orders v2 checkout and drops a Pay with PayPal button in the chat.
- Verified payments. After approval we capture. Every PayPal webhook is checked with
/v1/notifications/verify-webhook-signature, andPAYMENT.CAPTURE.COMPLETEDis reconciled against the order (status, amount, currency,custom_id). The PayPal fee and net come fromseller_receivable_breakdown. Only then does the customer get "Payment confirmed by PayPal" with a planned dispatch day, and that message is deterministic, not generated. - Fulfillment board (Bryntum Scheduler). A verified payment books the work in the workshop: quality check, packing and courier pickup, linked by dependencies, inside workshop hours, with pickups only when the courier comes. The owner can drag tasks; the server validates every move and refuses impossible ones. A refund takes the order off the board.
- Owner console (AG Grid + AG Charts). Live KPIs, a payment timeline, and tabs for the orders ledger, inventory (stock bars and a chart computed from verified payments only) and market prices.
- Owner copilot. "How did we do today?" runs the reconciliation sweep and answers with real numbers. "Show me unpaid orders over $20" filters and sorts the AG Grid ledger on screen. "What do we ship next?" reads the board. "Are our prices competitive?" asks Channel3's product search for comparable products and shows low / median / high market prices. "Refund the last order, it arrived cracked" issues a Payments v2 refund and notifies the customer when the signed
PAYMENT.CAPTURE.REFUNDEDarrives. - Safety nets. Forged webhooks are rejected and change nothing. Duplicates are ignored. If a webhook is lost, a sweep re-reads the capture from PayPal. If the buyer closes the tab before returning,
CHECKOUT.ORDER.APPROVEDtriggers the capture. If the webhook arrives before the capture response, the order stays PAID.
How I built it
- Backend: Python + FastAPI and a SQLite ledger with an audit timeline.
- PayPal: Orders v2 and Payments v2 go through the official PayPal Server SDK, which APIMatic generates from PayPal's API definition. I integrated it with the skills of APIMatic's PayPal Server SDK Context Plugin, which ground a coding agent in the SDK's real surface (typed request models, the
ApiResponseenvelope,ApiException, and therequests.Sessionseam for testing). Webhook signature verification uses the REST API directly, because Notifications is not part of the generated SDK. A plain httpx client with the same interface is kept as a fallback (PAYPAL_SDK=rest). - AI: a provider-agnostic tool-use loop. By default it runs Claude (
claude-sonnet-5-5) through the official Anthropic SDK, with strict JSON-schema tools, low effort for chat latency and server-side refusal fallbacks. The same loop can run an offline "demo brain" that emits the same tool calls, so anyone can run the project with no keys. - Guardrails in code, not in the prompt: the model never does arithmetic, it can't create a payment link in the same turn it priced the cart, there is a per-order cap, orders belong to their conversation,
PAIDis only set by a verified webhook or a direct PayPal read, and the copilot only reports market prices. - UI: plain JavaScript. AG Grid Community 36 with a custom theme from the Theming API, custom cell renderers and filters driven by the copilot; AG Charts Community for the inventory chart; Bryntum Scheduler Community 8 (MIT) for the fulfillment board, with async server validation in
beforeEventDropFinalize/beforeEventResizeFinalize. - Local PayPal sandbox simulator: I wanted to build and test the full flow, including signed webhooks, before having sandbox credentials. So I wrote a small simulator that mirrors the documented endpoints, payloads and error names (
ORDER_NOT_APPROVED,ORDER_ALREADY_CAPTURED…) and signs its webhooks. The SDK talks to it through arequestsadapter; switching to the real sandbox isPAYPAL_MODE=sandbox. - Tests: 36 automated tests cover pricing, reconciliation, the REST client and the Server SDK adapter (request shape and error mapping), signature failures, the full chat-to-verified-payment flow, forged/duplicate/lost/early webhooks, the closed-tab path, refunds, fulfillment planning and move validation, copilot grid views, inventory, and Channel3 parsing.
- Demo video: recorded by a script (Playwright headless browser + synthetic voice + ffmpeg), so the video shows the real app running end to end.
Challenges I ran into
- Making "paid" mean paid. Return URLs, capture responses and webhooks arrive in any order. Moving the PayPal calls to the Server SDK made the signed webhook land before the capture response in my tests, which exposed a real race: the late capture response overwrote
PAIDwithCAPTURED. Now an order the webhook settled never moves back, and there's a test for it. - Keeping the LLM away from money. The model is great at conversation and bad at being an accountant. Pricing, confirmation, status and scheduling are tools with server-side checks.
- Time zones on the board. The workshop works on Buenos Aires time while the owner (or a judge) may be anywhere, so the board uses shop-local wall-clock times and shows only workshop hours.
Accomplishments that I'm proud of
- An agent that actually transacts end to end (sell, charge, verify, reconcile, schedule, refund) with guardrails that hold regardless of which model is plugged in.
- Forged-webhook rejection you can trigger live from the console, and a board that refuses impossible moves.
- A repo a judge can run in five minutes with no accounts, with tests for every money path.
What I learned
- The PayPal webhook signature verification API is the cleanest way to make agent-driven commerce trustworthy: the agent never has to "believe" anyone.
- A generated SDK plus a context plugin made the PayPal integration faster and more typed, and its different timing surfaced a bug that hand-written code had hidden.
- Tool design matters more than prompt design for agents that move money.
What's next for Tendero
- WhatsApp Business and Instagram adapters (the agent is already channel-agnostic).
- Send courier tracking numbers to PayPal (Orders v2 trackers) when a pickup happens.
- Pay Later and Venmo as funding sources, and Payouts to split revenue between makers.
- PayPal Agent Toolkit / MCP server as an alternative tool backend for the owner copilot.
Built With
- ag-charts
- ag-grid
- anthropic
- apimatic
- bryntum
- channel3
- claude
- fastapi
- javascript
- paypal
- paypal-orders-v2
- paypal-server-sdk
- paypal-webhooks
- playwright
- python
- sqlite