Orders against every payment rail
Each order traced to the rail that collected it, across Stripe and PayPal at once: paid once, charged twice, split across rails, or not paid at all.
GET /v1/reconciliations/across/rails
Reconciliation infrastructure · retail & payments
Your POS, your processors, your order ledger and your bank each tell a different story about the same money. Okwan matches them record by record and says what agrees, what differs and why, as an engine your product embeds.
recon.across_rails
Shopify orders against Stripe and PayPal
#1002
$999.00
Stripe $999.00
PayPal $999.00
Both rails took the full total. $999.00 is owed back.
#1003
$150.00
Stripe $150.00
Matched on the order reference.
#1004
$450.00
Stripe $450.00
PayPal $450.00
The same order, collected on both rails.
#1005
$120.00
PayPal $115.32
PayPal's $4.68 fee accounts for the gap exactly.
#1006
$75.00
nothing collected
Marked paid in the ledger. Nothing on any rail.
—
no order
PayPal $333.00
Collected against #9999, which doesn't exist.
2 orders charged twice · $1,449.00 owed back
read: Shopify · Stripe · PayPal
6
connectors live today
7
verdicts, where most tools give two
1 → 3
one definition becomes an API, a SQL view and an agent tool
0
write access to any payment rail
Retail first
For POS dealers, ISVs and payment providers whose merchants sell in store and online, and collect on more than one system. Three reconciliations run today; the next three are chosen with the platforms we work with.
Each order traced to the rail that collected it, across Stripe and PayPal at once: paid once, charged twice, split across rails, or not paid at all.
GET /v1/reconciliations/across/rails
When a processor fee or a refund accounts for a gap exactly, it is named as the cause, so the list you chase holds only real breaks.
explains=[rail_fee, refund]
Point a declaration at a query over your own Postgres and reconcile it against a processor like any other system.
ResourceRef(connector="postgres", resource="sql")
Card batches closed at the register against what the processor actually deposited, by day and by location.
The adjustments that make a day's sales and a day's deposits disagree, each named rather than netted away.
For dealers and ISOs: processor residual statements checked against the merchants and volume you expect to be paid on.
Which of the three in discovery costs your merchants the most? Tell us; it decides what we build next.
Beyond retail
Retail is where we start, not where the engine stops. A reconciliation is a declaration over any two systems that can list their records, so a new industry is a set of connectors, not a new product.
Healthcare
Claims ↔ remittances
What was billed against what payers actually paid, and why the rest did not arrive.
Logistics
Carrier invoices ↔ shipments
Every invoiced shipment against one that moved, at the rate that was agreed.
Subscriptions
Billing ↔ payouts
Invoices and renewals against the money that settled, after fees, refunds and disputes.
Marketplaces
Orders ↔ seller payouts
What buyers paid against what each seller received, net of commission.
These use the same engine as retail; each needs its systems' connectors first.
The distinctions are the product
A tool that only says matched or unmatched turns every refund into a false alarm and pairs look-alike payments by coin flip. Okwan tells them apart, and says what it could not see.
Matched, and the amounts agree.
Matched; a fee or a refund accounts for the gap exactly.
Matched, and nothing accounts for the gap. The number to chase.
More than one equally good candidate. Reported, never guessed.
In the ledger, and on no rail that was read.
Collected, with no record it belongs to.
The other side was not read far enough back to say. Never called missing.
How it works
Declare the match once. Okwan generates a REST route, a SQL view and an agent tool from that one declaration, with nothing hand-written in between, so the three can never drift apart.
Reconciliation(
name="shopify_stripe",
left=ResourceRef(connector="shopify", resource="orders",
operation="list"),
right=ResourceRef(connector="stripe", resource="charges",
operation="list", params={"status": "succeeded"}),
keys=[
ExactRef(left="name", right="order_ref"),
Fuzzy(amount="total_received_minor", currency="currency",
amount_right="amount", timestamp_right="created",
window="7d"),
],
amount=AmountRef(left="net_payment_minor", right="net_minor"),
explains=[Explains(path="fee_minor", side="right", label="rail_fee")],
)GET /v1/reconciliations/shopify_stripe, with request validation and an OpenAPI reference.
recon.shopify_stripe, joinable with anything else in the catalog and fetched live at query time.
okwan_reconcile on the hosted MCP server. Read-only by construction.
For developers
Federated SQL over live connectors. Only the tables a query names are fetched, and only when it runs: no pipeline, no copy of your merchants' data.
Connected today
curl https://okwan.onrender.com/v1/query \
-H "Authorization: Bearer okw_…" \
-d '{"sql": "SELECT o.name, o.total_price_minor, c.amount
FROM shopify.orders o
LEFT JOIN stripe.charges c
ON c.order_ref = o.name"}'Security
Okwan holds credentials to your merchants' payment systems, so the guarantees are structural: enforced by how the system is built, not by a setting someone can change.
A reconciliation over a write operation cannot be declared. Okwan reads; it never moves money.
Each stored credential has its own encryption key and is bound to its tenant, connector and field. None travels in a request.
API keys are stored as hashes. A lost key is replaced, never read back.
Another account's tenant answers 404, not 403, so the API cannot reveal who else is a customer.
We are working with a small number of retail and payments platforms. Bring one reconciliation your team or your merchants do by hand, and we will show you the verdicts.