Skip to main content
Four lines on your server, two components on your page, one secret: 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.
The app key is a secret. Server only: never a browser, a query string, or a tool schema. It is also the only Payman value you configure; the SDK resolves everything else from it.
2

Install

One base URL serves sandbox and live; the key prefix picks the environment. Set 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

That is the whole server. 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.