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:
- Retrieve the auto-invoicing config
(
stripe_autoinvoicing:read). - Update the auto-invoicing config
(
stripe_autoinvoicing:write). - List auto-invoiced charges
(
stripe_autoinvoicing:read). - List auto-invoiced correctives
(
stripe_autoinvoicing:read).
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 —nullmeans the company default invoice series is used. It must be a series of complete invoices (invoice_kind: complete), the purpose that numbersF1; - 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):
- List connected accounts
(
stripe_autoinvoicing:read). - Retrieve a connected account
(
stripe_autoinvoicing:read). - Update a connected account
— name,
series_id(sendnullto clear it), and the per-account configuration (stripe_autoinvoicing:write). - Disconnect a connected account
(
stripe_autoinvoicing:write).
# Assign the CURSOS series and enable auto-invoicing on one store
curl -X PATCH https://api.factuarea.com/v1/companies/{company}/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 PATCH 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 PATCH 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.
Connect a Stripe account you already have
Factuarea offers two ways to connect Stripe, and you choose between them when you connect an account:
| Method | When to use it | What happens |
|---|---|---|
Existing account (stripe_app) | You already collect payments with your own Stripe account and want to keep it | You install the Factuarea Stripe App from Stripe and authorise it with OAuth 2.0. No new account is created and nothing moves |
New account (account_links) | You do not have a Stripe account yet | Factuarea creates a Standard account and takes you through Stripe's onboarding (Account Links) |
Without an explicit choice the method is account_links, the same flow as ever.
Each connected account reports the method it was linked with in
connection_method, so the two kinds can live side by side in one company.
Install the Stripe App
- In Factuarea, connect a Stripe account and choose the existing-account option.
- Stripe asks you to install the Factuarea app on the account you pick and to approve its permissions.
- Back in Factuarea, the account appears as a connected account with
connection_method: stripe_appandauthorization_status: authorized. As with any new account, its auto-invoicing is off until you configure it.
The app asks only for what the integration reads: events, charges, payment intents, Checkout sessions, customers, invoices, payouts, balance transactions and disputes. Its only write permission is creating Checkout sessions, which Factuarea uses for payment links of your invoices.
Authorization status and reauthorizing
Every connected account exposes authorization_status:
| Value | Meaning |
|---|---|
authorized | Factuarea can call Stripe on behalf of the account |
reauthorization_required | Stripe no longer accepts the authorization (revoked or expired). Events that need the Stripe API wait in the inbox as stripe_authorization_required and your admins are notified |
revoked | The account was disconnected or the app was uninstalled |
Factuarea renews the access of each account automatically, and a daily task renews the accounts that have had no activity, so the authorization never lapses just because nothing was charged for a while. Events that do not need the Stripe API, such as a refund, keep being processed while a reauthorization is pending.
To reauthorize, repeat the connection with the existing-account option in the same
company. The account keeps its id, name, series, settings and rules, goes back
to authorized, and you can replay
the events that were waiting.
Uninstall or disconnect
- Uninstalling the app in Stripe disconnects the account in Factuarea: its
tokens are deleted, its
authorization_statusbecomesrevoked, and your admins are notified (stripe_app_uninstalledin the inbox). Invoices and history are kept, and later events from that account are recorded without being processed. - Disconnecting from Factuarea (Disconnect a connected account) does the same on your side. The app stays installed in Stripe until you uninstall it from your Stripe Dashboard.
One account, one company
A Stripe account can be active in only one company at a time:
- Connecting an account that is active in another company is rejected with
409(stripe_account_linked_elsewhere). The message does not reveal which company has it, and nothing is stored. - Connecting again an account that is already active in your company renews its
credentials, its method and its authorization status without duplicating it:
it keeps its
id, name, series, settings and rules. - Once an account is disconnected, another company can connect it.
- Webhooks are routed to the company that has the account active.
Authorizing an account from a second company can invalidate the access Stripe
had issued to the first one. In that case the first company's account moves to
reauthorization_required and its admins are notified.
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:
| Situation | Invoice issued |
|---|---|
| A valid NIF is captured in Checkout, or the resolved client already has a NIF on file | Ordinary (F1) |
| No NIF, charge total at or below the threshold, and "require NIF" is off | Simplified (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. They are all listed in the integration event inbox, which is where you see what happened to each one.
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 PATCH https://api.factuarea.com/v1/companies/{company}/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).
Simplified invoices up to 3,000 €. Art. 4.2.e of Royal Decree 1619/2012
allows a simplified invoice up to 3,000 € (VAT included) for certain services,
among them restaurants and catering. The default threshold stays at 400 € because
Factuarea does not know your activity: raise simplified_threshold_cents only
after confirming with your tax advisor that your activity is covered.
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 data | Invoice line |
|---|---|
rate.percentage | the line's VAT rate (vat_rate), used as-is — never recomputed |
taxable_amount | the line's taxable base (unit price = base ÷ quantity) |
taxability_reason zero_rated / product_exempt / customer_exempt | exempt line at 0 % |
taxability_reason reverse_charge | reverse 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.
Concept of charges without Checkout
A charge that arrives without a Checkout session has no line items to mirror, so the invoice gets a single line. Its description is the first of these that exists:
- the
conceptof the charge rule that matched; - the description of the PaymentIntent;
- the default concept of the account,
default_line_concept; - the generic text,
Cobro Stripe pi_….
The text is cut at 255 characters. Set the default concept of an account with
Update a connected account
(stripe_autoinvoicing:write), from 1 to 255 characters; null clears it:
curl -X PATCH https://api.factuarea.com/v1/companies/{company}/connected-accounts/0192f3a4-… \
-H "Authorization: Bearer fact_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0192f3a4-…" \
-d '{ "default_line_concept": "Online reservation" }'default_line_concept, connection_method and authorization_status are part of
every connected account in List
and Get a connected account.
Note and due date of automatic invoices
Every invoice that Factuarea issues automatically from a connected account can carry a note that you configure once per account, invoice_note: up to 500 characters, trimmed, written in the notes of each invoice. It is null by default, and then the invoices carry no note.
Set it with Update a connected account (stripe_autoinvoicing:write). Omitting invoice_note leaves it as it is, an empty string or null clears it and a text assigns it; a text longer than 500 characters returns 422 parameter_invalid_value with param invoice_note:
curl -X PATCH https://api.factuarea.com/v1/companies/{company}/connected-accounts/0192f3a4-… \
-H "Authorization: Bearer fact_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0192f3a4-…" \
-d '{ "invoice_note": "Paid online by card. Thank you for your purchase." }'When the charge was in another currency, the line with the conversion to EUR and the ECB rate is appended to the note. invoice_note is part of every connected account in List and Get a connected account, and the MCP tool update_stripe_connected_account accepts it.
Automatic invoices take the default due date of any invoice: 30 days from the issue date.
Email each invoice to the payer
Each connected account can also email every invoice that Factuarea issues automatically to the address that comes with the Stripe charge, that is, the person who paid: email_invoice_to_payer, false by default. Factuarea takes the address from the Checkout (customer_details.email), from the Stripe invoice of a subscription (customer_email) or from the charge itself (receipt_email), and sends the invoice with your company template, its PDF attached and no payment button, because it is already paid.
The address is only where the invoice is delivered: it is not stored in the invoice. A simplified invoice stays a simplified invoice, without a recipient; when the charge identifies the customer, the full invoice is issued to that customer as usual and is emailed to the address of the charge as well.
Turn it on with Update a connected account (stripe_autoinvoicing:write). Omitting it leaves it as it is, and a value that is not a boolean returns 422 parameter_invalid_value with param email_invoice_to_payer:
curl -X PATCH https://api.factuarea.com/v1/companies/{company}/connected-accounts/0192f3a4-… \
-H "Authorization: Bearer fact_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0192f3a4-…" \
-d '{ "email_invoice_to_payer": true }'Sending never undoes the invoice or the payment, and it never makes the webhook fail. When a charge carries no email, or the email is rejected, the invoice is issued and paid all the same, and the outcome is recorded in the integration events: autoinvoice.email_requested, autoinvoice.email_skipped (the charge has no valid email) or autoinvoice.email_failed (the email was rejected, for example because the sender identity of your company is not verified yet). The option is part of every connected account in List and Get a connected account, and the MCP tool update_stripe_connected_account accepts it.
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 entry lands in the
inbox as
unsupported_currency, parked: once the official rate for that date is published, replaying the event issues the invoice.
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).
Charges that are not invoiced: treatment rules
Not every charge is a taxable operation. A cancellation fee charged to a card, or the sale of a gift voucher usable for any service, may not be an operation to invoice at the time of payment. Each connected account has its own treatment rules that decide what happens to a spontaneous charge, one that does not pay an invoice you already had in Factuarea:
- Invoice it, optionally with its own concept and VAT rate.
- Do not invoice it, stating one reason from a closed list. No invoice, no
Paymentand no VeriFactu record are created; the charge is kept in a list of non-invoiced charges.
Rules are evaluated in order of position, and the first one that matches
decides. If none matches, the charge is invoiced as described in the rest of
this page.
Conditions
condition.type | Matches when | Limits |
|---|---|---|
metadata_equals | The charge has the metadata key with that value | key 1–40 characters, exact; value 1–500, compared ignoring case and surrounding spaces |
description_contains | Any description of the charge contains the text | value 2–255 characters, ignoring case and accents |
payment_link | The charge was paid through that Payment Link | value in the form plink_… |
The metadata is that of the PaymentIntent merged with that of the Checkout session or the Stripe invoice (the latter wins when a key repeats). The descriptions compared are the one of the PaymentIntent, those of the Checkout lines, and those of the Stripe invoice and its lines.
Outcomes
outcome | Fields | Effect |
|---|---|---|
invoice | concept (optional, 1–255 characters); vat_rate (optional, an existing sales VAT rate) | The invoice is issued as always, with that concept and that VAT. Lines that carry a Stripe Tax breakdown keep it |
do_not_invoice | non_invoice_reason (required); concept and vat_rate are not allowed | The charge is recorded and not invoiced |
The non_invoice_reason values are indemnity_not_subject,
multipurpose_voucher and invoiced_elsewhere (the charge is already invoiced in
another system). A combination that breaks these rules answers 422
(charge_rule_invalid, with the field in error.param), and an account can hold
up to 50 rules (charge_rule_limit_reached).
Rules are never applied to the charge that pays an invoice created in
Factuarea (the one carrying metadata.factuarea_invoice) nor to subscription
cycles.
Examples and their legal basis
Check each template with your tax advisor. Factuarea does not decide whether a charge is subject to VAT: a rule applies the treatment you configure, and reviewing it is the responsibility of the taxpayer. The references below explain the criteria; they are not advice for your particular case.
Cancellation or no-show fee, with no prior deposit. The Directorate-General for Taxes (DGT) binding ruling V0839-20 considers that the card charge for a late cancellation or a no-show, without a prior deposit, has the nature of an indemnity, so there is no operation subject to VAT. It rests on art. 78.Tres.1º LIVA: indemnities that by their nature and function are not the consideration for a taxable operation are not part of the taxable base. If the customer had paid a deposit that you keep, the treatment is different and is corrected with a manual corrective invoice.
{
"condition": { "type": "metadata_equals", "key": "type", "value": "penalty" },
"outcome": "do_not_invoice",
"non_invoice_reason": "indemnity_not_subject"
}Single-purpose voucher. When the service and its VAT rate are known when the voucher is sold, its sale is already the taxable operation and it is invoiced at the rate of that service (Resolution of the DGT of 28 December 2018, BOE-A-2018-18094).
{
"condition": { "type": "description_contains", "value": "gift voucher" },
"outcome": "invoice",
"concept": "Gift voucher",
"vat_rate": 10
}Multi-purpose voucher. When the service or the rate is not known yet, its sale
is not subject to VAT and the tax accrues when the voucher is redeemed (same
Resolution, third section). Charges excluded this way keep their reason
multipurpose_voucher.
{
"condition": { "type": "payment_link", "value": "plink_1Pabc2DEFghi3JKL" },
"outcome": "do_not_invoice",
"non_invoice_reason": "multipurpose_voucher"
}In the app, these three come as templates, each with its legal basis and the reminder to confirm it with your tax advisor.
Manage the rules
Six operations under connected-accounts/{account}, with the scopes
stripe_autoinvoicing:read (lists) and stripe_autoinvoicing:write (the rest).
The id of a rule is a UUID v7, and a rule or an account of another company
answers 404 (charge_rule_not_found, connected_account_not_found).
| Operation | Endpoint | Scope | MCP tool |
|---|---|---|---|
| List the rules | GET /v1/companies/{company}/connected-accounts/{account}/charge-rules | stripe_autoinvoicing:read | list_stripe_charge_rules |
| Create a rule | POST /v1/companies/{company}/connected-accounts/{account}/charge-rules | stripe_autoinvoicing:write | create_stripe_charge_rule |
| Reorder the rules | PUT /v1/companies/{company}/connected-accounts/{account}/charge-rules/order | stripe_autoinvoicing:write | reorder_stripe_charge_rules |
| Update a rule | PUT /v1/companies/{company}/connected-accounts/{account}/charge-rules/{rule} | stripe_autoinvoicing:write | update_stripe_charge_rule |
| Delete a rule | DELETE /v1/companies/{company}/connected-accounts/{account}/charge-rules/{rule} | stripe_autoinvoicing:write | delete_stripe_charge_rule |
| List the non-invoiced charges | GET /v1/companies/{company}/connected-accounts/{account}/non-invoiced-charges | stripe_autoinvoicing:read | list_stripe_non_invoiced_charges |
- Create answers
201with the rule, placed at the end of the list. - Update is a full
PUT: it sendscondition,outcome,concept,vat_rateandnon_invoice_reasonagain, and it does not change theposition. - Reorder takes every
idof the account's rules in the new order:{ "rule_ids": ["…", "…"] }. A list that is incomplete, repeated or contains an id that is not in the account answers422(charge_rule_invalid) and changes nothing. - Delete answers
204. The charges it had excluded keep their record.
{
"id": "0192f3a4-7b2c-7c1d-9e8f-1a2b3c4d5e6f",
"object": "charge_rule",
"position": 1,
"condition": { "type": "metadata_equals", "key": "type", "value": "penalty" },
"outcome": "do_not_invoice",
"non_invoice_reason": "indemnity_not_subject",
"concept": null,
"vat_rate": null,
"created_at": "2026-10-07T09:30:00+00:00",
"updated_at": "2026-10-07T09:30:00+00:00"
}Non-invoiced charges
Every charge excluded by a rule is recorded once, whatever the number of events
Stripe sends for it. The list is paginated by cursor (limit 1–100, 25 by
default, starting_after and ending_before), newest first, and can be narrowed
to a period with occurred_from and occurred_to (YYYY-MM-DD, both included,
by the date of the charge):
{
"data": [
{
"id": "0192f3b8-1e4a-7f20-8c3d-5e6f7a8b9c0d",
"object": "non_invoiced_charge",
"connected_account_id": "0192e7c2-4d5e-7f3b-a012-3c4d5e6f7081",
"transaction_id": "pi_3PenaltyAbC",
"amount": 30.0,
"refunded_amount": 10.0,
"net_amount": 20.0,
"currency": "EUR",
"description": "No-show fee",
"reason": "indemnity_not_subject",
"charge_rule_id": "0192f3a4-7b2c-7c1d-9e8f-1a2b3c4d5e6f",
"occurred_at": "2026-10-07T20:15:00+00:00",
"refunded_at": "2026-10-08T09:02:00+00:00"
}
],
"has_more": false,
"next_cursor": null
}amount, refunded_amount and net_amount are in major units of the charge currency, at Stripe's decimal scale for that currency (3,000 JPY is 3000, 30.00 EUR is 30). charge_rule_id is null when the rule that excluded the charge was deleted
afterwards. The inbox shows each of
these events as excluded_by_charge_rule.
- A refund of a non-invoiced charge creates no corrective invoice and does not
go to manual review: it is recorded as
refund_of_non_invoiced_chargeand adds to the charge'srefunded_amount(succeeded and pending refunds), with the date of the latest one inrefunded_at.net_amountis what the merchant keeps:amountminusrefunded_amount. If a pending refund later fails or is canceled, it is subtracted again. - In the quarterly package, the
ZIP
and the email to the accountant
add
cobros_sin_factura_Q{quarter}_{year}.csvwith the charges of the quarter and the refunds made in it: each refund counts in the quarter it happens, as a negative line when the charge belongs to an earlier quarter. Sendinclude_non_invoiced_charges: falseto leave the file out. - A dispute of a non-invoiced charge does not cancel any payment.
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.
| Refund | Corrective 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.
Each document is numbered in a series of its own purpose: the corrective goes in the
company's default corrective series (R-2026-001…; RS for an R5), and an
auto-invoiced simplified invoice in the default simplified series, not in the
account's series. Any of them is created on the spot if the company has none.
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
inbox 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.
Opting out parks the refunds, it does not discard them. With
refunds_enabled: false, each refund is recorded in the
inbox as
refund_autoinvoicing_disabled and kept, encrypted, for 30 days. Within
that window pick one of the two exits, never both: turn refunds_enabled
back on and replay the event, or
issue the corrective by hand. Doing both leaves the refund with two
correctives, each numbered in its series and filed with VeriFactu — the
replay's idempotency only recognises correctives issued through that same
automatic path, so one you wrote by hand does not stop it. Past the 30 days the
content is purged and issuing it by hand is the only route left.
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 PATCH https://api.factuarea.com/v1/companies/{company}/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_reason | What 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, quote_accept | Invoiced from invoice.paid, with the real lines of the Stripe invoice — see Invoices created in Stripe |
upcoming, others | Ignored with an info log |
- Trials don't invoice. A cycle whose
invoice.totalis 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. They land in the inbox as
subscription_proration_review, which is actionable but not replayable: thebilling_reasonnever changes, so the invoice has to be issued by hand. 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).
That same context also travels on the invoice itself, as system metadata keys you can filter listings by. See Metadata and reconciliation for the whole table — which keys are always written, which are omitted when they don't apply — and two ready-made recipes: every invoice of one subscription, and the invoices of a single billing period.
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.
A cycle that arrived while the toggle was off can still be invoiced — for 30
days. It is not silently dropped: it is recorded in the
inbox as
subscription_autoinvoicing_disabled, with its content kept encrypted. Turn
subscription_autoinvoicing_enabled (and enabled) on and
replay the event within that window,
and the cycle's real invoice is issued — with its series number and its
VeriFactu record, exactly as if it had been invoiced at the time. After 30 days
the content is purged, the record remains, and the only route left is issuing the
invoice by hand. Cycles billed before your Connect endpoint started sending
invoice.paid never reached Factuarea, so there is nothing to replay for those.
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.
Invoices created in Stripe
An invoice that you create in Stripe by hand (manual) or that comes from an
accepted quote (quote_accept) is invoiced from its invoice.paid event, with
its real lines, as long as the account has auto-invoicing enabled. Otherwise it
waits in the inbox as
autoinvoicing_disabled, parked, so you can replay it.
- The charge is identified by the PaymentIntent that paid the invoice (the invoice id when there is none), so the Factuarea invoice is the same one that would have been issued from the payment itself.
- The treatment rules apply, using the description and the lines of the Stripe invoice.
- The F1/F2 decision, the conversion to EUR and the total check are the same as for any charge.
One charge, one invoice
The same charge can reach Factuarea through several events. Factuarea recognises what each payment belongs to, so it never produces two invoices:
- The PaymentIntent of a Checkout session is invoiced with the data of the session (lines, NIF, metadata and Payment Link), whichever event arrives first.
- The PaymentIntent of an invoice created in Stripe or of a subscription
cycle is not invoiced as a spontaneous charge: it is recorded in the inbox as
handled_by_stripe_invoice, and its invoice comes frominvoice.paid. - A redelivered event, or a second event of the same charge, produces at most one invoice.
Defaults at a glance
| Field | Default | Effect of the default |
|---|---|---|
enabled | false | Auto-invoicing off — nothing is issued until you enable it |
simplified_threshold_cents | 40000 (400 €) | Charges without a NIF up to 400 € become F2 |
require_nif | false | Simplified invoices are allowed; tax ID is offered but not required |
refunds_enabled | true | A Stripe refund generates an automatic corrective invoice (while auto-invoicing is on) |
subscription_autoinvoicing_enabled | false | Stripe subscription cycles are not auto-invoiced until you enable it (requires enabled too) |
invoice_note | null | Automatic invoices carry no note (only, if the charge was in another currency, the conversion to EUR) |
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.