Stripe Integration
When a Stripe payment comes in, your server calls thelawin.dev and generates a ZUGFeRD invoice. The whole thing runs off a single webhook.
Prerequisites
- Stripe account with webhook access
- thelawin.dev API key (for testing:
env_sandbox_demo) - Server for receiving webhooks (Node.js, Python, Ruby, etc.)
Architecture
Stripe Payment → Webhook (payment_intent.succeeded)
→ Your server (extracts customer data + amount)
→ POST api.thelawin.dev/v1/generate
→ Email PDF to customerWebhook Handler (Node.js)
javascript
import Stripe from 'stripe';
import { ThelawinClient } from '@thelawin/sdk';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const thelawin = new ThelawinClient(process.env.THELAWIN_API_KEY);
app.post('/webhooks/stripe', async (req, res) => {
const event = stripe.webhooks.constructEvent(
req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET
);
if (event.type === 'payment_intent.succeeded') {
const payment = event.data.object;
const customer = await stripe.customers.retrieve(payment.customer);
const result = await thelawin.invoice()
.number(`RE-${Date.now()}`)
.date(new Date().toISOString().split('T')[0])
.format('zugferd')
.seller({
name: 'Your Company GmbH',
vatId: 'DE123456789',
city: 'Berlin',
country: 'DE'
})
.buyer({
name: customer.name,
email: customer.email,
city: customer.address?.city,
country: customer.address?.country || 'DE'
})
.addItem({
description: payment.description || 'Service',
quantity: 1,
unitPrice: payment.amount / 100,
})
.generate();
if (result.success) {
console.log(`Invoice generated: ${result.filename}`);
}
}
res.json({ received: true });
});Webhook Handler (Python)
python
import stripe
from thelawin import ThelawinClient
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
client = ThelawinClient(os.environ["THELAWIN_API_KEY"])
@app.route("/webhooks/stripe", methods=["POST"])
def stripe_webhook():
event = stripe.Webhook.construct_event(
request.data, request.headers["Stripe-Signature"],
os.environ["STRIPE_WEBHOOK_SECRET"]
)
if event["type"] == "payment_intent.succeeded":
payment = event["data"]["object"]
customer = stripe.Customer.retrieve(payment["customer"])
result = (
client.invoice()
.number(f"RE-{int(time.time())}")
.date(datetime.now().strftime("%Y-%m-%d"))
.format("zugferd")
.seller("Your Company GmbH", vat_id="DE123456789", city="Berlin", country="DE")
.buyer(customer["name"], email=customer["email"], country="DE")
.add_item(payment.get("description", "Service"), quantity=1, unit_price=payment["amount"] / 100)
.generate()
)
if result.success:
result.save_pdf(f"invoices/{result.filename}")
return jsonify(received=True)Stripe Checkout → Invoice
For Stripe Checkout sessions, listen for checkout.session.completed:
javascript
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
const lineItems = await stripe.checkout.sessions.listLineItems(session.id);
// Build invoice items from lineItems.data
}Next Steps
- API Reference | Full field documentation
- n8n Guide | Handle Stripe webhooks without your own server
- Invoice Formats | ZUGFeRD, XRechnung, Factur-X