
Tutorial - Creating a Secure Webhook
Description
Categories
⚙️ Automation
Nodes Used
n8n-nodes-base.ifn8n-nodes-base.setn8n-nodes-base.filtern8n-nodes-base.webhookn8n-nodes-base.webhookn8n-nodes-base.splitOutn8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNote
PriceGratuit
Views0
Last Updated11/28/2025
workflow.json
{
"meta": {
"instanceId": "e409ea34548a2afe2dffba31130cd1cf2e98ebe2afaeed2a63caf2a0582d1da0",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "d776b55a-65fe-4e12-9071-58911fa9c5cc",
"name": "Registered API Keys",
"type": "n8n-nodes-base.set",
"position": [
160,
520
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "2ab01b9f-473f-4f1b-8ddf-8d5a7b706323",
"name": "registered_api_keys",
"type": "array",
"value": "[\n {\n \"user_id\":\"user_1\",\n \"api_key\":\"test\"\n },\n {\n \"user_id\":\"user_2\",\n \"api_key\":\"sk-lihefoihz12121ZFzk124zehfAZJAOJZ14joEKe1h\"\n }\n]"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "d20bdc9b-3020-4fa3-88c3-b6c3d5a585b2",
"name": "API Key Identified",
"type": "n8n-nodes-base.if",
"position": [
480,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "0e476976-504d-4d21-a5d1-849fe54d322f",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.user_id }}",
"rightValue": "={{ $('Secured Webhook').item.json.headers['x-api-key'] }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "73ee78a3-9e0c-4f24-a05f-f860dc8fda88",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
120,
-260
],
"parameters": {
"color": 3,
"width": 300,
"height": 420,
"content": "### ⚙️ Key Verification Logic\n\nThis node takes the API key from the incoming request and asks our \"database\" (the second webhook) if it's valid.\n\n**In a real-world scenario, you would replace this and the nodes below with a single Database node (e.g., Supabase, Postgres) to perform the lookup.**"
},
"typeVersion": 1
},
{
"id": "b34ad49e-351a-4f42-8336-d4300d442c50",
"name": "Respond to Webhook (success)",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
700,
-100
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={\n \"status\": \"success\",\n \"user_id\": \"{{ $json.user_id }}\"\n}"
},
"typeVersion": 1.4
},
{
"id": "2da5855a-d54a-43f1-b4c5-3ff510a10bd3",
"name": "Respond to Webhook (unauthorized)",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
700,
100
],
"parameters": {
"options": {
"responseCode": 401
},
"respondWith": "json",
"responseBody": "{\n \"error\": \"Please provide a valid x-api-key header.\"\n}"
},
"typeVersion": 1.4
},
{
"id": "0b56b704-4dd6-490a-9998-f8eb2ef0a00e",
"name": "Secured Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-60,
0
],
"webhookId": "bf0be3b1-f140-4bc5-9040-a0e71dd78661",
"parameters": {
"path": "tutorial/secure-webhook",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode",
"authentication": "headerAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "pDp6BhprTE7kfSQy",
"name": "n8n Templates"
}
},
"typeVersion": 2
},
{
"id": "7308010d-5050-4e5e-8c8b-c57194b88516",
"name": "Check API Key",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
220,
0
],
"parameters": {
"url": "={{ $env.WEBHOOK_URL + ($env.N8N_ENDPOINT_WEBHOOK ?? \"webhook\") }}/tutorial/secure-webhook/api-keys",
"options": {},
"sendBody": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "api_key",
"value": "={{ $json.headers['x-api-key'] }}"
}
]
},
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "pDp6BhprTE7kfSQy",
"name": "n8n Templates"
}
},
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "88070916-27b2-42fb-8005-970b6d5a08fc",
"name": "Find API Key",
"type": "n8n-nodes-base.filter",
"position": [
600,
520
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "526ec1b1-4f6d-41ea-b35a-1347c01aa03a",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.api_key }}",
"rightValue": "={{ $('Get API Key').last().json.body.api_key }}"
}
]
}
},
"typeVersion": 2.2,
"alwaysOutputData": true
},
{
"id": "2a06aaa0-1ac3-4750-9770-6eec7e9310a9",
"name": "Get API Key",
"type": "n8n-nodes-base.webhook",
"position": [
-60,
520
],
"webhookId": "bf0be3b1-f140-4bc5-9040-a0e71dd78661",
"parameters": {
"path": "tutorial/secure-webhook/api-keys",
"options": {},
"responseMode": "lastNode",
"authentication": "headerAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "pDp6BhprTE7kfSQy",
"name": "n8n Templates"
}
},
"typeVersion": 2
},
{
"id": "3803f9b8-deec-4262-a76b-bfd72d572493",
"name": "Split Out Users",
"type": "n8n-nodes-base.splitOut",
"position": [
380,
520
],
"parameters": {
"options": {},
"fieldToSplitOut": "registered_api_keys"
},
"typeVersion": 1
},
{
"id": "d81a0dd5-9c90-4fee-a7ec-46e9aabb53a3",
"name": "Test Secure Webhook",
"type": "n8n-nodes-base.httpRequest",
"position": [
-420,
0
],
"parameters": {
"url": "={{ $env.WEBHOOK_URL + ($env.N8N_ENDPOINT_WEBHOOK ?? \"webhook\") }}/tutorial/secure-webhook",
"method": "POST",
"options": {},
"sendHeaders": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "x-api-key",
"value": "test"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "pDp6BhprTE7kfSQy",
"name": "n8n Templates"
}
},
"typeVersion": 4.2
},
{
"id": "ca63d58d-4683-4dbc-bd1e-6fd2724152f7",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-500,
-180
],
"parameters": {
"color": 6,
"width": 600,
"height": 340,
"content": "### ▶️ Public Endpoint & Tester\n\n* **`Secured Webhook`**: This is your public-facing endpoint. It listens for requests containing an `x-api-key` header.\n* **`Test Secure Webhook`**: Use this node to test the endpoint. Change the `x-api-key` header value to test valid and invalid keys."
},
"typeVersion": 1
},
{
"id": "14afef1c-6a99-46f5-96e1-c536a9afde69",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-140,
320
],
"parameters": {
"color": 7,
"width": 1020,
"height": 380,
"content": "### 📦 Mock Database\n\nThese nodes simulate a database of users and their API keys.\n\n* **`Get API Key`**: A private webhook that receives a key and checks it against the list.\n* **`Registered API Keys`**: **EDIT THIS NODE** to add or remove the API keys you want to be considered valid. Each key should be unique to a user."
},
"typeVersion": 1
},
{
"id": "12074ea9-65a9-4696-8c87-c77842f44467",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
440,
-300
],
"parameters": {
"color": 7,
"width": 440,
"height": 580,
"content": "#### ✅ Gatekeeper\n\nThis IF node checks the result from our \"database\".\n\n* If a user was found for the given API key, it proceeds to the **success** response.\n* If not, it sends a **401 Unauthorized** error."
},
"typeVersion": 1
}
],
"pinData": {
"Get API Key": [
{
"body": {
"api_key": "sk-lihefoihz12121ZFzk124zehfAZJAOJZ14joEKe1h"
},
"query": {},
"params": {},
"headers": {
"via": "1.1 Caddy",
"host": "api.ia2s.app",
"accept": "application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7",
"user-agent": "axios/1.8.3",
"x-n8n-auth": "my-secret-n8n-webhooks-password",
"content-type": "application/json",
"content-length": "58",
"accept-encoding": "gzip, compress, deflate, br",
"x-forwarded-for": "192.168.80.1",
"x-forwarded-host": "api.ia2s.app",
"x-forwarded-proto": "https"
},
"webhookUrl": "https://api.ia2s.app/webhook/tutorial/secure-webhook/api-keys",
"executionMode": "production"
}
],
"Secured Webhook": [
{
"body": {},
"query": {},
"params": {},
"headers": {
"via": "1.1 Caddy",
"host": "api.ia2s.app",
"accept": "application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7",
"x-api-key": "sk-lihefoihz12121ZFzk124zehfAZJAOJZ14joEKe1h",
"user-agent": "axios/1.8.3",
"x-n8n-auth": "my-secret-n8n-webhooks-password",
"content-length": "0",
"accept-encoding": "gzip, compress, deflate, br",
"x-forwarded-for": "192.168.80.1",
"x-forwarded-host": "api.ia2s.app",
"x-forwarded-proto": "https"
},
"webhookUrl": "https://api.ia2s.app/webhook/tutorial/secure-webhook",
"executionMode": "production"
}
]
},
"connections": {
"Get API Key": {
"main": [
[
{
"node": "Registered API Keys",
"type": "main",
"index": 0
}
]
]
},
"Check API Key": {
"main": [
[
{
"node": "API Key Identified",
"type": "main",
"index": 0
}
]
]
},
"Secured Webhook": {
"main": [
[
{
"node": "Check API Key",
"type": "main",
"index": 0
}
]
]
},
"Split Out Users": {
"main": [
[
{
"node": "Find API Key",
"type": "main",
"index": 0
}
]
]
},
"API Key Identified": {
"main": [
[
{
"node": "Respond to Webhook (success)",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond to Webhook (unauthorized)",
"type": "main",
"index": 0
}
]
]
},
"Registered API Keys": {
"main": [
[
{
"node": "Split Out Users",
"type": "main",
"index": 0
}
]
]
}
}
}