# Security: what the hub stores and how to revoke it

> Who operates the hub, what it keeps (secrets encrypted), what each key can do, and how to revoke one key or disconnect the whole account.

## Who operates it

Talo Partner Hub is run by an official partner of Talo Pay, part of [Talo's partner program](https://talo.com.ar/partner); it isn't a Talo product. It works with partner credentials Talo issued (sandbox today; production once Talo issues the production credentials), and Talo processes every payment in the merchant's own account. You can confirm the partnership directly with Talo through its official WhatsApp line, [+54 11 3371-1752](https://wa.me/541133711752), the one Talo lists in its terms of service (https://talo.com.ar/terms-of-service, under Información de contacto).

## Where the money is

Payments are created and processed by Talo in the merchant's own Talo account: buyers transfer to a CVU of that account, and the money stays there. The hub never holds funds, and its API can't send money to an account of its choosing: the only money that leaves the merchant's account through it is a refund of a payment (`full` keys only), which Talo sends back for that payment; the hub can't set where it goes. Otherwise it creates and reads payments and payment links, simulates transfers in sandbox and adjusts the account's payment settings.

## What the hub stores

| What | How |
|---|---|
| The account's Talo authorization (a partner token Talo issues when the person authorizes the hub) | Encrypted with AES-256-GCM. The key lives in the hub's deployment settings, never in the database, and each value is bound to its purpose. |
| Hub API keys (`tph_test_…`, `tph_live_…`) | Only a SHA-256 hash and a short prefix. A new key on its way to the CLI or an MCP client is kept encrypted until that client picks it up (minutes; if nobody does, the hub revokes it); after that it can't be read back. |
| Webhook secrets (`whsec_…`) | Encrypted like the Talo authorization; shown once. |
| Payments the hub created | Talo's payment id, your external_id, amount, status and the payment's events (status, CVU, alias, amounts, refunds, motive and payment link). The buyer's `client_data` is passed to Talo and not kept. |
| Payment links | Title, description, amount or range, redirect URL, and when it was created and disabled. A payment a buyer starts from a link records which link it came from. For the limits only, each new payment attempt from a link leaves a keyed hash (HMAC-SHA256, 32 hex characters) of the link and the buyer's IP in a table of its own, for a day (older entries are deleted on the link's next attempt); the payment doesn't carry it, and the IP itself is never stored. |
| Checkout tokens (`ct_…`) | Not stored: each is signed (HMAC-SHA256 with its own key) and checked when used. |
| Webhook URLs and delivery results | As registered. A Vercel protection-bypass secret in the URL is stored too, but never shown back. |
| Usage | Which key called which route, the channel and the status code, for the account's metrics. No request bodies, no keys. |

The CLI keeps the person's login key in their user config, readable only by them (mode 600 on macOS and Linux; the user profile on Windows), and there too, while they authorize, the pending login so it can be picked up again: it includes a single-use token that collects that login's key. The CLI deletes it when the login connects or fails for a reason other than expiry, on logout or disconnect, and once the hub no longer honors it (about 20 minutes after the login started) the next time it saves its config. With MCP over OAuth the browser only carries a one-time code; the MCP client trades it (with PKCE) for the key at the hub's token endpoint, so the key never goes through the conversation. Dashboard sessions are encrypted, HttpOnly cookies, and the API only accepts them together with a header a cross-site page can't send.

## What each key can do

| Scope | Can | Can't |
|---|---|---|
| `read` | read payments, payment links, events, the account and its settings, balance and bank movements (when Talo allows them), the list of keys and webhook URLs, readiness | create or change anything |
| `charge` | read, create payments, create and disable payment links, simulate sandbox transfers, ping webhooks | refund, change amounts or expiry, manage keys or webhooks |
| `full` | everything above, plus refunds, price and expiry changes, keys, webhooks, account settings, disconnecting | |

Only a `full` key (or the dashboard) can create keys, of any scope; `read` and `charge` keys can't create keys. Checkout servers and agents that charge get `charge`: a leaked `charge` key can create payments but can't move money out. Sandbox (`tph_test_`) and production (`tph_live_`) are separate keys on separate Talo accounts. Agents following the hub's skills confirm with the person before any production refund, price change or new production key.

## Revoking

| To | Do |
|---|---|
| Revoke one key | `npx talo-hub keys revoke <id>`, the dashboard, or `DELETE /api/v1/api-keys/{id}` |
| Sign the CLI out | `npx talo-hub logout` (`--all` for both environments): revokes its key on the hub (if the hub can't be reached, it says so, to revoke it from the dashboard) and deletes it from the machine |
| Stop a webhook | `npx talo-hub webhooks remove <id>` |
| Disconnect the account from the hub | the dashboard's **Desconectar**, `npx talo-hub disconnect --yes` (production needs `--production` too, even when it's the active environment), or `DELETE /api/v1/account` with a `full` key. The hub erases its copy of the Talo authorization, revokes every key, disables every webhook endpoint and ends every dashboard session of that environment, all at once. Payments and money stay in Talo. Coming back is a new authorization (`npx talo-hub login`), and sessions from before the disconnect stay ended. |

Disconnecting doesn't revoke the authorization on Talo's side, and the hub keeps its record of past payments and events (no buyer data). It's the account owner's decision: an agent must not run it unless the person asks for it in the conversation. Disconnecting also disables every payment link of the account for good, and its checkout tokens stop working.

## Checkout token and payment links

- **Checkout token** (`checkout_token`, `ct_…`): comes with each payment the API creates or reads, so the merchant's page can show that payment with the widget. It is read-only and names one payment of one account: it opens that payment's public view (`GET /api/public/payments/{id}`: status, amounts, CVU, alias, expiry, motive, Talo's payment page) and nothing else, no other payment, no writes, no account data. It ends 15 days after it was issued. It can't be revoked one by one: disconnecting the account stops them all, and tokens issued before a disconnect stay dead after the account reconnects. A bad, expired or another payment's token gets the same 404 as a payment that doesn't exist, so the endpoint doesn't reveal what exists.
- **Payment links** (`/l/<id>`): anyone with the URL can start a payment, so the hub allows up to 20 payments per hour from the same network (IP) per link, and a breaker of 200 per link per 10 minutes; a buyer who comes back to the link gets the payment they already started (also after a partial transfer), which doesn't count. Buyers on the same network (one Wi-Fi, one office) count together. The network is told apart by a keyed hash of the link and the IP, which lives only in the limits' own table, for a day; the IP itself is never stored. Coming back works with an HttpOnly cookie scoped to that link's path, for a day, while that payment is still PENDING or UNDERPAID for the same amount, so the buyer doesn't get a new CVU. A disabled link, or one whose account was disconnected, says only that it isn't available.
- Both are only UI. Fulfil orders on the signed webhook, never on the widget's `talo-paid` event or a buyer returning to your site: anyone can fire a browser event.

## Webhooks

Every delivery is signed (HMAC-SHA256 over the timestamp and the raw body), and your verifier accepts it only within 5 minutes of that timestamp, so your server can tell it came from the hub; see [webhooks](https://talopartnerhub.vercel.app/docs/webhooks.md). The hub doesn't follow redirects, and it never shows back a Vercel protection-bypass secret in a webhook URL.

---
Human page: https://talopartnerhub.vercel.app/docs/security · All pages: https://talopartnerhub.vercel.app/docs/index.md
