What invoice gets generated

How BeeL decides between standard and simplified, how it picks a rectification code (R1–R5), and which series is used.


Every Stripe event that produces an invoice goes through a small decision pipeline. The result is one of: a standard invoice (type: STANDARD), a simplified invoice (type: SIMPLIFIED), or a corrective (type: CORRECTIVE with a rectification_code R1–R5).

If you're new to which AEAT codes map to which API values, Invoice types is the canonical translation page.

Forward invoices: STANDARD vs SIMPLIFIED

When BeeL receives a payment success event (payment_intent.succeeded, invoice.paid, etc.), it creates one invoice. The type comes from two inputs:

        Stripe payment received


  ┌──────────────────────────────────────────┐
  │  Is the amount ≤ the simplified          │
  │  threshold (configured on the connection)?│
  ├──────────────────────────────────────────┤
  │  Yes ▼                  │  No ▼          │
  │                         │                │
  │  Does the customer      │  type:         │
  │  have full fiscal data  │  STANDARD      │
  │  (NIF + address)?       │  (require      │
  │  ├─ No  → SIMPLIFIED    │  NIF or        │
  │  └─ Yes → STANDARD      │  alt_id)       │
  └──────────────────────────────────────────┘

The simplified threshold per connection

Default: 400 €. Configurable per Stripe connection. The maximum allowed is 3 000 € (the legal cap for simplified invoices).

  • Amount threshold AND no fiscal data → type: SIMPLIFIED
  • Amount threshold AND fiscal data present → type: STANDARD (the law accepts standard below the threshold)
  • Amount > threshold → type: STANDARD (mandatory)

You set the threshold in Settings > Integrations > Stripe > Default series and limits.

What "fiscal data" means

BeeL checks the resolved customer (after customer mapping) for:

  • nif (Spanish) or alternative_id (foreign), AND
  • A complete address (street + city + country)

If anything is missing, the path defaults to SIMPLIFIED (when amount allows it) or rejects the request (when STANDARD is required by amount).

Forward invoices: numbering

BeeL resolves the invoice series at issuance time:

  1. Look up your default series for the resolved document type (standard or simplified)
  2. Allocate the next number on that series

If you delete the default for standard or simplified invoices, the next Stripe webhook fails until you set a new default.

You need at least two default series in each environment: one for STANDARD invoices and one for SIMPLIFIED. Plus one for CORRECTIVE if you process refunds.

Corrective invoices: refunds and credit notes

Two Stripe events produce a corrective in BeeL — and the way they behave is slightly different.

Stripe eventBeeL rectification_typerectification_code source
refund.updated (status=succeeded)TOTAL if the refund matches the full payment, PARTIAL otherwiseDerived from refund.reason
credit_note.createdAlways PARTIAL (delta)Derived from credit_note.reason

rectification_code is an enum on the API request (R1/R2/R3/R4/R5) — the same names AEAT uses. See Corrective invoices for the canonical R1–R5 reference.

Mapping Stripe's reason to R1–R5

The mapping is fixed — not configurable per connection. It depends on two inputs: the original invoice's type and the Stripe reason value.

Rule 1 — Original was SIMPLIFIED:

ConditionBeeL rectification_code
Any refund or credit note on a SIMPLIFIEDR5 (always)

R5 is the only valid corrective code for a simplified original, per VeriFactu spec (Art. 80.1–80.2 LIVA). The Stripe reason value is ignored on simplified originals.

Rule 2 — Original was STANDARD (or a previous corrective):

Stripe reason valueBeeL rectification_code
duplicateR4 (resto de causas — duplicate charge is a non-error correction)
fraudulentR4 (resto de causas)
requested_by_customerR1 (most common — customer asked for the refund)
order_changeR1
product_unsatisfactoryR1
expired_uncaptured_chargeR1
Anything else, blank, or nullR1 (default fallback)

R2 and R3 are not auto-emitted from Stripe events — they need explicit fiscal context (insolvency, bad debt) that Stripe doesn't model. Issue those manually via the corrective invoice endpoint when applicable.

Multiple refunds on the same payment

A single Stripe charge can receive multiple partial refunds over time. Each refund fires its own refund.updated event, and each one produces its own corrective in BeeL:

Original payment:    121 €  →  Invoice INV-2026/00042  (type: STANDARD, ACCEPTED)

Refund #1 (re_abc):  -40 €  →  RECTIF-2026/00012  (type: CORRECTIVE, rectification_code: R1, PARTIAL)
Refund #2 (re_def):  -30 €  →  RECTIF-2026/00013  (type: CORRECTIVE, rectification_code: R1, PARTIAL)
Refund #3 (re_ghi):  -51 €  →  RECTIF-2026/00014  (type: CORRECTIVE, rectification_code: R1, PARTIAL — sums to full refund)

Each refund is deduplicated by its own Stripe refund ID (re_…), not by the payment intent. So multiple refunds on the same payment cannot collide.

If a single refund equals the full payment amount, BeeL emits a TOTAL corrective (the original moves to VOIDED). If a sequence of partial refunds eventually sums to the full amount, each one is still PARTIAL — BeeL doesn't retro-promote the chain to TOTAL.

Credit notes (Stripe Billing)

credit_note.created is Stripe Billing's native primitive for crediting a customer (subscription downgrades, prorations, manual credits). Some specifics:

  • The mapping table above applies the same way — credit_note.reason runs through the same R1–R4 / R5 rules
  • BeeL always emits credit-note correctives as PARTIAL, even when they zero out the original
  • Each credit note is deduplicated by its Stripe credit note ID (cn_…)

Finding the original invoice

For a refund or credit note, BeeL needs the original Beel invoice to link the corrective to:

  1. Read the Stripe payment intent ID (charge.payment_intent on a refund, invoice.payment_intent on a credit note)
  2. Look up a Beel invoice that references that payment intent
  3. Build the corrective referencing it

If the original isn't found, the event is parked for retry. Possible causes:

  • The refund arrived before the original payment webhook (out-of-order delivery) — BeeL retries on a backoff
  • The original was issued outside BeeL (legacy data, ad-hoc) — manual handling required
  • The connection that processed the original was deleted — relink and replay

Multi-currency note

Stripe refunds and credit notes can in principle be denominated in any currency. BeeL only processes EUR — non-EUR refunds are skipped the same way non-EUR payments are.

What can't be generated

ScenarioOutcomeWhy
Partial refund on a SIMPLIFIED originalCorrective with rectification_code: R5, rectification_type: PARTIALAllowed; SIMPLIFIED's only valid code is R5
Refund larger than the originalSkippedLikely data error in Stripe; investigate
Refund on an already-voided invoiceSkippedTOTAL correctives are terminal
Currency ≠ EURSkippedBeeL only emits EUR-denominated invoices
Amount = 0 €SkippedNo invoice generated for free transactions

The full skip list is in Edge cases and limits.

Finding generated invoices

Invoices BeeL generates from Stripe carry system metadata you can filter the invoice list by — most usefully external_customer_id (the Stripe cus_…) and external_payment_id (the PaymentIntent / Charge / Stripe Invoice id). To pull a customer's invoice history in one call:

curl -g "https://app.beel.es/api/v1/invoices?metadata[external_customer_id]=cus_ULGk8bzIr88aag" \
  -H "Authorization: Bearer beel_sk_live_xxx"

The -g flag tells curl not to treat [ ] as glob characters; SDKs encode them automatically. external_customer_id is present whenever the payment carried a customer — flows with no customer (Terminal, payment links without customer collection; see When the recipient ends up incomplete) won't have it. The complete list of system keys, query rules and limits lives in Filtering by metadata.