This workflow follows the HTTP Request → Redis recipe pattern — see all workflows that pair these two integrations.
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": "WhatsApp Baileys Staging Handler",
"tags": [
"staging",
"whatsapp",
"baileys"
],
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "whatsapp-baileys-staging",
"responseMode": "lastNode",
"options": {}
},
"id": "wa-order-trigger",
"name": "WhatsApp Order Staging Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
250,
300
]
},
{
"parameters": {
"jsCode": "// HMAC-SHA256 verification for staging Baileys webhook\n// Verifies x-signature header against HMAC_SECRET_STAGING env var\n\nconst crypto = require('crypto');\nconst signature = $input.item.json.headers?.['x-signature'] || $input.item.json.headers?.['x-baileys-signature'] || '';\nconst hmacSecret = process.env.HMAC_SECRET_STAGING;\nconst body = JSON.stringify($input.item.json.body || $input.item.json);\n\nif (!hmacSecret) {\n // No secret configured - skip verification in dev mode\n return { json: { ...$input.item.json, hmac_verified: true, skip_verify: true } };\n}\n\nconst expectedSignature = 'sha256=' + crypto.createHmac('sha256', hmacSecret).update(body).digest('hex');\n\n// Use timingSafeEqual only if both strings have the same length\nconst isValid = signature.length === expectedSignature.length && \n crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));\n\nif (!isValid && signature) {\n throw new Error('HMAC verification failed - invalid signature');\n}\n\nreturn { json: { ...$input.item.json, hmac_verified: true } };"
},
"id": "hmac-verify",
"name": "Verify HMAC Signature",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
470,
300
]
},
{
"parameters": {
"jsCode": "// Parse Baileys-style webhook body\n// Expected format: { messages: [{ key: { remoteJid, id }, message: { ... } }], event: \"messages.upsert\" }\n\nconst body = $input.item.json.body || $input.item.json;\nconst messages = body.messages || [];\n\nif (!messages || messages.length === 0) {\n // No messages in payload - might be other event type\n return { json: { ...body, parsed: false, skip_processing: true } };\n}\n\nconst msg = messages[0];\nconst key = msg.key || {};\n\n// Extract message text\nlet text = '';\nif (msg.message?.conversation) {\n text = msg.message.conversation;\n} else if (msg.message?.extendedTextMessage?.text) {\n text = msg.message.extendedTextMessage.text;\n} else if (msg.message?.imageMessage?.caption) {\n text = msg.message.imageMessage.caption;\n} else if (msg.message?.videoMessage?.caption) {\n text = msg.message.videoMessage.caption;\n}\n\n// Extract user JID (phone number)\nconst remoteJid = key.remoteJid || '';\n// Convert JID to phone number (remove @s.whatsapp.net)\nconst phone = remoteJid.replace('@s.whatsapp.net', '').replace('@g.us', '');\n\nconst textLower = text.toLowerCase().trim();\n\n// Intent detection keywords\nconst orderKeywords = ['order', 'buy', 'purchase', 'get proxy', 'want proxy', 'isp', 'residential', 'mobile', 'datacenter'];\nconst trialKeywords = ['free trial', 'trial', 'free plan', 'try free'];\nconst statusKeywords = ['status', 'check', 'track', 'where is', 'my order'];\nconst helpKeywords = ['help', 'menu', 'start', 'hello', 'hi'];\n\nconst isOrder = orderKeywords.some(k => textLower.includes(k));\nconst isTrial = trialKeywords.some(k => textLower.includes(k));\nconst isStatus = statusKeywords.some(k => textLower.includes(k));\nconst isHelp = helpKeywords.some(k => textLower.includes(k));\n\nconst intent = isOrder ? 'order' : isTrial ? 'free_trial' : isStatus ? 'status' : isHelp ? 'help' : 'unknown';\n\nreturn {\n json: {\n ...body,\n parsed: true,\n skip_processing: false,\n channel: 'baileys',\n phone: phone,\n user_id: phone,\n remote_jid: remoteJid,\n message_id: key.id || '',\n message_text: text,\n message_raw: msg,\n intent,\n is_order: isOrder,\n is_trial: isTrial,\n is_status: isStatus,\n is_help: isHelp\n }\n};"
},
"id": "parse-msg",
"name": "Parse Baileys Message",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
690,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": false
},
"conditions": [
{
"id": "cond-skip",
"leftValue": "={{$json.skip_processing}}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "false"
}
}
]
},
"options": {}
},
"id": "if-parse",
"name": "If Valid Message",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
910,
300
]
},
{
"parameters": {
"jsCode": "// Prepare idempotency key with staging prefix\n// Uses staging: prefix to avoid collision with prod\n\nconst messageId = $input.item.json.message_id || '';\nconst phone = $input.item.json.phone || '';\nconst idempotencyKey = `staging:idempotency:baileys:${messageId || phone}`;\n\nreturn {\n json: {\n ...$input.item.json,\n idempotency_key: idempotencyKey\n }\n};"
},
"id": "prep-idemp",
"name": "Prepare Idempotency Key",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1130,
300
]
},
{
"parameters": {
"rule": "set",
"key": "{{$json.idempotency_key}}",
"value": "1",
"options": {
"ttl": 86400,
"setNX": true
}
},
"id": "idemp-check",
"name": "Idempotency Redis Check",
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
1350,
300
],
"notes": "Check if message already processed with staging: prefix"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": false
},
"conditions": [
{
"id": "cond-need-process",
"leftValue": "={{$json[\"set\"]}}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true"
}
}
]
},
"options": {}
},
"id": "if-process",
"name": "If New Message",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1570,
300
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.minimax.chat/v1/chat/completions",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {{$env.MINIMAX_API_KEY}}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "model",
"value": "MiniMax-M2"
},
{
"name": "messages",
"value": [
{
"role": "system",
"content": "You are Styxproxy, a WhatsApp proxy reseller assistant. Parse customer messages and extract intent. Available intents: order, renewal, status, help, free_trial, recovery, how_to_use, check_data, referral_share, unknown. For orders, extract: proxy_type (isp/residential/mobile/datacenter), duration, quantity. Respond with JSON: { intent, entities: { proxy_type, duration, quantity }, original_message }"
},
{
"role": "user",
"content": "={{$json.message_text}}"
}
]
}
]
},
"options": {
"timeout": 10000
}
},
"id": "llm-parse",
"name": "MiniMax LLM Parser",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1790,
300
]
},
{
"parameters": {
"jsCode": "// Parse LLM response\n\nlet content = $input.item.json.choices?.[0]?.message?.content || '';\nlet parsed = { intent: 'unknown', entities: {} };\n\ntry {\n if (typeof content === 'string') {\n content = content.replace(/^```json\\n?/, '').replace(/\\n?```$/, '');\n parsed = JSON.parse(content);\n }\n} catch (e) {\n // Use keyword fallback\n const msg = ($input.item.json.message_text || '').toLowerCase();\n parsed = {\n intent: msg.includes('order') || msg.includes('buy') ? 'order' : \n msg.includes('trial') ? 'free_trial' : \n msg.includes('status') ? 'status' : \n msg.includes('help') ? 'help' : 'unknown',\n entities: {}\n };\n}\n\nreturn {\n json: {\n ...$input.item.json,\n intent: parsed.intent || 'unknown',\n entities: parsed.entities || {},\n llm_raw: content\n }\n};"
},
"id": "parse-llm",
"name": "Parse LLM Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2010,
300
]
},
{
"parameters": {
"jsCode": "// Intent switch - route based on LLM-parsed intent\n\nconst intent = $input.item.json.intent;\n\nreturn {\n json: {\n ...$input.item.json,\n intent_routed: intent\n }\n};"
},
"id": "route-intent",
"name": "Intent Switch",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2230,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "cond-order",
"leftValue": "={{$json.intent_routed}}",
"rightValue": "order",
"operation": "equals"
}
]
},
"options": {}
},
"id": "if-order",
"name": "If Order Intent",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
2450,
150
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "cond-help",
"leftValue": "={{$json.intent_routed}}",
"rightValue": "help",
"operation": "equals"
}
]
},
"options": {}
},
"id": "if-help",
"name": "If Help Intent",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
2450,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "cond-trial",
"leftValue": "={{$json.intent_routed}}",
"rightValue": "free_trial",
"operation": "equals"
}
]
},
"options": {}
},
"id": "if-trial",
"name": "If Trial Intent",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
2450,
450
]
},
{
"parameters": {
"jsCode": "// Generate order menu for WhatsApp/Baileys\nconst message = `\ud83d\uded2 *Order Proxy*\n\nChoose your proxy type:\n\n\ud83d\udce1 *ISP Proxy* - \u20a66,500/month\n \u2022 Static IP, Nigeria\n \u2022 Fast speed\n\n\ud83c\udfe0 *Residential Proxy* - \u20a61,950/GB\n \u2022 Rotating IPs\n \u2022 High anonymity\n\n\ud83d\udcf1 *Mobile Proxy* - \u20a62,500/GB\n \u2022 4G/5G IPs\n \u2022 Carrier-level\n\n\ud83d\udda5\ufe0f *Datacenter Proxy* - \u20a64,000/month\n \u2022 Fast, reliable\n\nReply with:\n\u2022 \"ISP\" for ISP proxy\n\u2022 \"Residential\" for residential\n\u2022 \"Mobile\" for mobile\n\u2022 \"Datacenter\" for datacenter\n\nOr say \"menu\" to go back.`;\n\nreturn { json: { ...$input.item.json, reply_message: message } };"
},
"id": "gen-order-menu",
"name": "Generate Order Menu",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2670,
50
]
},
{
"parameters": {
"jsCode": "// Generate help menu\nconst message = `\ud83c\uddf3\ud83c\uddec *Styxproxy Proxy*\n\nI can help you with:\n\n\ud83d\uded2 *Order* \u2014 ISP, Residential, Mobile, Datacenter\n\ud83d\udcb3 *Check Status* \u2014 Track your order\n\ud83d\udcb0 *Pricing* \u2014 See all plans\n\u2753 *Help* \u2014 How to get started\n\ud83d\udd17 *Free Trial* \u2014 Get a free proxy\n\nWhat would you like to do?`;\n\nreturn { json: { ...$input.item.json, reply_message: message } };"
},
"id": "gen-help",
"name": "Generate Help Message",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2670,
300
]
},
{
"parameters": {
"jsCode": "// Generate trial info\nconst message = `\ud83c\udf81 *Free Trial*\n\nGet a free proxy to test our service!\n\n*How it works:*\n1. Complete a short survey\n2. Earn 2 hours per survey\n3. Do up to 12 surveys = 24 hours free!\n\n*To start:*\nClick the survey link, complete it, then come back and say \"done\"\n\n*Note:* Free trial proxies are shared and for testing only.\n\nReady to start? Say \"I want trial\" or click the survey link.`;\n\nreturn { json: { ...$input.item.json, reply_message: message } };"
},
"id": "gen-trial",
"name": "Generate Trial Message",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2670,
550
]
},
{
"parameters": {
"method": "POST",
"url": "{{$env.BAILEYS_RUNTIME_URL}}/send-message",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "phone",
"value": "={{$json.remote_jid}}"
},
{
"name": "message",
"value": "={{$json.reply_message}}"
}
]
},
"options": {}
},
"id": "baileys-reply",
"name": "Reply via Baileys HTTP",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2890,
300
],
"notes": "Sends to Baileys runtime at BAILEYS_RUNTIME_URL env var"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\"status\":\"ok\"}",
"options": {
"responseCode": 200
}
},
"id": "respond-200",
"name": "Respond 200 OK",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
3110,
300
]
}
],
"connections": {
"WhatsApp Order Staging Trigger": {
"main": [
[
{
"node": "Verify HMAC Signature",
"type": "main",
"index": 0
}
]
]
},
"Verify HMAC Signature": {
"main": [
[
{
"node": "Parse Baileys Message",
"type": "main",
"index": 0
}
]
]
},
"Parse Baileys Message": {
"main": [
[
{
"node": "If Valid Message",
"type": "main",
"index": 0
}
]
]
},
"If Valid Message": {
"main": [
[
{
"node": "Prepare Idempotency Key",
"type": "main",
"index": 0
}
]
]
},
"Prepare Idempotency Key": {
"main": [
[
{
"node": "Idempotency Redis Check",
"type": "main",
"index": 0
}
]
]
},
"Idempotency Redis Check": {
"main": [
[
{
"node": "If New Message",
"type": "main",
"index": 0
}
]
]
},
"If New Message": {
"main": [
[
{
"node": "MiniMax LLM Parser",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond 200 OK",
"type": "main",
"index": 0
}
]
]
},
"MiniMax LLM Parser": {
"main": [
[
{
"node": "Parse LLM Response",
"type": "main",
"index": 0
}
]
]
},
"Parse LLM Response": {
"main": [
[
{
"node": "Intent Switch",
"type": "main",
"index": 0
}
]
]
},
"Intent Switch": {
"main": [
[
{
"node": "If Order Intent",
"type": "main",
"index": 0
}
],
[
{
"node": "If Help Intent",
"type": "main",
"index": 0
}
],
[
{
"node": "If Trial Intent",
"type": "main",
"index": 0
}
]
]
},
"If Order Intent": {
"main": [
[
{
"node": "Generate Order Menu",
"type": "main",
"index": 0
}
]
]
},
"If Help Intent": {
"main": [
[
{
"node": "Generate Help Message",
"type": "main",
"index": 0
}
]
]
},
"If Trial Intent": {
"main": [
[
{
"node": "Generate Trial Message",
"type": "main",
"index": 0
}
]
]
},
"Generate Order Menu": {
"main": [
[
{
"node": "Reply via Baileys HTTP",
"type": "main",
"index": 0
}
]
]
},
"Generate Help Message": {
"main": [
[
{
"node": "Reply via Baileys HTTP",
"type": "main",
"index": 0
}
]
]
},
"Generate Trial Message": {
"main": [
[
{
"node": "Reply via Baileys HTTP",
"type": "main",
"index": 0
}
]
]
},
"Reply via Baileys HTTP": {
"main": [
[
{
"node": "Respond 200 OK",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
WhatsApp Baileys Staging Handler. Uses redis, httpRequest. Webhook trigger; 18 nodes.
Source: https://github.com/sonnyagent30-beep/Styxproxy/blob/878d93ddb0424068ef90bf013fa3a31461b471a6/.n8n/workflows/staging/03-whatsapp-baileys-staging.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.
[](https://www.linkedin.com/in/mosaab-yassir-lafrimi/)[](https://t.me/joevenner)
Shared Checkout Staging Handler. Uses redis, httpRequest, telegram. Webhook trigger; 20 nodes.
Telegram Order Staging Handler. Uses redis, httpRequest, telegram. Webhook trigger; 17 nodes.
Channel Intent Router (Template). Uses redis, httpRequest, telegram. Webhook trigger; 16 nodes.
Order Handler. Uses redis, httpRequest, whatsApp. Webhook trigger; 11 nodes.