Payouts & bank reconciliation
How Factuarea ingests Stripe payouts, links them to the charges they group, reconciles them against your Norma 43 bank statement, and emits the payout.reconciled event.
When you connect Stripe via Stripe Connect, Stripe doesn't transfer each
charge to your bank one by one — it batches many charges, subtracts its fees, and
sends a single payout (po_xxx) to your account. The line that lands on your
bank statement reads STRIPE PAYOUT 1.234,56 € and is the net of N charges
minus fees, so it never matches the total of any single invoice.
Factuarea closes that loop. It ingests every payout, links it to the charges that make it up, and reconciles the bank line against the payout — not against an invoice. When you confirm the match, the payout, the bank transaction and all the underlying charges are marked reconciled in one atomic step.
Payouts are read-only on the public API: you can list and inspect them and their reconciliation state, but the reconciliation itself happens in the dashboard against your imported Norma 43 statement. Two v1 endpoints expose them:
- List Stripe payouts
(
payouts:read). - Retrieve a Stripe payout
(
payouts:read).
Ingesting a payout
Every time Stripe completes a payout it sends a payout.paid webhook to your
Connect endpoint. Factuarea reacts to it:
- It records the payout —
connected_account_id(acct_xxx),stripe_payout_id(po_xxx), the net, fees and gross amounts, the currency and the expected arrival date — withstatus: ingested. - It reads the payout's balance transactions on your behalf (a read-only,
paginated call to Stripe) to discover which charges the payout groups and
the total fees. That breakdown is stored as the informative
composition.
Ingestion is idempotent on two levels: by the Stripe event.id (a redelivered
payout.paid is processed at most once) and by the stripe_payout_id (two
different events for the same po_xxx never create a duplicate row — the
uniqueness is guaranteed in the database, even under concurrent webhooks).
If the breakdown can't be read (a transient Stripe error after retries), the
payout is not half-ingested: the whole step is retried, and the event.id
is only marked processed once ingestion fully succeeds. There is never a payout
row without its amounts.
To ingest payouts you must enable the payout.paid event on your Connect
webhook endpoint in the Stripe Dashboard. As always, validate the flow with a
fact_test_ key first — in the sandbox ingestion runs
against an isolated company with all external effects switched off.
Linking charges to the payout
The balance transactions tell Factuarea which charges compose the payout. Each
component carries its Stripe payment_intent (pi_xxx) — the same identifier
Factuarea stamped on the Payment it recorded when the charge was auto-invoiced
(see Stripe auto-invoicing).
Using that identifier, Factuarea finds the matching Payment records of your
company and stamps the payout id on each one. So a payout's charges are linked to
the cobros that produced them — the relationship that later lets reconciliation
cascade down to every charge.
Linking is best-effort and idempotent: re-linking the same payout is not an
error, and a component with no corresponding Payment (a charge collected before
auto-invoicing existed, or by another tool) is recorded in the integration log
without blocking the rest. The payout is ingested regardless — reconciliation
matches on the net amount, it never requires a complete charge breakdown.
Reconciling against the bank statement
Reconciliation runs over your imported Norma 43 bank statement, in the dashboard. When you upload a statement, Factuarea proposes matches for each credit line. Before trying to match a credit against a pending invoice, it checks whether the line is a payout:
| Signal | Rule |
|---|---|
| Amount | The bank credit equals the payout's net amount (within a small rounding tolerance). This is the hard signal. |
| Arrival window | The bank value date falls within ±3 days of the payout's arrival_date (banks settle with a small lag). |
| Description | A STRIPE mention adds confidence but is never required — the wording varies between banks. |
The outcome depends on how many ingested payouts fit:
- Exactly one candidate → an automatic payout match.
- More than one → a suggestion with the candidates, for you to pick.
- None → the line continues to the ordinary per-invoice matching (a credit that isn't a payout should still match an invoice).
A transaction matched against a payout is excluded from per-invoice matching, and vice-versa, so the same bank line is never reconciled twice.
Matching is per currency. The payout is ingested in its real currency and only matches bank lines in the same currency — there is no conversion (this is accounting reconciliation, not a fiscal operation, so it never issues or alters an invoice).
Confirming the match
When you confirm a payout match in the dashboard, Factuarea performs one atomic transaction:
- The bank transaction is marked reconciled (with match type
payout). - The payout transitions to
reconciled(a terminal state) and records the reference of the bank transaction inbank_transaction_ref. - Every
Paymentlinked to the payout is stamped with itsreconciled_atand the bank transaction reference.
Guards protect every step: the bank transaction must still be pending, the
payout must still be ingested, and the amounts must agree. Confirming a payout
that is already reconciled, or a transaction that is already matched, is
rejected with no partial state left behind. The whole operation is tenant-scoped:
a payout or transaction of another company is never visible nor reconcilable.
Inspecting payouts on the API
List your company's payouts with cursor pagination, filtered by reconciliation
status and by arrival-date window:
curl "https://api.factuarea.com/v1/payouts?status=ingested&arrival_date[gte]=2026-01-01&limit=25" \
-H "Authorization: Bearer fact_test_…"{
"data": [
{
"id": "0192f3a4-7b2c-7e10-9c1a-1f2e3d4c5b6a",
"object": "stripe_payout",
"connected_account_id": "acct_1QabcDEF2ghIJklm",
"stripe_payout_id": "po_1QabcDEF2ghIJklm",
"amount_net": "1234.56",
"fee_total": "37.04",
"amount_gross": "1271.60",
"currency": "EUR",
"arrival_date": "2026-01-08",
"status": "ingested",
"reconciled_at": null,
"bank_transaction_ref": null,
"composition": {
"components": [
{ "payment_intent": "pi_3QabcDEF2ghIJklm", "charge_id": "ch_3QabcDEF2ghIJklm", "amount": 121.00, "fee": 3.50 }
],
"fee_total": 37.04
}
}
],
"has_more": false,
"next_cursor": null
}Every identifier is opaque:
idis the payout UUID v7 — the public identity of the resource.connected_account_id(acct_xxx) andstripe_payout_id(po_xxx) are external Stripe ids, not foreign keys to other Factuarea resources.bank_transaction_refis the UUID (v7) of the reconciled bank statement transaction —nullwhile the payout is stillingested.compositionreferences opaque Stripe ids (payment_intent=pi_xxx,charge_id=ch_xxx), not internal payment UUIDs. It may be empty when the breakdown couldn't be read.
A payout's status is ingested until it is reconciled against a bank line, then
reconciled (terminal). Retrieve a single payout by its id:
curl "https://api.factuarea.com/v1/payouts/0192f3a4-7b2c-7e10-9c1a-1f2e3d4c5b6a" \
-H "Authorization: Bearer fact_test_…"It returns 404 if the payout doesn't exist or belongs to another company.
The outbound event
When a payout is reconciled, Factuarea emits the payout.reconciled event. Its
payload carries the full payout snapshot (object) plus the net amount, currency
and the bank transaction reference, so a webhook receiver can close its own books
the moment the money is confirmed in the bank. Subscribe to it like any other
event — see Webhooks and Events.
There is no payouts:write scope: payouts are observed, never mutated, through the
API. The only state change — reconciliation — is driven from the dashboard against
your Norma 43 statement, and the event is what notifies your integration.
Stripe auto-invoicing
Auto-issue invoices from Stripe Connect charges — NIF capture in Checkout, the simplified-invoice threshold, requiring a NIF, and which charges are routed to manual review.
AEAT census verification
Check your company's — and your clients' — name + NIF pair against the AEAT census before issuing VeriFactu invoices — deterministic states, sandbox magic NIFs and fail-open behavior.