Penumbra has no dashboard of its own. It's a headless engine you drop into the CRM your team already runs. One typed client over the full orchestration API: routing decisions, transactions, the chargeback fighter, settlement, ledger, and analytics. Request a sandbox key and the SDK comes with it.
Penumbra runs server-side, behind your CRM. Your team never logs into a Penumbra dashboard; the engine pushes every decision into the tools you already use.
sk_test_… key and access to the @penumbra/integration client. No real money moves in sandbox.createClient({ baseUrl, apiKey }). No React, no framework lock-in; it's a dependency-free client that runs in Node, an edge function, or any JS runtime.sk_test_ for sk_live_. No code changes; environments are key-scoped.The same first calls, typed in TypeScript or plain REST from any language:
import { createClient } from '@penumbra/integration'; const penumbra = createClient({ baseUrl: 'https://api.penumbrahq.com/api/v1', apiKey: process.env.PENUMBRA_API_KEY, // sk_test_… from your sandbox }); // Every charge, with the routing decision that picked the PSP const { data } = await penumbra.getPayments({ limit: '25' }); for (const txn of data) { console.log(txn.psp_used, txn.routing_decision); } // Fight a chargeback: score it, draft the rebuttal, submit const disputes = await penumbra.listDisputes(); await penumbra.scoreDispute(disputes.data[0].id); const rebuttal = await penumbra.fightDispute(disputes.data[0].id);
curl https://api.penumbrahq.com/api/v1/payments?limit=25 \ -H "Authorization: Bearer sk_test_..." \ -H "Accept: application/json"
import os, requests resp = requests.get( "https://api.penumbrahq.com/api/v1/payments", headers={"Authorization": f"Bearer {os.environ['PENUMBRA_API_KEY']}"}, params={"limit": 25}, ) print(resp.json()["data"])
TypeScript-first today. The packaged client is TS/JS. Every endpoint is plain REST, so any language works now. See the API reference for the full client-library matrix.
One client, the whole orchestration surface. A few of the domains:
Every charge carries the routing_decision that chose its PSP: the "why this processor" narrative, in your CRM.
List, filter, retrieve, refund, capture. getPayments(), getPayment(id).
Score a dispute, draft the AI rebuttal, run image forensics, submit. scoreDispute · fightDispute.
Per-merchant destinations, splits, batch settlement across bank, crypto, and forex rails.
Double-entry, immutable. Balances, entries, journals, splits, all auditable.
Approval rate, PSP comparison, decline heatmaps, value saved by routing, portfolio health.
Upload a processor statement; get the fee decomposition and savings back.
Initiate PSP-native boarding and poll the decision. Penumbra holds a thin identity record.
Subscribe to the Pen.v1 events; HMAC-signed, retried, 99.9% delivery SLO.
Penumbra ships native in Aquifer, our merchant-services CRM, and connects to the CRM you already run. The pattern is the same everywhere: the client runs server-side, behind your app, and your front-end renders the data in your own UI.
The sk_ key lives on your backend, never in the browser. Your app proxies the calls.
There's no Penumbra app to log into. Transactions, disputes, and payouts surface in your CRM.
sk_test_ hits simulated processors; sk_live_ moves real money. Same code path.
We'll review within one business day and send your sandbox key plus the integration client. Build the whole flow against simulated processors before a dollar moves.