# Customer mappings

How BeeL. matches Stripe customers to BeeL. customers, auto-creates ad-hoc records, and handles missing fiscal data.

Every Stripe payment carries some hint about who paid (customer ID, email, billing details). BeeL. turns that into a *customer mapping* — a stored link between the Stripe-side customer and the BeeL-side customer (or to an ad-hoc record when no BeeL. customer exists yet). The mapping is what lets BeeL. issue a properly-identified invoice on every subsequent payment from the same buyer.

<Callout type="info">
  **Mappings are built and maintained automatically.** They are not user-editable today and there is no public API to create, link, or unlink them. Their content is derived from what Stripe sends on each payment, plus your **auto-create customer** connection setting. The integration points that *are* yours to control live in [Connection settings](/stripe/connection-settings).
</Callout>

<Callout type="info">
  **Recent improvements (May 2026)** — if you previously hit any of these, they're fixed:

  - Mappings now persist on the **first** webhook delivery, including the auto-create path. Previously some flows skipped the mapping write, so the *second* payment from the same buyer was treated as new.
  - The mapping is updated **atomically**. Two concurrent webhook deliveries for the same Stripe customer can no longer race to create duplicate mappings.
  - Country **no longer defaults to `ES`**. If Stripe sends a foreign address (or no country at all), the mapping stores what Stripe actually sent.
  - The Stripe charge total is now mirrored on **both** forwards and corrective invoices. Refunds that previously wouldn't reconcile because the credit note total drifted from the original charge now match exactly.
  - Simplified (F2) invoices issued from Stripe **preserve the customer's name and email** from the Stripe payload instead of defaulting everyone to *Consumidor final*.
</Callout>

## Two kinds of mapping

A mapping is either:

- **Linked** — tied to a managed customer in your BeeL. account. Future invoices use the managed customer's current data (address, legal name).
- **Ad-hoc** — a stand-alone snapshot of the buyer's data extracted from Stripe. Future invoices reuse the snapshot without creating a BeeL. customer.

Both are valid sources for an invoice's `recipient`. Whether a mapping ends up linked or ad-hoc depends on what Stripe sent and on auto-linking (below) — you don't choose at mapping time.

## What happens on every Stripe payment

```text
event arrives
     │
     ▼
1. Read the connection's "auto-create customer" setting
     │
     ├─ off  →  No mapping work. Invoice issued with the
     │          buyer's data inline (nothing stored).
     │
     └─ on   ▼
                 2. Look up a mapping for this Stripe customer
                    (by Stripe customer ID and/or email).
                 │
                 ├─ found
                 │    ├─ linked  → recipient = the linked managed customer
                 │    └─ ad-hoc  → recipient = the stored snapshot
                 │
                 └─ not found
                       ▼
                 3. Build a snapshot from the Stripe payload:
                    – customer.name, email, address
                    – customer.tax_ids[0].value as NIF (if Spanish format)
                 4. Persist a new ad-hoc mapping with that snapshot
                 5. recipient = the new snapshot
```

After step 5, the next payment from the same Stripe customer will hit step 2 and reuse the mapping — no re-extraction.

## How persistence works

A mapping is uniquely identified by the pair *(your Stripe connection, the Stripe customer ID)*. When a webhook arrives and **auto-create customer** is on, BeeL. updates the mapping atomically:

- If no mapping exists for that Stripe customer, it's created — either linked to a managed customer (if one matches the NIF the payload carries) or as ad-hoc with the snapshot extracted from the payload.
- If a mapping already exists, the mapping row itself isn't rewritten: the link target and the ad-hoc snapshot stay as they were captured the first time.

So the **mapping** is stable across webhooks. **But the data behind it is not always stable** — and that distinction matters:

- For a **linked** mapping, every subsequent webhook re-runs the upsert against the managed customer's own record. If Stripe sent updated fiscal data, the **managed customer** is updated to match — *Stripe wins* over what you have in BeeL. If you need to preserve a manual edit on a managed customer, turn off **auto-create customer** for that connection.
- For an **ad-hoc** mapping, the snapshot is captured once at creation and never refreshed by a later webhook. If Stripe later collects a NIF or corrects the address, the existing ad-hoc mapping keeps the old data — see [When the recipient ends up incomplete](#when-the-recipient-ends-up-incomplete).

## When BeeL. auto-links to a managed customer

When **auto-create customer** is on and the Stripe payload carries a valid Spanish NIF, BeeL. looks up your managed customers by that NIF:

- **A match exists** → the mapping is created linked to the existing managed customer (no duplicate created), and the managed customer's fiscal data is **refreshed with what Stripe sent** under the *Stripe wins* policy.
- **No match** and Stripe sent a complete address → BeeL. creates a brand-new managed customer from the Stripe payload and links the mapping to it.
- **No match** and the address is incomplete → no managed customer is created or updated; the mapping is created as **ad-hoc** with the snapshot.

If the payload has no valid NIF (foreign buyer with only `alternative_id`, or no tax data at all), the mapping is always ad-hoc — no auto-link, no managed customer.

## Ad-hoc fields

When a mapping is created as ad-hoc, BeeL. stores a snapshot of the buyer's data from the Stripe payload. The fields and their sources:

| Snapshot field | Source |
|---|---|
| Legal name | `customer.name` or `charge.billing_details.name` |
| NIF | `customer.tax_ids[0].value` (if Spanish format) |
| Email | `customer.email` |
| Phone | `customer.phone` |
| Street, number, postal code, city, province, country, country code | extracted from `customer.address` (or `charge.billing_details.address`) |

This snapshot is the **source for the auto-emitted invoice's recipient block** whenever the mapping is ad-hoc (no managed customer linked). It is populated only when the mapping is first created — once it exists, future webhooks never overwrite it (see [How persistence works](#how-persistence-works)).

## When the recipient ends up incomplete

Two common reasons:

### 1. Stripe didn't send a customer

Some payment flows (Stripe Terminal, payment links without customer collection) don't attach a `customer.*` block. BeeL. falls back to `charge.billing_details` (name, email, address). When there is no Stripe customer ID at all, **no mapping is created** — the payment is just one-off — and the invoice falls to F2 (simplified), populating the recipient with whatever name/email Stripe sent and otherwise defaulting to *Consumidor final*. See [Generated invoices](/stripe/generated-invoices).

### 2. No NIF on the Stripe customer

Stripe lets you collect tax IDs (`customer.tax_ids`) but it's opt-in. Without a NIF, BeeL. cannot issue an identified invoice: up to 3,000 € the payment gets a simplified invoice, which never carries the NIF, and above it BeeL. saves a draft and the charge lands in the **Needs attention** inbox (*Necesita atención* in the dashboard) for you to complete (see [Edge cases and limits](/stripe/edge-cases-and-limits#necesita-atención--inbox-of-skipped-events)). With a NIF and a complete address, the invoice is always a standard one.

**Fix:** enable Stripe Tax's "collect tax IDs" or use Checkout's tax-collection options. As soon as the **next** payment from the same buyer carries `tax_ids`, the lookup at step 2 above still finds the existing mapping — but because mappings are frozen at creation time, that new NIF won't propagate into the old mapping. For payments that originated under the old (NIF-less) mapping, the operational path is to fix the buyer's setup in Stripe so any *future* customer ID gets a fresh, properly-built mapping on first use.

<Callout type="info">
  **Country is no longer assumed to be Spain.** If Stripe sends no country, BeeL. stores whatever Stripe actually sent (including empty) — and the invoice path adapts based on the real data.
</Callout>

## Connection settings that affect mappings

| Setting | Default | Effect |
|---|---|---|
| **Auto-create customer** | On | Persist a mapping on every new buyer. Turning off keeps invoices flowing but stores no mapping. |

You change it in [Connection settings](/stripe/connection-settings).

### Pitfall: turning auto-create off mid-stream

The flag only gates **new** mappings. If you turn **auto-create customer** off after the integration has been running:

- Existing mappings keep resolving normally. Every payment from a Stripe customer that BeeL. has already seen will reuse its mapping and issue the invoice as usual.
- Payments from a *new* Stripe customer ID won't create a mapping. The recipient is built inline from the Stripe payload and the [Stripe event](/stripe/supported-events) lands in the **Needs attention** inbox so you can review it.

Turning the flag back on does not retroactively persist mappings for events processed while it was off.

## Related

<Related>

- [Connection settings](/stripe/connection-settings) — the toggles you control
- [Data conversion](/stripe/data-conversion) — how the resolved customer maps into the invoice
- [Generated invoices](/stripe/generated-invoices) — F1/F2 decision based on fiscal data
- [Edge cases and limits](/stripe/edge-cases-and-limits) — what BeeL. does when fiscal data is missing

</Related>

---

Full OpenAPI spec: https://docs.beel.es/api/openapi