- Trigger
- Stripe webhook, POST on the path /stripe
- Nodes
- 3
- Requires
- Postgres
- Category
- Payments
- From the course
- Stripe and Payment Automations
- Published
- 2026-09-10
Node types used
- Webhook
- Code
- Postgres
What it does
Stripe will POST to any URL you give it, and so will anyone else who finds that URL. The Stripe Trigger node handles verification for you, but the moment you need the raw request body, your own path, or a header your proxy adds, you are back on a plain Webhook node and the signature check becomes your job. Skip it and your billing workflow will act on a request you did not send.
The Webhook node listens for POST requests on the path stripe with Raw Body turned on, so n8n hands the next node the exact bytes Stripe signed as a binary property instead of parsed JSON. A Code node splits the Stripe-Signature header into its t= timestamp and every v1= signature, computes an HMAC-SHA256 of timestamp + "." + rawBody using the secret in $env.STRIPE_WEBHOOK_SECRET, and compares each candidate with crypto.timingSafeEqual. A missing header, a malformed header, a missing secret, no matching signature or a timestamp more than 300 seconds old all throw. Only when every check passes does it parse the raw body and pass the event on. The last node inserts the whole event into a stripe_events table with ON CONFLICT (id) DO NOTHING, so a retried delivery cannot create a second row.
It stops there. There is no routing on event type, no business logic and no custom response. The Postgres node ships disabled so the workflow imports and runs before you have created the table. Treat this as the front door you put in front of every other Stripe workflow.
Node by node
- 1
Webhook Accepts POST requests on the path stripe. Raw Body is on, so the request body arrives as a binary property named data rather than parsed JSON, which is the only form that still matches what Stripe signed.
- 2
Verify Stripe signature Reads the stripe-signature header, splits it into the t= timestamp and every v1= signature (there is more than one while a signing secret is being rolled), computes an HMAC-SHA256 of `${timestamp}.${rawBody}` with $env.STRIPE_WEBHOOK_SECRET, and compares each candidate using crypto.timingSafeEqual. It throws on a missing or malformed header, a missing secret, no match, or a timestamp more than 300 seconds away from now. On success it returns JSON.parse(rawBody) as the item.
- 3
Log event Inserts id, type, api_version, livemode, request id, created (through to_timestamp), the full event as jsonb, and currency, amount and balance_transaction read off data.object, into stripe_events. ON CONFLICT (id) DO NOTHING makes a redelivery a no-op. This node is disabled in the export.
Setup after import
- 1
Create a Postgres credential and select it on the Log event node. The export ships with the credential id set to REPLACE_ME so n8n asks you to pick your own.
- 2
Create the stripe_events table before you enable that node. The insert needs id as the primary key plus type, api_version, livemode, request_id, created, data (jsonb), currency, amount_cents and balance_transaction.
- 3
Set STRIPE_WEBHOOK_SECRET in the n8n environment. The Code node reads it through $env and throws a readable error if it cannot see it.
- 4
Copy the production webhook URL n8n shows for the path stripe, add it as an endpoint in the Stripe dashboard, and paste that endpoint signing secret into STRIPE_WEBHOOK_SECRET. The secret belongs to the endpoint, not to the account.
- 5
Enable the Log event node once the table exists.
Limits
Verification and logging only. No event routing and no business logic.
The Webhook node sets no response mode, so n8n acknowledges the request before the Code node runs. A failed check shows as a failed execution in n8n, not as an error response to Stripe.
The five minute timestamp tolerance is hardcoded in the Code node.
Idempotency goes no further than ON CONFLICT (id) DO NOTHING on the event id.
Download the workflow
The 3 node export as n8n reads it. Credential ids, personal values and real endpoints are replaced with placeholders, so nothing here can reach an account that is not yours. No email, no account.
Download stripe-webhook-signature-verification.jsonGet help with this in the community
If the import fails, if a node errors on the first run, or if you want to point this at a service it does not cover yet, post it in the free House of Loops community. Say which template it is and paste the error. Shannon Atkinson answers, and the answer stays there for the next person who hits the same thing.
Ask in the communityMore templates
Stripe subscription lifecycle router in n8n
Verifies the Stripe signature, then routes customer.subscription events, invoice.paid and invoice.payment_failed to one branch per subscription status.
- Trigger
- Stripe webhook, POST on the path /stripe-subscription-lifecycle
- Nodes
- 15
Daily Stripe reconciliation to Postgres with a Slack alert
Runs at 02:00, pages through yesterday's Stripe balance transactions, finds the ids with no row in stripe_events, logs the result and alerts Slack on a gap.
- Trigger
- Schedule Trigger, daily at 02:00
- Nodes
- 7
Newsletter signup form to Beehiiv with spam guards in n8n
A public signup webhook with a honeypot field, a per-IP rate limit in Postgres, origin and email checks, an upsert and a Beehiiv v2 subscription create.
- Trigger
- Webhook, POST on the path /lead-magnet-signup
- Nodes
- 9