PAYMAN_APP_KEY. Node 22.5 or newer.
Building an agent? If your agent already runs a model against a tools array, give it
payman.tool() and answer the call with handleToolUse(): see Add it as a tool. That swaps only the run() call below. The app, the mount, the buttons, and the four results are the same.1
Register an app
In the developer console (the button at the top right), open Apps, then Register an app.
The app key is shown once. Copy it then; if you lose it, generate a new one.
2
Install
PAYMAN_BASE_URL only when pointing at a local or self-hosted stack.Not on Express? The same routes ship as web-standard handlers at
@paymanai/connect/fetch, a subpath of the core package. Next.js has its own wrapper: see Next.js. The rest of this page is identical either way.3
Mount and run
mount() is async: await it. It registers three GET routes on your origin: /payman/callback for the consent return, plus /payman/approvals/:ref/stream and /payman/resume/stream for your page to watch. currentUser is the only thing the SDK reads from your auth; wire it to however you identify users.Connections land in an encrypted SQLite store at ./.payman. forUser() is per call; never hoist it.Your session cookie must be SameSite=Lax or the consent return drops it in production, which you cannot catch locally; mount() prints a reminder. await payman.doctor() checks your registered redirect URI and the registration clock; run it in CI.4
Render the UI
<ConnectButton> opens the consent popup, falls back to a plain link when the popup is blocked, and retires itself once connected. <ApprovalButton> opens Payman’s approval page; the customer approves there. Your app never approves and has no field that could carry a code; onSettled hands you the receipt.The components are headless. Import styles.css once, or style the class names yourself; className replaces the default class.5
Handle the four results
run() resolves one of four shapes and never throws for wire failures. Switch on result.kind. The tool path returns the same four on turn.ui, so this table and the buttons above serve either integration.JSON.stringify(result) is safe to send to a browser; diagnostics live on a non-enumerable symbol.Next
Add it as a tool
Your agent already runs a model. This is the one tool it needs.
Actions
run(), sessions, streaming, the four results in detail.
Approvals
What happens when a payment freezes, start to receipt.
Any framework
The same routes on Hono, SvelteKit, Bun, Deno.
Example app
The whole pattern as a running app you can read.

