Skip to main content
examples/ledgerly in the payman-connect repo is Ledgerly: a bookkeeping product with a chat box, built on the SDK. Ask it to pay an invoice and a connect button appears in the conversation; consent happens on Payman’s pages, and the answer is already streaming when you land back. A payment over threshold freezes into an approval card and settles to “Payment sent”. Ledgerly’s own agent is a real model with payman.tool() as its only tool (a labelled keyword stub fills in when no model key is set).
Register http://localhost:4321/payman/callback in the developer console; the app key is the whole configuration.
Against a local stack, point PAYMAN_BASE_URL at it (.env.example already does). Otherwise the SDK talks to production, where the app self-read fails and the client throws on the first call.
Every Payman line is tagged ── PAYMAN, so the whole integration is one grep. examples/nextjs (port 3000) and examples/slack are the App Router and no-browser siblings.

Worth stealing

  • SameSite=Lax on the session cookie (sessions.mjs): Strict drops it on the consent return, in production only.
  • refuseRequest in server.mjs: Host, Content-Type, Sec-Fetch-Site, Origin, checked before any route dispatches.
  • The prose before the connect button (stub.mjs): bank access always gets a reason first.
  • The model-loop invariants: a round-trip cap and exactly one tool_result per tool_use in assistant.mjs, the rollback on a failed turn in server.mjs.
  • What the page never holds: both keys stay server-side, and the browser talks only to this app’s origin.