Factuarea API
Payment gateways

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:

Ingesting a payout

Every time Stripe completes a payout it sends a payout.paid webhook to your Connect endpoint. Factuarea reacts to it:

  1. 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 — with status: ingested.
  2. 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:

SignalRule
AmountThe bank credit equals the payout's net amount (within a small rounding tolerance). This is the hard signal.
Arrival windowThe bank value date falls within ±3 days of the payout's arrival_date (banks settle with a small lag).
DescriptionA 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:

  1. The bank transaction is marked reconciled (with match type payout).
  2. The payout transitions to reconciled (a terminal state) and records the reference of the bank transaction in bank_transaction_ref.
  3. Every Payment linked to the payout is stamped with its reconciled_at and 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:

  • id is the payout UUID v7 — the public identity of the resource.
  • connected_account_id (acct_xxx) and stripe_payout_id (po_xxx) are external Stripe ids, not foreign keys to other Factuarea resources.
  • bank_transaction_ref is the UUID (v7) of the reconciled bank statement transaction — null while the payout is still ingested.
  • composition references 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.

On this page