The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "WooCommerce \u2192 KSeF Gateway",
"nodes": [
{
"parameters": {
"content": "## Send WooCommerce orders to KSeF automatically\n\n### How it works\nThis workflow listens for a signed WooCommerce order webhook, verifies its `X-WC-Webhook-Signature` header, pulls the buyer's NIP from order meta data, and forwards the order to your self-hosted [KSeF Gateway](https://github.com/jurczykpawel/ksef-gateway) - a small HTTP wrapper around Poland's official e-invoice API. Orders without a NIP are treated as consumer purchases and skipped, since KSeF is only required for B2B sales. VAT rate is auto-detected per line item from WooCommerce's tax totals.\n\n### Setup\n1. Deploy a KSeF Gateway instance (Docker, Render, or StackPilot - see the project README) and note its URL. New to KSeF, or need a certificate (KSeF tokens are being phased out)? See [the main README](https://github.com/jurczykpawel/ksef-gateway#certificate-based-auth-alternative-to-tokens).\n2. Open **Configuration (EDIT ME)** and fill in your gateway URL, gateway API key, seller details, and the WooCommerce webhook secret.\n3. In WooCommerce, add a custom checkout field storing the buyer's NIP as order meta `_billing_nip` (or `billing_nip`), then create a webhook (Settings \u2192 Advanced \u2192 Webhooks) pointing at this workflow's Production URL with that same secret.\n4. See the red note on **Verify Signature** for one required n8n environment setting.\n\n### Customization tips\nAdd a Slack or email node after **Respond Skipped** if you want a heads-up whenever an order is missing a NIP.",
"height": 720,
"width": 700,
"color": 1
},
"id": "sticky-overview",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-760,
-380
]
},
{
"parameters": {
"content": "## Receive & Verify\nAccepts the WooCommerce webhook and checks its HMAC signature before trusting the payload.",
"height": 510,
"width": 1120,
"color": 7
},
"id": "sticky-section-a",
"name": "Section: Receive & Verify",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-760,
360
]
},
{
"parameters": {
"content": "## Build & Check\nMaps the order into a KSeF invoice payload and checks for a buyer NIP.",
"height": 510,
"width": 520,
"color": 7
},
"id": "sticky-section-b",
"name": "Section: Build & Check",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
440,
360
]
},
{
"parameters": {
"content": "## Send & Respond\nSends the invoice to your KSeF Gateway and reports the result back to WooCommerce.",
"height": 350,
"width": 520,
"color": 7
},
"id": "sticky-section-c",
"name": "Section: Send & Respond",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1040,
360
]
},
{
"parameters": {
"content": "## Node setting required\nThis Code node needs `require('crypto')`. Self-hosted n8n: set `NODE_FUNCTION_ALLOW_BUILTIN=crypto` in the n8n environment, then restart.",
"height": 220,
"width": 300,
"color": 3
},
"id": "sticky-warning",
"name": "Warning: crypto module",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-130,
710
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "woo-ksef",
"responseMode": "responseNode",
"options": {
"rawBody": true
}
},
"id": "webhook",
"name": "WooCommerce Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-700,
550
],
"notes": "Raw Body is required so the signature check below sees the exact bytes WooCommerce signed.",
"onError": "continueRegularOutput"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "a1",
"name": "ksefGatewayUrl",
"value": "https://your-ksef-gateway.example.com",
"type": "string"
},
{
"id": "a2",
"name": "sellerNip",
"value": "0000000000",
"type": "string"
},
{
"id": "a3",
"name": "sellerName",
"value": "Your Company sp. z o.o.",
"type": "string"
},
{
"id": "a4",
"name": "sellerStreet",
"value": "ul. Example 1",
"type": "string"
},
{
"id": "a5",
"name": "sellerCity",
"value": "00-001 Warszawa",
"type": "string"
},
{
"id": "a6",
"name": "webhookSecret",
"value": "change-me-webhook-secret",
"type": "string"
},
{
"id": "a7",
"name": "gatewayApiKey",
"value": "your-gateway-api-key",
"type": "string"
}
]
},
"includeOtherFields": true,
"options": {
"stripBinary": false
}
},
"id": "config-1",
"name": "Configuration (EDIT ME)",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-400,
550
],
"notes": "Fill in your KSeF Gateway URL, seller details, and the WooCommerce webhook secret before activating this workflow."
},
{
"parameters": {
"jsCode": "const crypto = require('crypto');\nconst config = $('Configuration (EDIT ME)').first().json;\nconst item = $input.first();\n\nconst signatureHeader = (item.json.headers && item.json.headers['x-wc-webhook-signature']) || '';\nconst rawBody = item.binary && item.binary.data\n ? Buffer.from(item.binary.data.data, 'base64').toString('utf8')\n : JSON.stringify(item.json.body || {});\n\nconst expected = crypto.createHmac('sha256', config.webhookSecret).update(rawBody).digest('base64');\n\nlet valid = false;\nif (signatureHeader && expected.length === signatureHeader.length) {\n valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));\n}\n\nreturn [{ json: { valid, body: JSON.parse(rawBody) } }];"
},
"id": "code-verify",
"name": "Verify Signature",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-100,
550
],
"notes": "Verifies WooCommerce's base64 HMAC-SHA256 X-WC-Webhook-Signature header and re-parses the raw body for downstream nodes."
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"leftValue": "={{ $json.valid }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true"
}
}
]
},
"options": {}
},
"id": "if-sig",
"name": "Signature Valid?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
200,
550
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\"success\":false,\"error\":\"invalid signature\"}",
"options": {
"responseCode": 401
}
},
"id": "res-unauth",
"name": "Respond Unauthorized",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
200,
710
],
"notes": "Dead end: bad or missing signature. Nothing downstream trusts this payload."
},
{
"parameters": {
"jsCode": "// Transform WooCommerce order to KSeF invoice format\nconst order = $input.first().json.body;\nconst config = $('Configuration (EDIT ME)').first().json;\n\n// Extract NIP from billing fields or meta_data\nconst nipField = (order.meta_data || []).find(m => m.key === '_billing_nip' || m.key === 'billing_nip');\nconst buyerNip = nipField?.value || '';\n\nif (!buyerNip) {\n // No NIP = consumer (osoba fizyczna), skip KSeF\n return [{ json: { skip: true, reason: 'No buyer NIP - consumer purchase, KSeF not required' } }];\n}\n\n// Map WooCommerce line items to KSeF items\nconst items = (order.line_items || []).map(item => {\n const netTotal = parseFloat(item.total) || 0;\n const taxTotal = parseFloat(item.total_tax) || 0;\n const quantity = item.quantity || 1;\n const unitPrice = Math.round((netTotal / quantity) * 100) / 100;\n\n // Detect VAT rate from tax amount\n let vatRate = 23; // default\n if (netTotal > 0) {\n const rate = Math.round((taxTotal / netTotal) * 100);\n if ([23, 8, 5, 0].includes(rate)) vatRate = rate;\n }\n\n return {\n name: item.name,\n quantity: quantity,\n unitPrice: unitPrice,\n vatRate: vatRate\n };\n});\n\nconst today = new Date().toISOString().split('T')[0];\n\nconst invoice = {\n invoiceNumber: `WOO/${order.number || order.id}`,\n issueDate: today,\n saleDate: order.date_created?.split('T')[0] || today,\n issuePlace: order.billing?.city || 'Warszawa',\n currency: order.currency || 'PLN',\n type: 'VAT',\n seller: {\n nip: config.sellerNip,\n name: config.sellerName,\n address: {\n street: config.sellerStreet,\n city: config.sellerCity\n }\n },\n buyer: {\n nip: buyerNip,\n name: order.billing?.company || `${order.billing?.first_name} ${order.billing?.last_name}`,\n address: {\n street: order.billing?.address_1 || '',\n city: `${order.billing?.postcode || ''} ${order.billing?.city || ''}`\n }\n },\n items: items,\n payment: {\n paid: order.status === 'completed' || order.date_paid != null,\n date: order.date_paid?.split('T')[0] || today,\n method: order.payment_method === 'cod' ? 'cash' : 'transfer'\n }\n};\n\nreturn [{ json: { skip: false, invoice } }];"
},
"id": "transform",
"name": "Transform to KSeF",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
500,
550
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"leftValue": "={{ $json.skip }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "false"
}
}
]
},
"options": {}
},
"id": "has-nip",
"name": "Has NIP?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
800,
550
],
"notes": "IF, not Filter - a Filter node only has one output and silently drops non-matching items, which broke the 'skipped' response path."
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: true, skipped: true, reason: $json.reason }) }}",
"options": {
"responseCode": 200
}
},
"id": "respond-skip",
"name": "Respond Skipped",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
800,
710
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Configuration (EDIT ME)').item.json.ksefGatewayUrl }}/ksef/invoice",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json.invoice) }}",
"options": {
"timeout": 120000
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Api-Key",
"value": "={{ $('Configuration (EDIT ME)').item.json.gatewayApiKey }}"
}
]
}
},
"id": "send-invoice",
"name": "Send to KSeF Gateway",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1100,
550
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: $json.data?.ksefNumber ? true : false, ksefNumber: $json.data?.ksefNumber || null, error: $json.error || null }) }}"
},
"id": "respond-ok",
"name": "Respond OK",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1400,
550
]
}
],
"connections": {
"WooCommerce Webhook": {
"main": [
[
{
"node": "Configuration (EDIT ME)",
"type": "main",
"index": 0
}
]
]
},
"Configuration (EDIT ME)": {
"main": [
[
{
"node": "Verify Signature",
"type": "main",
"index": 0
}
]
]
},
"Verify Signature": {
"main": [
[
{
"node": "Signature Valid?",
"type": "main",
"index": 0
}
]
]
},
"Signature Valid?": {
"main": [
[
{
"node": "Transform to KSeF",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond Unauthorized",
"type": "main",
"index": 0
}
]
]
},
"Transform to KSeF": {
"main": [
[
{
"node": "Has NIP?",
"type": "main",
"index": 0
}
]
]
},
"Has NIP?": {
"main": [
[
{
"node": "Send to KSeF Gateway",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond Skipped",
"type": "main",
"index": 0
}
]
]
},
"Send to KSeF Gateway": {
"main": [
[
{
"node": "Respond OK",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [],
"triggerCount": 0
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
WooCommerce → KSeF Gateway. Uses httpRequest. Webhook trigger; 15 nodes.
Source: https://github.com/jurczykpawel/ksef-gateway/blob/45af8720814454ef53dff12543891ac72733a0d4/examples/n8n/woocommerce-ksef.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di
This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .
eek-Go v2 (Batch-Then-Review). Uses httpRequest. Webhook trigger; 75 nodes.
This workflow receives webhook requests from a content calendar and uses the X API v2 to publish text posts, threads, image/video posts, and polls, as well as delete existing posts and run a credentia
This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c