Invoices for your Lovable app
Lovable builds your app and takes payments through Stripe Checkout, but it does not create a legally valid e-invoice. Rechnungskit fills that gap: in an edge function of your app, for example the one that starts the Stripe checkout, you send the order to Rechnungskit with one API call. Rechnungskit gets the payment from your connected Stripe account. As soon as it matches the order, a GoBD-compliant ZUGFeRD invoice under EN 16931 is created automatically, with your own number sequence, a DATEV export and archiving.
One API call from an edge function in your app, and every sale in your Lovable app becomes a compliant e-invoice with a DATEV export. You don't have to build any accounting logic yourself.
Writing invoices is easy. Writing compliant ones is not.
The difference between a PDF and a compliant e-invoice matters at the next tax audit. Rechnungskit closes exactly this gap.
Four steps to a compliant e-invoice.
- 01Create an API keyIn Rechnungskit, go to Connections → API and create a key with the orders:write permission (plus invoices:read if you want to fetch the finished invoice). For testing, use an rk_test_ key; the code stays the same later. Store the key in Lovable under Cloud, Secrets (with your own Supabase project, in its edge function secrets), never in the frontend.
- 02Connect Stripe onceYour Lovable app already uses your own Stripe account. Connect that same account once in Rechnungskit under Connections → Connect and set your API orders as the sales basis. That tells Rechnungskit that the Stripe payment belongs to the order you reported via the API. This step needs no code; you do it once in the dashboard.
- 03Report the order from the Edge FunctionLovable builds Stripe Checkout with edge functions, without a webhook by default. Add the call to POST /v1/orders with the buyer address and line items in whole cents either in the function that starts the checkout (then your order number goes into the payment metadata) or after payment, with the Stripe payment reference (payment_intent). The key stays on the server, and the call is a single fetch.
- 04The invoice is created automaticallyOnce the Stripe payment reaches Rechnungskit and matches the reported order, the e-invoice is created: validated against EN 16931, with the correct tax rate, archived to GoBD standards and ready for the DATEV export. For physical goods you also report the shipment via POST /v1/fulfillments so the date of supply (Leistungsdatum) is right; digital products skip this step. If you need it, GET /v1/orders/{orderNo} returns the finished invoice, including PDF and XML, to your app.
Lovable builds the app, not the invoice
With Lovable you can build a shop or a SaaS in hours and take payments through Stripe Checkout (Lovable docs on the Stripe integration). What's missing only shows up when the tax office or your tax advisor (Steuerkanzlei) asks: Lovable creates a payment, but not an invoice in the sense of German law. There's no structured document under EN 16931, no GoBD archive (GoBD being the German rules for keeping digital records), no VAT logic per country and no DATEV export. Rebuilding all of that in your app is exactly the kind of work you picked an AI app builder to avoid.
Rechnungskit closes that gap where your app already handles the purchase: in an edge function. One API call reports the order, the payment comes from your connected Stripe account, and the sale turns into a ZUGFeRD invoice with your own number sequence, archived in an audit-proof way and ready for booking in DATEV.
Why this fits Lovable well
A Lovable app already has what the connection needs. By default it runs on the built-in backend Lovable Cloud with edge functions and secrets, or on your own Supabase project if you prefer (Lovable docs). Edge functions are the clean way to call an external API: on the server with the key in the secrets, not from the browser. Lovable's Stripe integration uses them as well (Lovable docs).
Lovable doesn't set up a webhook by default. Your app checks the payment status directly with Stripe. That is fine for Rechnungskit, because Rechnungskit gets the payment itself through your connected Stripe account. Your app only has to report the order, and there are two places to do it:
- When the checkout starts. In the edge function that creates the Checkout Session,
report the order with your order number and put the same number into
payment_intent_data.metadataasorder_no. If the buyer abandons the checkout, no payment arrives and no invoice is created. - After payment. In the function that checks the payment status, or in a webhook if
you have Lovable set one up, report the order with the Stripe payment reference as
paymentRef. If that function only runs when the buyer returns to your success page, an order can go missing when they close the window first. Rechnungskit then shows the payment as an unmatched payment task, and you assign it by hand.
The call you add
The example shows the after-payment route: in the edge function that handles the paid
Stripe checkout, add a fetch to POST /v1/orders. Amounts go in whole cents, the Stripe payment reference goes in
paymentRef, and the API key comes from the secrets:
// Edge function, after a paid Stripe checkout (session retrieved via the Stripe API)
const res = await fetch('https://api.rechnungskit.de/v1/orders', {
method: 'POST',
headers: {
'Authorization': `Bearer ${Deno.env.get('RECHNUNGSKIT_API_KEY')}`,
'Content-Type': 'application/json',
'Idempotency-Key': session.id // Stripe session ID: prevents duplicate documents
},
body: JSON.stringify({
orderNo: session.id,
paymentRef: session.payment_intent, // pi_... from Stripe
email: session.customer_details?.email,
billingAddress: {
name: session.customer_details?.name,
country_code: session.customer_details?.address?.country, // required for VAT
postal_code: session.customer_details?.address?.postal_code,
city: session.customer_details?.address?.city,
street: session.customer_details?.address?.line1
},
items: [
{ name: 'Pro plan', quantity: 1, unitGrossCents: 2900, taxRate: 19 }
]
})
})
That's all the code. The invoice itself isn't created by this call. It's created as soon as
the Stripe payment reaches Rechnungskit and matches the order. There is deliberately no
paid field: the payment status always comes from your payment provider, never from the
sender. So an abandoned order can never become a real invoice.
Where does the API key come from?
You create the key in Rechnungskit under Connections → API (directly at
app.rechnungskit.de/settings/api). Click to create a key, grant the orders:write
permission (and invoices:read if you want to fetch finished invoices), and the full key
is shown to you exactly once. For trying things out, use an rk_test_ key; for live
operation, rk_live_. Store the value in Lovable under Cloud, Secrets (for example as
RECHNUNGSKIT_API_KEY), never in the frontend. In the Edge Function you read it with
Deno.env.get('RECHNUNGSKIT_API_KEY'), as in the example above.
Selling physical goods? One more step
For purely digital products and SaaS, the call above is all you need: the invoice is created
once the payment matches. If you ship real goods, a second call comes in, because the date
of supply (Leistungsdatum) on the invoice is the shipping date, not the payment date: a
supply by dispatch is carried out when the dispatch begins (section 13.1(2) UStAE, the German VAT application decree). As soon as you ship the order, report it with
POST /v1/fulfillments:
// When the goods go out (e.g. in your app's "mark as shipped" flow)
await fetch('https://api.rechnungskit.de/v1/fulfillments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${Deno.env.get('RECHNUNGSKIT_API_KEY')}`,
'Content-Type': 'application/json',
'Idempotency-Key': `fulfill-${orderNo}`
},
body: JSON.stringify({
orderNo, // the same order number as in /v1/orders
status: 'fulfilled',
shippedAt: new Date().toISOString(),
trackingNumber: 'DHL-1234567' // optional
})
})
For this, set requiresShipping: true when you create the order. Rechnungskit then waits
for the fulfillment report before it creates the invoice with the correct date of supply.
If you leave requiresShipping out (the default), there's no waiting stage and you never
need /v1/fulfillments.
One-time setup, stated plainly
The code is simple, but you set up a few things once, or you'll wonder why no invoice
appears. No fetch does these for you:
- Connect Stripe in the dashboard and set it to API. You connect your Stripe account once in Rechnungskit under Connections → Connect and choose your API orders as the sales basis. Only then does Rechnungskit bring payment and order together. It's a click, not code.
- Send the country code. Without
country_codein the billing address the VAT can't be determined, and the order is rejected. An email-only checkout isn't enough. - Set an Idempotency-Key. Every POST needs the header; otherwise a duplicate call (such as a reloaded success page) creates two orders. The Stripe session ID is a good value.
- Retry on 5xx. Deployments cause brief outages. Retry the call after a short pause so no order gets lost while the payment has already arrived.
- Check your products' tax rates. Rechnungskit assigns new products automatically, and you confirm the assignment once. If it's missing, the document is held back rather than issued incorrectly.
Show your AI the docs
Rechnungskit publishes a complete OpenAPI 3.1 specification at
api.rechnungskit.de/v1/openapi.json. You can hand it to the AI in Lovable or ChatGPT and
ask it to generate an edge function that reports an order to Rechnungskit for the Stripe
checkout. Because the specification includes the required fields and even the retry
rule, the result is usually a correct function. The one step the AI can't know from the
code, connecting Stripe in the dashboard, is described above.
The full reference with working examples is in the API documentation.
Building with Bolt, Replit or another builder instead? It works the same way, see
invoices for no-code and AI app builders.
FAQ
Rechnungskit is not a tax advisory or law firm. This article explains general principles and does not replace advice from a tax advisor (Steuerberater, § 5 StBerG) or a lawyer (§ 3 RDG). Rechnungskit is built for businesses based in Germany and prepares documents, tax rates and bookings automatically. How your specific case is treated remains your decision, ideally together with your tax advisor or a lawyer.
Ready for the e-invoicing mandate?
Create an account, connect your payment provider, check everything in free test mode. Billing starts when you go live.
Try it now