Factuarea API
Payment gateways

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.

When you connect Stripe via Stripe Connect, Factuarea can auto-issue an invoice for every successful charge: the charge is turned into an invoice with status: sent, registered for VeriFactu, and a Payment is recorded against it. The flow is idempotent end-to-end, so a redelivered webhook never produces a duplicate invoice.

Two flows feed it:

  • Flow A — a charge that pays an existing Factuarea invoice (a Checkout Session Factuarea created from a payment link). The invoice already exists; the charge marks it paid.
  • Flow B — a spontaneous charge with no prior invoice (a Payment Link the merchant created in their own Stripe Dashboard, or any other Connect charge). Factuarea creates the invoice from the charge.

The configuration is read and written through the company-wide v1 endpoints:

If you run several stores under separate Stripe accounts, each account has its own series and its own configuration — see Multiple stores.

Auto-invoicing is gated by the Stripe integration module of your plan and is off by default — enable it explicitly with enabled: true.

What the Stripe API exposes. Company-wide auto-invoicing configuration (a convenience inherited from the single-store model — the real source of truth is the per-account config, see Multiple stores), connected accounts, auto-invoiced charges, correctives, and payouts for bank reconciliation.

Multiple stores

A business can run several "stores" or lines (a physical shop + an online course) under different Stripe accounts (Stripe Connect) and want independent invoice numbering for each one (TIENDA-2026-…, CURSOS-2026-…). Factuarea models each Stripe account you connect as a connected account: every Account Link you complete adds an account — it never overwrites the previous one — and charges on each account are routed to that account's series and configuration.

Each connected account carries:

  • a series (series_id) used for the invoices auto-created from its charges — null means the company default invoice series is used;
  • its own auto-invoicing configuration (autoinvoicing_enabled, simplified_threshold_cents, require_nif, refunds_enabled, subscription_autoinvoicing_enabled) — every fiscal rule on this page applies per account.

When a webhook arrives, Factuarea resolves the Stripe account (acct_xxx) to its connected account and issues the invoice in that account's series, with that account's fiscal policy — so two stores produce invoices in two separate, correct numbering series.

New accounts start safe

A newly connected account does not inherit another account's configuration: it starts with the same safe defaults as a fresh setup — auto-invoicing off, threshold 400 €, "require NIF" off, refunds on, subscriptions off — and no series (it falls back to the company default until you assign one). Configure it explicitly before it issues anything.

Per-account v1 endpoints

Manage accounts under the connected-accounts resource (same stripe_autoinvoicing:read|write scopes; identity is the account id, a UUID v7):

# Assign the CURSOS series and enable auto-invoicing on one store
curl -X PUT https://api.factuarea.com/v1/connected-accounts/0192f3a4-… \
  -H "Authorization: Bearer fact_test_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0192f3a4-…" \
  -d '{
    "series_id": "0192aaaa-…",
    "autoinvoicing_enabled": true
  }'

Disconnecting an account keeps its already-issued invoices and history; later webhooks for it are recorded without processing. Referencing the id of an account that belongs to another company returns 404 (connected_account_not_found) — multi-tenant isolation never leaks existence.

The company-wide endpoint while you have one store

The company-wide config endpoints above stay valid while you have exactly one connected account: the GET returns that single account's effective config, and the PUT proxies to it (it writes the account's configuration, never a duplicate company-level copy).

As soon as you connect a second account, the company-wide config can no longer answer "which account?". Both the GET and the PUT then return 422 (per_account_config_required, message in Spanish) pointing you to the per-account endpoints — Factuarea never writes to two places, so there is no divergence between a company-level config and the accounts.

One source of truth. The per-account configuration is the only place settings live. The company-wide endpoint is a convenience that proxies to the single account; it never holds a separate copy, so reading and writing always agree. With two or more accounts, use connected-accounts/{account} directly.

Ordinary or simplified invoice

A Spanish invoice needs the recipient's NIF to be issued as an ordinary invoice (F1). A B2C charge without a NIF is exactly the case the law settles with a simplified invoice (F2). Factuarea decides which one to issue from the data the charge carries plus your fiscal policy:

SituationInvoice issued
A valid NIF is captured in Checkout, or the resolved client already has a NIF on fileOrdinary (F1)
No NIF, charge total at or below the threshold, and "require NIF" is offSimplified (F2)
No NIF and (total above the threshold or "require NIF" is on)Manual review — no invoice is auto-issued

A captured NIF is validated against the Spanish format (NIF/NIE/CIF). A NIF with an invalid format counts as no NIF, so an F1 is never issued with junk data.

Charges routed to manual review are not lost: they are recorded in the integration log so you can issue the invoice by hand. Auto-invoicing keeps going for the rest — a charge in review never fails the webhook.

The absolute legal ceiling for a simplified invoice is 3,000 €, enforced by the invoicing domain itself: a charge without a NIF above 3,000 € always goes to manual review, whatever the threshold is set to.

Capturing the NIF in Checkout

So a client who does have a NIF can provide it, the Checkout Sessions Factuarea creates (Flow A) enable Stripe's tax-ID collection. The client can enter their es_cif/eu_vat at payment time, and that NIF drives the F1 path.

NIFs are also read from any incoming checkout.session.completed:

  • from customer_details.tax_ids (the standard Stripe field), and
  • from the custom fields of Payment Links the merchant builds in their own Stripe Dashboard — Factuarea looks for a field whose key looks like a tax ID (nif, dni, cif, vat, tax).

A charge that arrives only as payment_intent.succeeded (no Checkout) carries no captured NIF, but it can still be an F1 if the client is resolved by email and already has a NIF on file.

The threshold

simplified_threshold_cents is the amount in cents at or below which a charge without a NIF is auto-issued as a simplified invoice. It defaults to 40000 (400 €) and accepts any value in the range [0, 300000] (0–3,000 €).

A conservative default of 400 € is deliberate: the 3,000 € ceiling is only legal in specific rated sectors, and Factuarea does not know your sector — raise the threshold only if your activity allows it.

curl -X PUT https://api.factuarea.com/v1/stripe-autoinvoicing/config \
  -H "Authorization: Bearer fact_test_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0192f3a4-…" \
  -d '{
    "enabled": true,
    "simplified_threshold_cents": 100000,
    "require_nif": false
  }'

Both fiscal fields are optional: omit simplified_threshold_cents or require_nif and their current values are kept. A threshold outside the range returns 422 (validation_error).

Requiring a NIF

require_nif (default false) has two coherent effects:

  • In the Checkout Sessions Factuarea creates, the tax ID is marked required (if_supported), so the client is prompted for it.
  • In the decision above, it vetoes the F2: a charge without a NIF goes to manual review instead of becoming a simplified invoice.

Turn it on when your company never wants automatic simplified invoices — every charge then either has a NIF (F1) or waits for you in manual review.

Auto-issued invoices are fiscally real and irreversible (the VeriFactu Alta record is created). Validate your fiscal policy with a fact_test_ key first: in the sandbox the VeriFactu record is created locally and never transmitted to the AEAT, so you can exercise the F1/F2/review decision safely before going live.

The outbound event

Every auto-created invoice — F1 or F2 — emits the invoice.auto_created event and a payment.received event. For a simplified invoice the payload carries client_id: null (no recipient), so a webhook receiver can tell F1 and F2 apart.

Real VAT breakdown with Stripe Tax

If you use Stripe Tax, every charge already carries the real tax breakdown per line — the rate, the taxable base, and (where it applies) the reason a line is exempt or reverse-charged. Factuarea mirrors that breakdown onto the invoice instead of flattening everything to a single default rate.

The Checkout webhook doesn't include the line items, so Factuarea makes a second, read-only API call on your behalf to retrieve them with their taxes, then maps each line:

Stripe Tax dataInvoice line
rate.percentagethe line's VAT rate (vat_rate), used as-is — never recomputed
taxable_amountthe line's taxable base (unit price = base ÷ quantity)
taxability_reason zero_rated / product_exempt / customer_exemptexempt line at 0 %
taxability_reason reverse_chargereverse charge (ISP) line at 0 %

So a charge with mixed VAT (e.g. 21 % consulting + 10 % a book) becomes an invoice with two real lines, each at its own rate, and the multi-rate breakdown carries through to the VeriFactu record.

The VAT is never recalculated — Stripe already computed it, and recomputing would introduce cent-level drift. Factuarea takes the rate and taxable base straight from Stripe Tax.

When there's no Stripe Tax (you haven't enabled it in your Stripe account), nothing changes: every line falls back to your company's default VAT rate, exactly like before. A company with no default rate configured falls back to 0 % — never a phantom 21 %.

Real line items

When a charge carries several line items (several products or concepts), they appear as real, separate lines on the invoice — each with its own description, quantity and price — instead of being collapsed into one. The lines are free lines (not linked to your product catalogue).

This is orthogonal to the invoice type: the F1/F2 decision above chooses the type, the line mapping chooses the lines — both an ordinary and a simplified invoice get the same real lines.

A charge that arrives only as payment_intent.succeeded (no Checkout Session, so no retrievable line items), or a charge whose line retrieval fails after retries, still produces an invoice: it falls back to a single line at the default VAT rate. The invoice is never lost over a non-essential detail.

Before issuing, Factuarea validates the total: the total derived from the mirrored lines (sum of subtotal + VAT per line, in EUR) must match the amount actually charged, within a small rounding tolerance (±1 cent per line, minimum ±0.05 €). If it doesn't, the charge is routed to manual review (total_mismatch) instead of issuing an invoice whose total diverges from the real charge — the webhook still succeeds.

Charges in another currency

A charge in a currency other than EUR is no longer skipped. Factuarea converts it to EUR using the European Central Bank (ECB) reference rate for the payment date and issues the invoice in euros — base, VAT and total, and the VeriFactu/AEAT record, all in EUR (Art. 12.1 RD 1619/2012: the VAT amount must be stated in euros).

  • A EUR charge is passed through untouched.
  • A non-EUR charge with an available rate is converted; the trace of the conversion — original amount and currency, ECB rate, and rate date — is written to the invoice notes and to the integration log for fiscal auditing.
  • A charge in a currency with no ECB rate available is not auto-invoiced: it goes to manual review and the webhook still succeeds. Factuarea never invents a rate.

The ECB rates are cached daily (no extra database table), so several non-EUR charges on the same day share a single rate lookup.

Issuing the invoice in the original currency (option B) is intentionally out of scope — Factuarea always converts to EUR (option A).

Refunds and corrective invoices

An issued invoice is fiscally irreversible — it is never deleted or voided once paid. The only legal way to undo it is a corrective invoice (rectificativa). So when Stripe refunds a charge that Factuarea invoiced, Factuarea closes the fiscal loop for you: the charge.refunded webhook generates a linked corrective invoice automatically (with its own VeriFactu R record), no manual rectification needed.

This is controlled by refunds_enabled (default true). It only acts while auto-invoicing is enabled — the gate is enabled && refunds_enabled. A company that never turned auto-invoicing on sees no change.

RefundCorrective issued
Full (refunded: true)A total corrective that mirrors the whole original invoice as negative lines
Partial (amount_refunded < amount)A partial corrective with a single negative line for the refunded amount, at the original line's tax rate

The corrective is always issued by differences (AEAT TipoRectificativa: I), because a refund is a credit with negative amounts. It carries the devolucion correction reason; if the original is a simplified invoice (F2) the corrective is issued as R5 automatically.

The original invoice is located by two paths — by the factuarea_invoice metadata on the charge (Flow A) or by the deterministic UUID derived from the payment_intent (Flow B) — so refunds of charges issued before this feature existed are corrected too.

Idempotency is per individual refund. Stripe re-emits charge.refunded with the cumulative amount_refunded, but Factuarea keys on each individual refund id (re_xxx): each one produces at most one corrective. A redelivered event, or a second partial refund, never double-credits. A refund whose original invoice can't be located, isn't in a correctable state (sent/paid), or is already corrected is recorded in the integration log for manual review — it never fails the webhook.

Each automatic corrective emits the invoice.corrective_auto_created event (carrying the corrective, the original invoice, the originating refund_id and the provider), and you can list them via List auto-invoiced correctives.

To receive refunds you must enable the charge.refunded event on your Connect webhook endpoint in the Stripe Dashboard. As with auto-invoicing, validate the flow with a fact_test_ key first: in the sandbox the VeriFactu R record is created locally and never transmitted to the AEAT. To opt out, set refunds_enabled: false — refunds then stay in the integration log for manual handling, the current behaviour.

Subscription cycles

If you charge with Stripe Billing on your connected account — recurring monthly or yearly subscriptions — Factuarea can auto-issue an invoice for each billed cycle. Every renewal Stripe collects produces its own VeriFactu-compliant invoice, mirroring the real breakdown (lines, period, taxes) Stripe already computed, exactly like one-shot charges.

This is a separate toggle, subscription_autoinvoicing_enabled (default false), on top of the general enabled flag. Both must be on for a cycle to be invoiced — turning on subscriptions alone does nothing while auto-invoicing is globally off.

curl -X PUT https://api.factuarea.com/v1/stripe-autoinvoicing/config \
  -H "Authorization: Bearer fact_test_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0192f3a4-…" \
  -d '{
    "enabled": true,
    "subscription_autoinvoicing_enabled": true
  }'

Which cycles are invoiced

Factuarea invoices the standard billing cycle and routes everything else to review or ignores it, keyed on Stripe's billing_reason:

billing_reasonWhat Factuarea does
subscription_create (first cycle)Invoiced
subscription_cycle (each renewal)Invoiced
subscription_update, subscription_threshold (standalone prorations)Manual review — recorded in the integration log, not auto-invoiced
manual, upcoming, quote_accept, othersIgnored with an info log
  • Trials don't invoice. A cycle whose invoice.total is 0 € (a trial period, or a cycle fully covered by credit) emits no invoice; the first real charge after the trial is invoiced normally.
  • Prorations standalone (an upgrade/downgrade billed on its own, outside the regular cycle) are not auto-invoiced today — they go to manual review so you decide. The regular cycle that follows is invoiced as usual.
  • The F1/F2 decision is the same. The recipient's NIF is read from the invoice's customer_tax_ids (plus the resolved client's file); with a NIF the cycle is an ordinary (F1) invoice, without one and at or below the threshold an simplified (F2), and without one above the threshold (or with "require NIF" on) it goes to manual review — identical to the rules in the decision section. The same Stripe Tax line/VAT mirroring, EUR conversion and total validation apply.

Each cycle is its own invoice

A subscription cycle becomes a standalone invoice — it does not create or touch a Factuarea recurring invoice. The two are independent: Stripe drives the cadence and each invoice.paid produces one invoice.

Avoid double-invoicing. If you already model the same client's subscription as a manual recurring invoice in Factuarea, enabling subscription auto-invoicing for that Stripe subscription will produce two invoices per period — one from your recurring template and one from the Stripe cycle. Pick one source per client: either stop the manual recurring invoice or leave this toggle off for those subscriptions.

The outbound event

A subscription-cycle invoice emits a distinct event, invoice.subscription_auto_created (plus payment.received), so a webhook receiver can tell subscription cycles apart from one-shot charges. A one-shot charge keeps emitting invoice.auto_created as before. The auto-invoiced charges listing (List auto-invoiced charges) exposes the subscription context (subscription_id, stripe_invoice_id, period_start, period_end) for cycle charges (null for one-shot), and an optional origin filter (subscription/oneshot).

Idempotency is per billing cycle. Factuarea keys on each Stripe invoice id (in_xxx): a redelivered event, or a second event for the same cycle, produces at most one invoice. Historical cycles are not back-invoiced — only cycles billed after you enable the toggle are invoiced; replays of older invoice.paid events are ignored.

To receive subscription cycles you must enable the invoice.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 the VeriFactu Alta record is created locally and never transmitted to the AEAT.

Defaults at a glance

FieldDefaultEffect of the default
enabledfalseAuto-invoicing off — nothing is issued until you enable it
simplified_threshold_cents40000 (400 €)Charges without a NIF up to 400 € become F2
require_niffalseSimplified invoices are allowed; tax ID is offered but not required
refunds_enabledtrueA Stripe refund generates an automatic corrective invoice (while auto-invoicing is on)
subscription_autoinvoicing_enabledfalseStripe subscription cycles are not auto-invoiced until you enable it (requires enabled too)

With these defaults the only behaviour change once auto-invoicing is on is that a charge without a NIF up to 400 € becomes a simplified invoice instead of waiting in review, and a refund generates its corrective invoice automatically. Charges with a NIF behave exactly as before. Subscription cycles stay off until you opt in with subscription_autoinvoicing_enabled.

On this page