Save any webhook data to a database automatically with n8n — free workflow JSON Free, four-node n8n workflow that automatically saves incoming webhook data from services like Stripe, GitHub, and Typeform into a PostgreSQL database. The workflow uses a persistent HTTP endpoint to receive POST requests, a JavaScript code node to normalize the data into a consistent format, and a database node to insert the records before returning a 200 response. The author provides the complete workflow JSON and setup instructions, claiming the process takes about 10 minutes to implement. Every webhook is a data goldmine you're probably wasting. When Stripe charges a customer, when GitHub pushes a commit, when Typeform receives a response — your app fires a webhook. That data arrives once, gets processed, and disappears. Most developers handle webhooks reactively: catch the event, do the thing, move on. But every webhook is also a record — a timestamped, structured snapshot of what happened in your business. Here's a 4-node n8n workflow that saves every incoming webhook to a Postgres database automatically. Set it up once, and you'll have a queryable log of every webhook event your systems generate. A persistent HTTP endpoint that accepts POST requests. Any service that supports webhooks — Stripe, GitHub, Shopify, Typeform, Twilio, HubSpot, or your own app — can send events here. The URL stays the same forever; no polling needed. A JavaScript Code node that pulls the fields you care about and standardizes them into a consistent shape, regardless of which service sent the webhook: const payload = $input.first .json;\nconst body = payload.body || payload;\nreturn {\n json: {\n source: payload.headers?. 'x -github-event' ? 'github'\n : payload.headers?. 'stripe-signature' ? 'stripe'\n : payload.headers?. 'x webhook-source' || 'unknown',\n event type: body.type || body.event || body.action || 'raw',\n payload: JSON.stringify body ,\n received at: new Date .toISOString \n }\n} ;\n \n\nFor Stripe you'd use body.type e.g. payment intent.succeeded . For GitHub, body.action and body.repository.name . For Typeform, body.form response.answers . The Code node lets you handle all of them in one place. Node 3 — Postgres: INSERT Writes the normalized record to your database. Supports Postgres, MySQL, SQLite — or swap for Google Sheets, Airtable, or Notion if you don't have a database yet. Node 4 — Respond to Webhook Returns HTTP 200 immediately after the database write. Best practice: always respond within 5 seconds or the sender will retry and create duplicate records. --- Setup 10 minutes 1. Import the JSON into n8n New Workflow → Import from clipboard 2. Create the table in your database: sql CREATE TABLE webhook log id SERIAL PRIMARY KEY, source TEXT, event type TEXT, payload JSONB, received at TIMESTAMP DEFAULT NOW ; https://your-n8n.com/webhook/webhook-to-db Test it: send a test event, then run SELECT FROM webhook log to confirm the row appeared. { "name": "Webhook to Database", "nodes": {"parameters":{"httpMethod":"POST","path":"webhook-to-db","responseMode":"responseNode","options":{}},"id":"wb1","name":"Receive Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position": 240,300 }, {"parameters":{"jsCode":"const payload = $input.first .json;\nconst body = payload.body || payload;\nreturn {\n json: {\n source: payload.headers?. 'x-github-event' ? 'github'\n : payload.headers?. 'stripe-signature' ? 'stripe'\n : payload.headers?. 'x-webhook-source' || 'unknown',\n event type: body.type || body.event || body.action || 'raw',\n payload: JSON.stringify body ,\n received at: new Date .toISOString \n }\n} ;"},"id":"wb2","name":"Extract Fields","type":"n8n-nodes-base.code","typeVersion":2,"position": 460,300 }, {"parameters":{"operation":"executeQuery","query":"INSERT INTO webhook log source, event type, payload, received at VALUES '{{ $json.source }}', '{{ $json.event type }}', '{{ $json.payload }}'::jsonb, '{{ $json.received at }}'::timestamp RETURNING id"},"id":"wb3","name":"Save to Database","type":"n8n-nodes-base.postgres","typeVersion":2.3,"position": 680,300 }, {"parameters":{"respondWith":"json","responseBody":"={"ok": true, "id": {{ $json.id }}}"},"id":"wb4","name":"Return 200 OK","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position": 900,300 } , "connections": { "Receive Webhook":{"main": {"node":"Extract Fields","type":"main","index":0} }, "Extract Fields":{"main": {"node":"Save to Database","type":"main","index":0} }, "Save to Database":{"main": {"node":"Return 200 OK","type":"main","index":0} } }, "settings":{"executionOrder":"v1"}, "tags": {"name":"data"} } Use JSONB, not TEXT, for the payload column Postgres JSONB lets you query inside the payload with - operators: -- Find all Stripe payments over $50 SELECT received at, payload- 'amount' FROM webhook log WHERE source = 'stripe' AND event type = 'payment intent.succeeded' AND payload- 'amount' ::int 5000; This turns your webhook log into a lightweight analytics layer — no separate BI tool needed. Add deduplication to handle retries Services retry webhooks if they don't get a 200 within 5 seconds. Add a unique constraint on the event ID: ALTER TABLE webhook log ADD COLUMN event id TEXT UNIQUE; In Node 2: event id: body.id || body.event id || null — duplicate events get rejected at the DB level, silently. Route to different tables by source Add an IF/Switch node between Extract Fields and Save to Database. Route Stripe events to stripe events , GitHub events to github events . Faster queries, cleaner schema. Alert on high-value events Add a parallel branch after Extract Fields: IF event type === 'payment intent.succeeded' → Slack node. You get a Slack ping every time a payment lands, while the database write still happens in the main branch. Use Google Sheets instead of Postgres No database? Swap the Postgres node for a Google Sheets "Append Row" node. You lose JSONB querying but gain a shareable spreadsheet log that non-technical teammates can read. SELECT DATE received at , COUNT FROM webhook log WHERE event type = 'payment intent.succeeded' GROUP BY 1 event type LIKE '%failed%' or '%error%' This workflow is simple. What you build on top of it is not. This workflow is part of our 15-template n8n automation bundle — each one covering a different business use case: lead capture, invoice generation, AI customer support, social media automation, price monitoring, and more. Grab the full bundle at stripeai.gumroad.com — pre-tested, documented, ready to activate. Built with n8n. Self-hostable, open source, no vendor lock-in.