n8n Stripe Webhook: Automate Payment Handling Without Losing Your Mind
The worst time to manually process a payment is right after it happens, when you're excited and doing three other things. I know because I dropped the ball on two early sales before I stopped pretending I'd "handle it later." Here's how to wire up the n8n Stripe webhook so the workflow handles it immediately and you don't have to remember anything.
What's covered here
Why webhooks beat polling for Stripe events every single time
When someone pays through Stripe, you have a window of a few minutes to make a good impression. Send a fast, clear confirmation. Get them into your onboarding system. Make it feel professional. That window closes fast, and if your process depends on you manually checking the Stripe dashboard and firing off an email when you notice a new charge, you're burning that window regularly.
The alternative most people try first is polling: set up something that checks Stripe every few minutes and does work when it finds new payments. It works, kind of. But it's delayed, it adds unnecessary API load, and it creates weird timing issues at the edges. Stripe charges you nothing extra for webhooks, and webhooks are push-based: Stripe calls your endpoint the moment the event fires. No lag, no polling overhead, no hoping your check interval is short enough.
A webhook lets Stripe do the notifying. You just have to be ready to receive and act on the signal. That's the whole contract.
n8n makes this particularly clean because the Stripe Trigger node handles the webhook registration and verification for you. You don't have to mess with raw endpoint URL registration, signature checking, or any of the plumbing that makes custom webhook setups annoying. You get a URL, paste it in Stripe, and you're done with the infrastructure side.
Choosing the right Stripe event (because there are too many)
This is where people make their first real mistake. They pick the first event that sounds right and wonder later why their automation fires twice, or not at all, or at the wrong moment.
Stripe emits a lot of events. The two you will care about most for payment automation are:
checkout.session.completed: fires when a customer finishes a Stripe Checkout session. This is the event you want if you're using Stripe's hosted checkout page. It includes customer info, payment status, and line items.payment_intent.succeeded: fires when a payment intent succeeds. More low-level, better for custom checkout flows where you're managing the payment intent directly rather than using hosted checkout.
For most people selling digital products, courses, templates, or anything with Stripe's hosted checkout: use checkout.session.completed. It's cleaner data, more predictable timing, and it won't fire for failed attempts that later succeed after a retry. You want the completed session, not the underlying payment machinery.
If you are running a SaaS with subscription billing or a custom payment UI, payment_intent.succeeded gives you more flexibility. But it also requires you to handle edge cases that checkout.session.completed already abstracts away. Pick the simpler one unless you have a concrete reason not to.
Quick sanity check: if you listen on payment_intent.succeeded and you also use Stripe Checkout, you will probably see that event fire for every checkout session too. That means you could accidentally handle the same payment twice if you're not deduplicating on something like the payment intent ID. Simpler to just use the right event from the start.
Setting up the n8n Stripe Trigger node
Open a new workflow in n8n, add a trigger node, and search for Stripe Trigger. It should show up under the Triggers category.
Use a restricted key in production. Give it read access on Events and read/write on whatever resources your workflow will touch. It takes two minutes to set up and means a leaked key can't do nearly as much damage as a full secret key would.
checkout.session.completed (or payment_intent.succeeded if your setup calls for it).Once you save the node, n8n generates a webhook URL. Copy it. You'll need it in the next step.
Under "Events to listen to," select the same event you chose in n8n (
checkout.session.completed or payment_intent.succeeded). Save it.Stripe will immediately send a test ping to verify the endpoint is reachable. If n8n is cloud-hosted, this will work instantly. If you're running n8n locally, you'll need a tunnel like ngrok or Cloudflare Tunnel to expose your local instance to the public internet for this to work.
The test payload will arrive in n8n and you can inspect the full data structure. This is the step most people skip, and it's the step that saves the most time later. Know what fields you're working with before you start mapping them in downstream nodes.
The fields you'll use most from a
checkout.session.completed payload are:data.object.customer_details.emaildata.object.customer_details.namedata.object.amount_total(in cents, divide by 100)data.object.id(the session ID, useful as a unique key)data.object.payment_status(should be "paid")
Routing payment data to a CRM (Airtable example)
Once you have the trigger wired up and you've inspected a real test payload, the next node is usually a CRM write. Airtable is a good starting point because it's flexible, has a native n8n node, and doesn't require a developer to set up a database schema.
The shape of this part of the workflow is straightforward:
Stripe Trigger → Set (clean fields) → Airtable (create record)
Drop a Set node between the trigger and Airtable to normalize the data before it hits the CRM. This keeps the Airtable node clean and means you're not doing string manipulation inside the Airtable node itself, which is a mess to debug later.
In the Set node, define the fields you want to carry forward:
- Map
customer_emailfrom{{ $json.data.object.customer_details.email }} - Map
customer_namefrom{{ $json.data.object.customer_details.name }} - Map
amount_paidfrom{{ $json.data.object.amount_total / 100 }} - Map
session_idfrom{{ $json.data.object.id }} - Set
statusas a fixed value:New Customer
Then in the Airtable node, set the operation to Create Record and map each field to the corresponding Airtable column. Make sure your Airtable base has matching column names, or the node will write to the wrong fields silently. Silent failures are the worst kind.
About the amount_total field: Stripe stores amounts in the smallest currency unit (cents for USD). A $97 payment shows up as 9700. Divide by 100 in the Set node before writing to Airtable, or you'll have a CRM full of payments that look like they're each worth $9,700.
If you're not using Airtable, the same pattern works with HubSpot, Notion, Google Sheets, or any CRM that has an n8n node. The Set node in the middle is the part that matters, because it decouples your data shape from any specific destination. If you ever switch CRMs, you change one node, not five.
Already have this workflow in mind but don't want to build every node from scratch?
The n8n Automation Starter Pack includes a prebuilt "Stripe Payment to CRM + Welcome Email" workflow, plus 13 other automations for people who run real businesses. Skip the debugging session and start with something that already works.
See Google Workspace MCP →Sending the welcome email automatically
After the CRM record is created, the welcome email is the next logical step. This is the part that most people genuinely want but never get around to setting up manually. The window between payment and first communication matters a lot for perceived quality. An email that arrives in 30 seconds feels like a real product. One that arrives the next morning, or not at all, feels like a side project.
Add a Gmail node (or your email provider's node) after the Airtable step. Set the operation to Send Email and map the fields:
- Set To to the
customer_emailfield from your Set node - Set Subject to something like: "Your order is confirmed. Here's what's next."
- Write the body once, using the
customer_namefield to personalize the greeting
The email body is where most automation tutorials wave their hands and say "add your copy here." So here's what actually works: keep it short, confirm the purchase explicitly, tell them what to expect next, and give them a single action to take. One link. Not five. If it's a digital download, that's the link. If it's a course, that's the link. If it's access to something, that's the link.
The biggest failure mode for automated welcome emails is that they sound like they were written by a script. Which, technically, they were. The fix is not to add more warmth tokens. It's to write the email the way you'd write it if you were manually sending it to one good customer. Short, clear, human. Then automate that.
Send EmailTo:
{{ $json.customer_email }}From Name: your business name
Subject:
You're in. Here's your access. (or whatever fits your product)Body (text): reference
{{ $json.customer_name }} in the greeting, confirm the purchase, give the single next step.If you want HTML formatting, switch the body type to HTML and write a simple template. Keep it clean. Fancy email templates with eleven columns and six colors are a great way to land in the Promotions tab and never be read.
One addition worth including: a Telegram or Slack notification for yourself after the email fires. Not because you need to approve anything, but because it's genuinely useful to know when a real payment just processed and a real person just got their welcome email. The automation handles it, but you stay aware. That's the right relationship with automation: it does the work, you stay informed.
Common pitfalls in n8n Stripe webhook setups
Most of the pain in this workflow comes from the same small set of mistakes. These are all things I have personally wasted time on, so the list is not theoretical.
Listening for the wrong event
If you use payment_intent.succeeded when you should be using checkout.session.completed, you'll either get events firing at unexpected times or miss the customer data you need. Check the event type first. Fix this early, not after you've built three downstream nodes around the wrong payload shape.
The webhook URL not being publicly reachable
If you're developing locally and your n8n instance isn't exposed to the internet, Stripe can't reach it. Stripe will retry failed deliveries several times before giving up, which can make the problem look intermittent. Use n8n Cloud for the development phase, or set up a tunnel. Don't assume "it worked once" means "it works reliably."
Forgetting to check payment_status
A checkout.session.completed event fires even when the payment is still processing. For most instant payment methods it won't matter, but for bank transfers or other asynchronous methods, the session can complete before the money actually clears. Add a check: only proceed through the workflow if data.object.payment_status equals paid. Use an IF node right after the trigger to filter for that.
Duplicate processing
Stripe's webhook delivery is "at least once," which means if your endpoint is slow or returns an error, Stripe may send the same event again. If you're creating CRM records and sending welcome emails, duplicate processing means a customer gets two emails and two records. Guard against this by storing the session_id and checking for it before creating anything. A simple Airtable lookup or a Redis key check is enough.
Amount field confusion
Already mentioned this above, but it keeps coming up: amount_total is in cents. Always. Divide by 100 before you write it anywhere human-readable. A $97 product showing up as $9,700 in your CRM is embarrassing and also a little bit funny in retrospect.
Credentials expiring mid-operation
If the workflow suddenly stops creating Airtable records or sending emails but the trigger is still firing, check the credentials for those nodes first. OAuth tokens expire. API keys get rotated. The fix is usually a one-minute re-auth, but you have to know to look there instead of assuming the workflow logic broke.
The pattern worth remembering: Stripe webhook issues almost always come down to one of four things: wrong event type, unreachable endpoint, missing payment status filter, or duplicate processing. If something is broken, check those four before looking anywhere else.
Want this workflow already built?
If you built this yourself, you now have a working n8n Stripe payment automation: webhook fires on checkout completion, data routes into a CRM, welcome email goes out automatically, and you get a notification so you know it happened. That's a real, complete payment handling pipeline.
But if you'd rather skip the afternoon of field mapping and debugging and just start with something that already works, the prebuilt version is in the n8n Automation Starter Pack. The "Stripe Payment to CRM + Welcome Email" workflow is one of 14 workflows in the pack, all built for actual use cases rather than demo screenshots.
The pack is $97 and includes a 30-day guarantee. No pitch gymnastics: either you want the shortcut or you don't. If you're building this workflow for a real business and your time has value, the math is simple.
What you don't get from a prebuilt workflow is the understanding of why it works. So if you've read this far, you actually have both: the knowledge from building it yourself, and the option to not burn a weekend getting there.
The moment you automate this, the first thing you notice is that you stop thinking about it. That's not a small thing. Every payment no longer requires a decision about whether you remembered the follow-up steps. The workflow either ran or it didn't, and you can check the execution log instead of your memory. Build it once. Let it run.
Want this workflow already built?
The n8n Automation Starter Pack includes the complete "Stripe Payment to CRM + Welcome Email" workflow, pre-wired and ready to import. Plus 13 other automations for people running real businesses. $97, instant download, 30-day guarantee.
See Google Workspace MCP →