Every successful Stripe payment should trigger a professional invoice — but wiring that up manually is tedious and error-prone. Maybe you’re copying payment details into a Google Doc, exporting Stripe receipts one by one, or worse, forgetting to send invoices entirely. Your customers deserve better, and your bookkeeping shouldn’t depend on you remembering to open a spreadsheet at 11 PM.
In this guide, you’ll build an n8n workflow that listens for Stripe payments in real time, generates a polished HTML invoice, emails it to the customer through Gmail, and logs every transaction to Google Sheets — all without writing a single line of code outside the workflow editor. The whole thing runs on autopilot once you flip the switch.
Prefer to skip the setup? Grab the ready-made template → and be up and running in under 10 minutes.
What You’ll Build
- A customer completes a payment on your Stripe-powered site or app.
- Stripe fires a webhook to your n8n instance within seconds.
- n8n validates the event, extracts the payment details, and generates a branded HTML invoice.
- The invoice is emailed to the customer via your Gmail account — professional layout, correct amounts, zero manual effort.
- Every payment is simultaneously logged to a Google Sheet so you have a running financial record.
How It Works — The Big Picture
The workflow is a single linear pipeline with one branch at the end. Stripe pushes the data in, n8n processes it, and two outputs happen in parallel: the customer gets their invoice, and your spreadsheet gets a new row.
┌───────────────────────────────────────────────────────────────────────────┐ │ AUTO-SEND INVOICES ON STRIPE PAYMENT │ │ │ │ [Stripe Webhook] → [Validate Event] → [Extract Details] → [Build Email] │ │ ↓ ↓ │ │ [Send Gmail] [Log to │ │ Sheets] │ └───────────────────────────────────────────────────────────────────────────┘
What You’ll Need
- A Stripe account with at least one product or payment link configured. Free to set up — you only need access to the Developers → Webhooks section.
- A Gmail account for sending invoices. Any Google Workspace or personal Gmail works.
- A Google Sheets spreadsheet to serve as your payment log. Create a blank sheet named “Payments” with the column headers listed in the Data Structure section below.
- An n8n instance — self-hosted or n8n Cloud. The webhook node needs to be publicly reachable so Stripe can POST to it.
Estimated build time: 25–35 minutes from scratch, or under 10 minutes with the template.
Building the Workflow Step by Step
1 Stripe Payment Webhook (Webhook)
This is your entry point. The Webhook node creates a unique URL that Stripe will POST to every time a payment event occurs. Once the workflow is active, n8n listens at this URL around the clock.
- Add a Webhook node to the canvas.
- Set HTTP Method to
POST. - Set Path to
stripe-invoice-webhook(or any slug you prefer). - Leave Response Mode as “When node receives data” — this immediately returns a 200 OK to Stripe so it doesn’t retry.
- Save the workflow and copy the Production URL shown at the top of the node panel.
After completing this step, the webhook data structure will look like this when Stripe sends a payment_intent.succeeded event:
{
"id": "evt_3PqR7sLk2xYaB9c0",
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_1NrQ8fLk2xYaB9c0RvK4mT3z",
"amount": 4999,
"currency": "usd",
"receipt_email": "james.carter@gmail.com",
"billing_details": {
"name": "James Carter",
"email": "james.carter@gmail.com"
},
"description": "Pro Plan — Annual Subscription",
"payment_method_types": ["card"]
}
}
}
Tip: In Stripe Dashboard → Developers → Webhooks, create a new endpoint with the production URL you copied. Select only the payment_intent.succeeded event — this keeps your webhook focused and avoids processing events you don’t need.
2 Validate Payment Event (IF)
Stripe might send test events, retries, or events you didn’t filter at the dashboard level. This IF node acts as a gatekeeper — it only lets payment_intent.succeeded events through.
- Add an IF node and connect it to the webhook output.
- Set the condition:
{{ $json.type }}equalspayment_intent.succeeded. - The “true” branch continues to the next step. The “false” branch ends silently — no action needed for irrelevant events.
Tip: If you also sell subscriptions and want to handle recurring invoice payments, you can add a second condition for invoice.payment_succeeded and adjust the data extraction accordingly.
3 Extract Payment Details (Code)
Stripe’s webhook payloads are deeply nested. This Code node reaches into the payload, pulls out the fields you actually need, generates a unique invoice number, and packages everything into a flat, clean object that downstream nodes can reference easily.
- Add a Code node and connect it to the “true” output of the IF node.
- Paste the extraction script (included in the template). It does the following:
- Extracts
amount(converts from cents to dollars),currency,customer_email,customer_name, anddescription. - Generates an invoice number in the format
INV-YYYYMMDD-HHMMSS. - Throws an error if no customer email is found — you can’t send an invoice without a recipient.
- Extracts
After extraction, the data looks like this:
{
"invoice_number": "INV-20260409-143022",
"payment_id": "pi_1NrQ8fLk2xYaB9c0RvK4mT3z",
"amount": "49.99",
"currency": "USD",
"customer_email": "james.carter@gmail.com",
"customer_name": "James Carter",
"description": "Pro Plan — Annual Subscription",
"payment_method": "card",
"payment_date": "April 9, 2026",
"status": "Paid"
}
Stripe stores amounts in the smallest currency unit (cents for USD). The code divides by 100 so your invoice shows $49.99 instead of 4999.
4 Build Invoice Email (Code)
This is where the magic happens. A second Code node takes the clean payment data and assembles a fully styled HTML invoice that renders beautifully in every major email client — Gmail, Outlook, Apple Mail, and mobile.
- Add another Code node after Extract Payment Details.
- Paste the invoice template script (included in the template). Customize the company details at the top of the script:
company.name— your business namecompany.addressandcompany.city— your business addresscompany.email— your billing emailcompany.color— your brand’s primary hex color (default is#2563EB)
The template produces a clean invoice with a colored header bar, from/to addresses, a line-item table, a total amount callout, and a thank-you footer. It also generates a plain-text fallback for email clients that don’t render HTML.
Tip: The invoice uses inline CSS (not external stylesheets) because that’s what email clients require. If you want to tweak colors or fonts, edit the style attributes directly in the HTML string.
5 Send Invoice to Customer (Gmail)
The Gmail node sends the HTML invoice as a rich email. No PDF attachment needed — the invoice renders directly in the email body, which is how most modern billing systems (Stripe, Paddle, Gumroad) handle it.
- Add a Gmail node. Connect it to the Build Invoice Email output.
- Select your Gmail OAuth2 credential (or create one — see the Credentials Guide).
- Set To to the expression
{{ $json.customer_email }}. - Set Subject to
{{ $json.email_subject }}. - Set Message to
{{ $json.invoice_html }}. - Under Options, turn off “Append n8n attribution” if you want a clean footer.
Gmail’s sending limits: personal Gmail allows ~500 emails/day; Google Workspace allows ~2,000/day. If you process more payments than that, consider using an SMTP node with a transactional email service like SendGrid or Mailgun instead.
6 Log Payment to Google Sheets (Google Sheets)
Every invoice should leave a paper trail. This Google Sheets node appends a row to your payment log spreadsheet, giving you a live financial record that’s easy to search, filter, and export for accounting.
- Add a Google Sheets node. Connect it to the same Build Invoice Email output (it runs in parallel with the Gmail node).
- Select your Google Sheets OAuth2 credential.
- Set Operation to “Append or Update Row”.
- Choose your spreadsheet and select the “Payments” sheet.
- Map each column to its corresponding expression —
Invoice Number→{{ $json.invoice_number }}, and so on for all 10 columns.
The Data Structure
Create a Google Sheets spreadsheet with a sheet named Payments. Add these column headers in row 1:
| Column | Type | Example | Description |
|---|---|---|---|
Invoice Number |
Text | INV-20260409-143022 | Unique invoice identifier generated by the workflow |
Payment ID |
Text | pi_1NrQ8fLk2xYaB9c0RvK4mT3z | Stripe PaymentIntent ID for cross-referencing |
Customer Name |
Text | James Carter | Name from Stripe billing details |
Customer Email |
james.carter@gmail.com | Where the invoice was sent | |
Amount |
Number | 49.99 | Payment amount in currency units (not cents) |
Currency |
Text | USD | Three-letter currency code |
Description |
Text | Pro Plan — Annual Subscription | What the customer paid for |
Payment Method |
Text | card | How the customer paid |
Date |
Date | April 9, 2026 | Human-readable payment date |
Status |
Text | Paid | Always “Paid” since we only process successful payments |
Column names must match exactly — the Google Sheets node maps data by header name. Copy-paste them from the table above to avoid typos.
Here’s what a few rows look like after the workflow runs:
| Invoice Number | Customer Name | Amount | Currency | Date | Status |
|---|---|---|---|---|---|
| INV-20260409-143022 | James Carter | 49.99 | USD | April 9, 2026 | Paid |
| INV-20260409-151247 | Emily Rodriguez | 149.00 | USD | April 9, 2026 | Paid |
| INV-20260410-091530 | Michael Chen | 29.99 | USD | April 10, 2026 | Paid |
Full System Flow
┌─────────────────────────────────────────────────────────────────────────────┐ │ │ │ STRIPE DASHBOARD n8n WORKFLOW │ │ ─────────────── ──────────── │ │ │ │ Customer pays $49.99 │ │ │ │ │ ▼ │ │ payment_intent.succeeded ──POST──→ [Stripe Payment Webhook] │ │ │ │ │ ▼ │ │ [Validate Payment Event] │ │ │ type == "payment_intent.succeeded"? │ │ │ │ │ YES ▼ NO → (end) │ │ [Extract Payment Details] │ │ │ invoice_number, amount, email... │ │ │ │ │ ▼ │ │ [Build Invoice Email] │ │ │ HTML invoice + plain text │ │ │ │ │ ┌────┴────┐ │ │ ▼ ▼ │ │ [Send Gmail] [Log to Sheets] │ │ │ │ │ │ ▼ ▼ │ │ Customer receives Row added to │ │ invoice email Payments sheet │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
Testing Your Workflow
- Activate the workflow in n8n (toggle the Active switch).
- Send a test event from Stripe: Go to Stripe Dashboard → Developers → Webhooks → select your endpoint → click “Send test webhook” → choose
payment_intent.succeeded. - Check your Gmail — you should receive the invoice email within a few seconds. Open it and verify the layout renders correctly.
- Check your Google Sheet — a new row should appear in the Payments sheet with the test data.
- Make a real test payment using Stripe’s test mode (card number
4242 4242 4242 4242) to verify the full end-to-end flow with real data.
| Problem | Likely Cause | Fix |
|---|---|---|
| Webhook not triggering | Workflow is not active, or Stripe has the wrong URL | Make sure the workflow is toggled ON. Copy the production URL (not the test URL) and verify it matches what’s in Stripe Webhooks. |
| Email not received | Gmail credential expired or customer_email is empty | Re-authorize your Gmail OAuth2 credential. Check the Stripe PaymentIntent — make sure receipt_email or billing_details.email is set. |
| Google Sheet not updating | Column names don’t match or sheet name is wrong | Verify the sheet is named exactly “Payments” and that column headers match the mapping in the node (case-sensitive). |
| Invoice shows $0.00 | Test event doesn’t include real amount data | Stripe’s “Send test webhook” uses placeholder data. Make a test payment in Stripe test mode instead. |
| Error: “No customer email found” | PaymentIntent was created without an email | When creating PaymentIntents via API, always set receipt_email. For Checkout Sessions, email is collected automatically. |
Frequently Asked Questions
Does this work with Stripe Checkout Sessions or only direct PaymentIntents?
It works with both. Stripe Checkout Sessions create a PaymentIntent under the hood, so a payment_intent.succeeded event fires either way. The customer email is automatically captured during checkout.
Can I add my company logo to the invoice?
Yes — in the “Build Invoice Email” code node, replace the company name text in the header with an <img> tag pointing to a publicly hosted version of your logo. Most email clients render images up to 600px wide reliably.
What if I need to send a PDF attachment instead of an HTML email?
You can add an HTTP Request node between the Build Invoice Email and Gmail nodes that calls a PDF conversion API (like html2pdf.app or DocRaptor). The API returns a binary PDF that you attach to the email. The template’s HTML is already designed to render well in PDF format.
Will this handle international currencies and non-USD payments?
Yes. The workflow reads the currency field directly from Stripe and displays it on the invoice. It works with EUR, GBP, JPY, and every other currency Stripe supports. The cent-to-unit conversion (dividing by 100) works for all standard currencies — for zero-decimal currencies like JPY, you’d want to skip the division in the Code node.
What happens if my n8n instance is down when a payment comes in?
Stripe automatically retries failed webhook deliveries for up to 3 days with exponential backoff. When your n8n instance comes back online, it will receive the event and process it normally. No payments are lost.
Can I customize the invoice design to match my brand?
The Build Invoice Email node uses a single company.color variable that controls the header, accent, and total amount colors. Change it to your brand hex code and the entire invoice updates. You can also edit fonts, spacing, and layout by modifying the inline CSS in the HTML template.
Get the Stripe Auto-Invoice Template
Skip the setup — get the complete workflow JSON, a pre-built Google Sheets template, step-by-step setup guide, and credentials walkthrough. Import it into n8n and start sending invoices in under 10 minutes.
Instant download · Works on n8n Cloud and self-hosted
What’s Next?
- Add refund handling: Listen for
charge.refundedevents and send a credit note email using the same invoice template with a negative amount. - Multi-currency formatting: Add a Code node that formats amounts with the correct currency symbol ($, €, £) and decimal conventions based on the
currencyfield. - Monthly revenue dashboard: Use a scheduled trigger to pull data from your Payments sheet weekly and send a summary report to your Slack or email.
- Overdue payment reminders: For invoices tied to subscriptions, build a companion workflow that checks for failed payments and sends polite reminder emails.