---
name: vibiz-payments
description: Sell with Vibiz Stack payments - take money with no identity check and no Stripe account (Vibiz is the merchant; KYC only when the owner withdraws) or through the owner's own Stripe account, create a plan, add a checkout or Upgrade button, wire and verify payment webhooks, check subscriptions, and answer questions about fees (2% Vibiz fee plus card processing), the 7-day hold, refunds and withdrawing. Use when a Vibiz project (a vibiz.json in the folder, or the vibiz MCP tools) needs to take money or someone asks what a payment leaves them.
---

# Vibiz payments

**Connect first.** Not logged in (a tool answers `unauthorized` with `reason: not_logged_in`, or the CLI says "Not logged in")? Run `npx @vibiz/cli login`: show the person the link it prints and ask them to click Authorize (their account is opened on the way if they have none), then go on. Or connect the remote MCP server, nothing to install: `claude mcp add --transport http vibiz https://app.vibiz.app/mcp` (the client opens the same sign-in on first use). Never ask the person to paste a key into the chat. **Keep the CLI current:** a command named here that your `vibiz` doesn't have (an unknown-command or usage error) means the CLI is out of date, not that Vibiz can't do it: run `npm install -g @vibiz/cli@latest` (or `npx -y @vibiz/cli@latest <command>`) and retry, and refresh these skills with `vibiz skills add --global`. Never tell the person something isn't possible because an old CLI lacks the command.

Two ways to take money; say which one applies before anything else:

1. **Through Vibiz (the default, nothing to set up).** Vibiz is the merchant: a project sells from its first call, in test mode (`development`) and live (`production`), with **no identity check (KYC) and no Stripe account of the owner's**. Sales land in the project's Vibiz balance. Identity and a bank are asked for only when the **owner withdraws** (once, console ▸ Payments ▸ Get paid).
2. **The owner's own Stripe account (optional).** The owner connects it themselves (Stripe's own sign-in, from a link you give them; only the account owner can, in the console). New sales are then charged on their account and paid out by it directly: no Vibiz balance, no hold, no Withdraw.

## Before you start

- The project id is in `./vibiz.json` (`project_id`). Don't ask the user for it.
- Tools: the `vibiz` MCP server (`vibiz_payments`, `vibiz_docs`, `vibiz_dev_env`). Without it, the CLI (`vibiz payments …`) or HTTP (`https://app.vibiz.app/v1/projects/<id>/payments/…`, `Authorization: Bearer $VIBIZ_API_KEY`).
- Work in `development` first. Catalogs are separate per environment: create the product again with `env: production` to sell it live.

## Sell a plan

1. Create the product and price in development:
   `vibiz_payments { action: "create_product", project_id, env: "development", name: "Pro", price: { unit_amount_cents: 1500, type: "recurring", interval: "month", lookup_key: "pro-monthly" } }`
2. Write the Upgrade action on the server with `@vibiz/app` (`vibiz.payments.checkout({ lookup_key, user, success_url, cancel_url })` then redirect). Install it with `npm install @vibiz/app`; a project that can't add a dependency calls `POST /v1/app/payments/checkout` with `fetch` and `VIBIZ_APP_KEY`. See `references/guide-app-payments.md`.
3. Create a checkout on every click. Never hard-code a checkout URL: each is for one buyer and expires after 1 hour.
4. Gate features with `vibiz.payments.hasActive(userId, 'pro-monthly')` (or `GET /v1/app/payments/subscriptions?user=`). A "Manage billing" button: `const { url } = await vibiz.payments.portal(userId, { return_url })`.
5. Webhooks: set the URL (`vibiz_payments { action: "webhook", webhook_url }`, development) and verify in the route with `const event = await vibiz.webhooks.receive(req)`: it reads the raw body and checks the signature with only `VIBIZ_APP_KEY` (it fetches the signing secret itself). Never ask the person to add `VIBIZ_WEBHOOK_SECRET` to a host: the app key is the one key. Dedupe on `event.id`. See `references/guide-webhooks.md`.
6. Test: `vibiz_payments { action: "simulate", simulate: "payment" }` (development), then `vibiz_payments { action: "deliveries" }` to see the webhook's status code.
7. Going live: create the same product with `env: "production"`. The live webhook defaults to the app's real site: `https://<slug>.vibiz.app/api/vibiz/webhook` when it's deployed on Vibiz, or `https://<its domain>/api/vibiz/webhook` when it runs on another host and that domain is on the project (`vibiz domains add <domain> --elsewhere`). The secret is in the app's env, so the app's route must be `/api/vibiz/webhook`. **Always check** `webhook.url` (`vibiz_payments { action: "get", env: "production" }`) against where the app is actually live (ask the person if you don't know). To point it elsewhere on the app's own site, an agent key may set a live URL on the project's `<slug>.vibiz.app` address or one of its domains, over HTTP: `PATCH /v1/projects/<id>/payments?env=production { "webhook_url": "https://<domain>/api/vibiz/webhook" }` (the `vibiz_payments webhook` tool and `vibiz payments webhook` from an agent key change development only); any other URL, the events filter and the statement suffix are the owner's (console ▸ Payments ▸ Webhooks).

## The owner's own Stripe account (when to send the link)

Payments through Vibiz are the default and need nothing from the owner. Offer the alternative only when the owner says they already have a Stripe account, want sales paid out by their own Stripe, or ask why money is held by Vibiz:

1. `vibiz_payments { action: "connect_own_account", project_id, env: "production" }` (or `vibiz payments connect`) → `url`.
2. Give the owner that `url`. They open it signed in to the console and choose: connect their Stripe account, or keep payments through Vibiz. Only they can complete it (or disconnect it later); don't retry or try to finish it yourself.
3. `vibiz_payments get` → `account.mode` is `own` once they have. New sales are then charged on their Stripe account and paid out by it on its own schedule (no hold, no Withdraw for them); Vibiz keeps `fees.own_account_bps` on each sale (2% by default); money already in the Vibiz balance stays to withdraw, and subscriptions sold through Vibiz keep billing through Vibiz. The app's code doesn't change.

The link lasts 24 hours and only the signed-in account owner can use it (anyone else gets `owner_only`). `account.own.available: false`, or `provider_unavailable` with `reason: own_account_unavailable`, means it isn't offered right now: keep payments through Vibiz. Disconnecting is the owner's too (console ▸ Payments, or `vibiz payments disconnect` with an owner key).

## Facts to repeat exactly

`vibiz_payments get` → `fees` is the source of truth (the fee is set per account, and an admin can change it, for projects on their own Stripe account too): read it and quote `fees.example` rather than these defaults:

- Card processing 2.9% + 30¢ (most US cards; `card_fee_bps: 290`, `card_fee_fixed_cents: 30`).
- Vibiz fee: **2%** of each payment by default (`platform_bps: 200`; on the owner's own Stripe account `own_account_bps`, also 200 by default).
- **$100 → $3.20 processing + $2.00 Vibiz fee → $94.80** to the balance (`fees.example`: `card_fee_cents: 320`, `platform_fee_cents: 200`, `net_cents: 9480`).
- Live payments are pending **7 days** (`hold_days`), then available; `balance.next_available_at` says when. Test mode has no hold.
- Refunds don't return the processing fee or the Vibiz fee. A dispute takes the amount plus $15 (`dispute_fee_cents: 1500`), returned if it is won.
- Withdrawing: the **owner**, once, verifies identity and adds a bank (console ▸ Payments ▸ Get paid), then Withdraw. Don't promise when it reaches the bank: `estimated_arrival` shows it once scheduled.
- With the owner's own Stripe account connected (`account.mode: "own"`), none of the hold or withdrawing applies to new sales: their Stripe pays them out.

## Never

- Never take, ask for or pass a card number. Send a checkout link.
- Never retry an `owner_only` error with the same key. Tell the user `details.next_step` (live refunds, cancels, withdrawals, webhook and statement settings are the owner's, because this is an agent key).
- Never put `VIBIZ_APP_KEY` or `VIBIZ_WEBHOOK_SECRET` in client code or `NEXT_PUBLIC_` variables, and never print them.

**Console links**: payments, subscriptions and the payments account come back with `console_url`, the page in the web console (with `env` and the item open). When the owner has to act (refund, withdraw, webhooks), give them that link. `vibiz_docs console-urls` lists every page and its parameters.

## Not hosted on Vibiz?

Payments need no resource and no hosting: `vibiz init <name> --resources none`, and the app on its own host calls `vibiz.payments.checkout` with its one key (`VIBIZ_APP_KEY`); `success_url` and `cancel_url` are any `https` page on their site. Add the site's domain (`vibiz domains add shop.example.com --elsewhere`); once it is trusted (its TXT record verified, or the owner confirmed it), the live webhook defaults to `https://shop.example.com/api/vibiz/webhook`, and one left on the unused `<slug>.vibiz.app` address moves there by itself. Check `webhook.url` and set it as in step 7 if needed. The route verifies with `vibiz.webhooks.receive(req)` and only `VIBIZ_APP_KEY`. The vibiz-use-one skill has every step.

## References

- `references/payments.md`: the product page (pricing, limits, errors, what agents can do).
- `references/guide-payments.md`, `references/guide-app-payments.md`, `references/guide-webhooks.md`: the agent guides (`vibiz_docs`).
- `references/errors.md`: every error code.
