Integration
Formfeed for Stripe
Stripe keeps your invoices: the number, the customer, the lines, the discounts and the VAT. A webhook on invoice.finalized and one function turn each of them into a ZUGFeRD / Factur-X e-invoice, a PDF/A-3 with the invoice as XML inside, validated before it reaches you. Nothing is installed in Stripe, and nothing is calculated again: the e-invoice states what Stripe charged.
No app to install: a webhook handler and one function do it today.
Free plan with 100 units a month, no credit card
One webhook, one function
Stripe sends invoice.finalized once an invoice has its number. The handler lists the invoice’s lines with their tax rates, turns the invoice into the _invoice block and renders it with a template that prints the block, such as the E-invoice example in the app. The recipe, with the whole function and the same in n8n, is in the documentation.
const invoice = await stripe.invoices.retrieve(event.data.object.id);
const lines = [];
for await (const line of stripe.invoices.listLineItems(invoice.id, {
limit: 100,
expand: ['data.taxes.tax_rate_details.tax_rate'],
}))
lines.push(line);
const render = await formfeed.renders.create(
{
template: 'e-rechnung',
data: { _invoice: stripeToInvoice(invoice, lines, config) },
post: { einvoice: {} },
filename: `${invoice.number}.pdf`,
},
{ idempotencyKey: `stripe-${invoice.id}` },
);What the function takes from Stripe
- Every line with Stripe’s quantity, price and net amount. A discount stays visible as an allowance on its line, and a credit for unused time after an upgrade is a negative quantity.
- The VAT Stripe applied. Standard and reduced rates are category S; every other reason (reverse charge, exempt, not subject to tax) becomes the category you state once, because that choice is yours and your tax adviser’s.
- Stripe’s totals. The lines have to come to Stripe’s total, and what is paid already or settled from the customer’s credit balance is stated as prepaid.
- The customer as Stripe froze it when the invoice was finalised: name, address, EU VAT ID and e-mail.
It refuses rather than guesses
Shipping costs, a balance from earlier invoices, a line with two taxes and a tax reason without a category make the function stop with the invoice’s number, instead of making an e-invoice that differs from Stripe’s invoice. The API then checks the block against EN 16931 before anything is rendered or charged. When it refuses one, the cause is data missing in Stripe, most often a customer without an address or a country, and the refusal names every field and the rule it breaks.
Worth knowing before you build it
- Live e-invoices need the Starter plan or higher. While you build, a test key makes valid e-invoices with the test mark, up to 20 a day on the Free plan.
- Stripe delivers an event more than once. An idempotency key (24 hours) and the render id in the invoice’s metadata keep it to one e-invoice per invoice; two deliveries that arrive while the first render is still running need a queue, as the recipe explains.
- Formfeed makes and validates the file. Sending it to your customer and archiving it stay with you: Formfeed keeps the file only for the workspace’s retention period (30 days on Starter, a year on Pro), not for the years an invoice has to be kept.
- If Stripe also e-mails its own PDF of the invoice, your customer receives two documents with one number. Decide which one is the invoice.