FrancodesystemsFran<code>systems
PaymentsCustom integration

Holded Stripe integration

Stripe payments booked in Holded with no Excel and no manual reconciliation.

HoldedHolded
Stripe

Implementation time · 4-7 días laborables

Diagnosis

The problem

Stripe is excellent at collecting payments: payment links, recurring billing, card management, everything you need. But it leaves Spanish accounting invoicing completely orphaned. To reconcile 200 monthly payments with your Holded, someone ends up downloading Stripe's monthly CSV, opening it in Excel, looking up each customer and creating invoices by hand. Once you pass 50 payments a month, this is 5 to 10 hours of admin every month just moving data around. Stripe Invoicing exists, but its numbering does not fit the AEAT, it lacks the Spanish tax models (303, 390, 349) and it handles intra-community VAT poorly. Holded does handle all of this, but by default it does not listen to Stripe.

Proposal

The solution

Every Stripe payment (one-off, subscription, payment link, payment element) generates the matching invoice in Holded with the correct customer, VAT and series. Refunds trigger credit notes. Stripe fees are booked as an expense in their matching account. Payments in other currencies are recorded with their EUR equivalent at Stripe's own exchange rate. Idempotency guaranteed with Stripe's event ID: infinite retries never duplicate anything.

Scope

What we automate

  • PaymentIntent succeeded to Holded invoice with correct customer, VAT and series
  • Stripe Subscription renewal to recurring invoice with the recurring series
  • Stripe Checkout or Payment Link to invoice after succeeded
  • Stripe refund (full or partial) to Holded credit note
  • Stripe fee to an expense entry in the accounting account you define
  • Multi-currency with EUR equivalent at Stripe's own rate
  • Failed payments to a Holded notification for dunning management
  • Customer Portal: plan changes reflected in the next recurring invoice
  • Stripe Tax IDs to tax ID or VIES in Holded with intra-community validation
Who uses it

Real use cases

These are the profiles that most ask us for the HoldedStripe integration and what they get in the end.

Case 01

B2B SaaS with Stripe subscriptions (200 active customers)

Before: Every monthly renewal required admin to create the invoice by hand. 8 hours a week.

After: Automatic renewals with the correct recurring series. Admin is left only with failure dunning.

Case 02

DTC ecommerce collecting with Stripe plus Klarna

Before: 200 payments a month, partial refunds for returns, discount codes.

After: Invoice issued in seconds after succeeded. Partial refunds become exact credit notes. Zero leakage.

Case 03

Consultancy with international payments in USD

Before: USD payments were invoiced at the bank's daily rate, creating mismatches with the actual collection.

After: Exchange rate locked to Stripe's own rate. Bank and Holded match to the cent.

Before / After

What changes exactly

Without the integration

  • Stripe's monthly CSV opened in Excel and cleaned by hand
  • 5 to 10 hours a month generating invoices for Stripe payments
  • Partial refunds badly reflected or ignored
  • Stripe fees never booked as an expense
  • Multi-currency with EUR/USD gaps

With the integration

  • Stripe webhook to Holded invoice in seconds
  • Zero admin for successful payments, only failure dunning
  • Automatic partial credit notes with the exact amount
  • Stripe fees as an accounting expense entry
  • Multi-currency with Stripe's own EUR equivalent
Architecture

How we build it

A signed Stripe webhook endpoint (HMAC verification with the secret) that filters only the relevant events: payment_intent.succeeded, charge.refunded, invoice.payment_succeeded, customer.subscription.updated. A persistent BullMQ queue, an idempotent worker with dedupe by Stripe event.id, and calls to the Holded REST API with backoff. All the mapping logic (which Stripe Product to which Holded item, which series by currency or payment type) lives in versioned config. If Holded is down, we retry for 24 hours; if it still fails after that, we alert by email or Slack.

flow.ts
webhook stripe.event
queue.enqueue(jobId)
worker.handle() // idempotent
holded.api.call() // retries with backoff
log.emit({ status: 'ok' })
FAQ

Frequently asked questions

What about payments in USD or other currencies?

We record the EUR equivalent using Stripe's own exchange rate (the one that applies to the payout). That removes any gap between what Stripe pays into your bank and what shows in Holded.

Does it work with Stripe Subscriptions and Stripe Billing?

Yes. Each renewal triggers a Holded recurring invoice with the series you define. Plan changes (upgrade or downgrade) generate invoices with proration identical to Stripe's own calculation.

How are chargebacks handled?

When Stripe notifies a dispute, we record it as a pending credit note. If the merchant wins, the credit note is voided; if the customer wins, it is closed as final. You keep full tax traceability at all times.

Is Stripe Connect (marketplace) supported?

Yes. For marketplaces using Connect, we generate the buyer's invoice and separate the platform fee as your own income. Transfers to partners are booked as accounting expenses.

Intra-community tax ID in Stripe?

Yes. Stripe lets you store Tax IDs on the Customer. We read them and validate against VIES at the time of payment. If it is a valid intra-community B2B, VAT is 0%; if not, the local rate of the country applies.

And failed payments or dunning?

The failed_payment events are reflected in Holded as pending invoices. We can also integrate with your CRM (HubSpot, Pipedrive, and so on) so that sales sees the churn risk.

Shall we talk about your Stripe integration?

A 30-minute call, no strings. You leave with scope and a price.