This workflow follows the Error Trigger → HTTP Request 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": "Workflow B \u2014 AI Listing Engine",
"nodes": [
{
"id": "wb000001-0001-0001-0001-000000000001",
"name": "Listing Input Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
200,
300
],
"parameters": {
"path": "realestate/listing-input",
"httpMethod": "POST",
"responseMode": "lastNode",
"options": {}
}
},
{
"id": "wb000002-0002-0002-0002-000000000002",
"name": "Validate Inputs",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
440,
300
],
"parameters": {
"language": "javaScript",
"jsCode": "const body = $input.first().json.body ?? $input.first().json;\nconst errors = [];\n\nconst required = ['propertyId','rawNotes','bedrooms','bathrooms','location','area','priceGhs','propertyType','listingType'];\nfor (const f of required) {\n if (body[f] === undefined || body[f] === null || body[f] === '') {\n errors.push('Missing required field: ' + f);\n }\n}\n\nconst PROPERTY_TYPES = ['APARTMENT','HOUSE','LAND','COMMERCIAL','SELF_CONTAIN','CHAMBER_AND_HALL','BOYS_QUARTERS'];\nconst LISTING_TYPES = ['SALE','RENT'];\n\nif (!errors.length) {\n if (typeof body.bedrooms !== 'number' || body.bedrooms < 0) errors.push('bedrooms must be a non-negative number');\n if (typeof body.bathrooms !== 'number' || body.bathrooms < 0) errors.push('bathrooms must be a non-negative number');\n if (typeof body.priceGhs !== 'number' || body.priceGhs < 0) errors.push('priceGhs must be a non-negative number');\n if (!PROPERTY_TYPES.includes(body.propertyType)) errors.push('propertyType must be one of: ' + PROPERTY_TYPES.join(', '));\n if (!LISTING_TYPES.includes(body.listingType)) errors.push('listingType must be SALE or RENT');\n if (typeof body.area !== 'string' || !body.area.trim()) errors.push('area must be a non-empty string');\n}\n\nif (errors.length) {\n return [{ json: { _validationFailed: true, errors, propertyId: body.propertyId ?? null, body } }];\n}\n\nreturn [{ json: { _validationFailed: false, errors: [], ...body } }];"
}
},
{
"id": "wb000003-0003-0003-0003-000000000003",
"name": "Inputs Valid?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
680,
300
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-valid",
"leftValue": "={{ $json._validationFailed }}",
"rightValue": false,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
]
},
"options": {}
}
},
{
"id": "wb000004-0004-0004-0004-000000000004",
"name": "Create INPUTS_INVALID ReviewTask",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
920,
480
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"contentType": "json",
"jsonBody": "={{ { query: 'mutation { createReviewTask(data: { kind: \"LISTING_APPROVAL\", subjectProperty: { connect: { where: { id: \"' + ($json.propertyId ?? 'unknown') + '\" } } } }) { id kind status } }' } }}",
"options": {
"timeout": 15000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 2000
}
}
}
},
{
"id": "wb000005-0005-0005-0005-000000000005",
"name": "Return Invalid Inputs Response",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1160,
480
],
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "inv-ok",
"name": "ok",
"value": false,
"type": "boolean"
},
{
"id": "inv-errors",
"name": "errors",
"value": "={{ $('Validate Inputs').first()?.json?.errors ?? [] }}",
"type": "array"
}
]
},
"options": {}
}
},
{
"id": "wb000006-0006-0006-0006-000000000006",
"name": "Voice Note Present?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
920,
260
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-voice",
"leftValue": "={{ $json.voiceNoteUrl ?? '' }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEquals"
}
}
]
},
"options": {}
}
},
{
"id": "wb000007-0007-0007-0007-000000000007",
"name": "Transcribe Voice Note \u2014 Groq Whisper",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1160,
160
],
"onError": "continueErrorOutput",
"parameters": {
"method": "POST",
"url": "https://api.groq.com/openai/v1/audio/transcriptions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.GROQ_API_KEY }}"
}
]
},
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"name": "url",
"value": "={{ $json.voiceNoteUrl }}"
},
{
"name": "model",
"value": "whisper-large-v3-turbo"
},
{
"name": "language",
"value": "en"
}
]
},
"options": {
"timeout": 30000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 2000
}
}
}
},
{
"id": "wb000008-0008-0008-0008-000000000008",
"name": "Whisper Succeeded?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1400,
160
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-whisper",
"leftValue": "={{ $json.text ?? '' }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEquals"
}
}
]
},
"options": {}
}
},
{
"id": "wb000009-0009-0009-0009-000000000009",
"name": "Create TRANSCRIPTION_FAILED ReviewTask",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1640,
260
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"contentType": "json",
"jsonBody": "={{ { query: 'mutation { createReviewTask(data: { kind: \"LISTING_APPROVAL\", subjectProperty: { connect: { where: { id: \"' + $('Validate Inputs').first().json.propertyId + '\" } } } }) { id kind status } }' } }}",
"options": {
"timeout": 15000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 2000
}
}
}
},
{
"id": "wb000010-0010-0010-0010-000000000010",
"name": "Set Transcribed Text",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1640,
120
],
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "trans-text",
"name": "transcribed",
"value": "={{ $json.text ?? '' }}",
"type": "string"
}
]
},
"options": {}
}
},
{
"id": "wb000011-0011-0011-0011-000000000011",
"name": "Merge Notes",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1880,
300
],
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "merge-notes",
"name": "mergedNotes",
"value": "={{ (function() { const base = $('Validate Inputs').first().json.rawNotes || ''; const transcript = $('Set Transcribed Text').first()?.json?.transcribed ?? ''; return transcript ? base + '\\n\\nVOICE NOTE: ' + transcript : base; })() }}",
"type": "string"
},
{
"id": "merge-pid",
"name": "propertyId",
"value": "={{ $('Validate Inputs').first().json.propertyId }}",
"type": "string"
},
{
"id": "merge-beds",
"name": "bedrooms",
"value": "={{ $('Validate Inputs').first().json.bedrooms }}",
"type": "number"
},
{
"id": "merge-baths",
"name": "bathrooms",
"value": "={{ $('Validate Inputs').first().json.bathrooms }}",
"type": "number"
},
{
"id": "merge-loc",
"name": "location",
"value": "={{ $('Validate Inputs').first().json.location }}",
"type": "string"
},
{
"id": "merge-area",
"name": "area",
"value": "={{ $('Validate Inputs').first().json.area }}",
"type": "string"
},
{
"id": "merge-price",
"name": "priceGhs",
"value": "={{ $('Validate Inputs').first().json.priceGhs }}",
"type": "number"
},
{
"id": "merge-ptype",
"name": "propertyType",
"value": "={{ $('Validate Inputs').first().json.propertyType }}",
"type": "string"
},
{
"id": "merge-ltype",
"name": "listingType",
"value": "={{ $('Validate Inputs').first().json.listingType }}",
"type": "string"
}
]
},
"options": {}
}
},
{
"id": "wb000012-0012-0012-0012-000000000012",
"name": "Build OpenAI Messages",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2120,
300
],
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "msg-system",
"name": "system",
"value": "You are a Ghanaian property-portal copywriter. Your job is to produce five marketing outputs for a residential or commercial property listing in Ghana, using the structured inputs and raw agent notes provided.\n\nSTYLE GUIDE:\n- Write in plain, professional Ghanaian English. No US-style hyped language.\n- Never write 'DM us', 'WhatsApp us', 'Call now', 'Message us', or similar direct-contact calls to action. The property portal handles inbound enquiries.\n- Prices must be written in GHS with thousand separators, e.g. 'GHS 4,500/month' or 'GHS 850,000'.\n- Use neighbourhood context from this reference:\n EAST_LEGON: upscale residential, gated estates, embassies nearby\n AIRPORT_RES: Airport Residential Area, quiet, established, close to Kotoka\n CANTONMENTS: diplomatic quarter, tree-lined streets, high-security\n OSU: lively, central, restaurants and nightlife on Oxford Street\n LABONE: mid-to-high end, near beach, popular with expats\n SPINTEX: fast-growing, highway access, many new developments\n TEMA: industrial port city, community estates, affordable\n ADENTA: suburb on Adenta-Dodowa Road, growing middle-class\n MADINA: north-east Accra, busy market town, affordable\n ACHIMOTA: near Achimota Forest Reserve, educational institutions nearby\n DZORWULU: quiet, leafy, near Airport Hills\n AIRPORT_HILLS: elevated, panoramic views, premium\n RIDGE: government district, historic, mature trees\n ROMAN_RIDGE: upscale, near Ridge, embassies\n KASOA: Western Region border, high growth, affordable\n OTHER: describe based on provided location details\n\nOUTPUT REQUIREMENTS \u2014 return ONLY a JSON object with these five keys:\n\n1. listing_description (string, 200-300 words):\n Full property-portal description. Include: property type, bedrooms/bathrooms, price with frequency (per month or outright), neighbourhood feel, key amenities, standout features. End with a benefit statement.\n\n2. whatsapp_broadcast (string, max 500 characters, NO emojis):\n Plain-text broadcast message. Short. No direct-contact CTAs. Mention property type, location, price, 2-3 key features. End with 'Reply YES for details'.\n\n3. facebook_caption (string, 300-500 characters, light emojis OK):\n Engaging Facebook post. Highlight 2-3 features. Neighbourhood feel. Price. End with 'Tap the link in bio to enquire.'.\n\n4. instagram_caption (string, max 2200 characters, hashtags required):\n Visually descriptive IG caption. Lifestyle angle. Neighbourhood. Price. End with at least 10 relevant hashtags including #GhanaRealEstate #AccraHomes and area-specific tags.\n\n5. reel_script (string, no length limit):\n 30-second Instagram Reel voiceover with shot list. Format:\n [SHOT 1: ...] Voiceover: \"...\"\n [SHOT 2: ...] Voiceover: \"...\"\n etc. Include 4-6 shots. End with a property-portal tagline.\n\nOutput ONLY the JSON object. No markdown. No preamble.",
"type": "string"
},
{
"id": "msg-messages",
"name": "messages",
"value": "={{ (function() { const d = $json; return [{ role: 'user', content: JSON.stringify({ propertyId: d.propertyId, propertyType: d.propertyType, listingType: d.listingType, bedrooms: d.bedrooms, bathrooms: d.bathrooms, location: d.location, area: d.area, priceGhs: d.priceGhs, agentNotes: d.mergedNotes }) }]; })() }}",
"type": "array"
}
]
},
"options": {}
}
},
{
"id": "wb000013-0013-0013-0013-000000000013",
"name": "Compose Listing \u2014 gpt-4o",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.3,
"position": [
2360,
300
],
"parameters": {
"workflowId": {
"value": "SUBFLOW_OPENAI_CALL_ID",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"system": "={{ $json.system }}",
"messages": "={{ $json.messages }}",
"model": "gpt-4o",
"max_tokens": 2500,
"temperature": 0.4,
"workflow_label": "workflow-b-listing-engine"
},
"schema": [
{
"id": "system",
"displayName": "system",
"type": "string",
"required": false
},
{
"id": "messages",
"displayName": "messages",
"type": "array",
"required": true
},
{
"id": "model",
"displayName": "model",
"type": "string",
"required": true
},
{
"id": "max_tokens",
"displayName": "max_tokens",
"type": "number",
"required": false
},
{
"id": "temperature",
"displayName": "temperature",
"type": "number",
"required": false
},
{
"id": "workflow_label",
"displayName": "workflow_label",
"type": "string",
"required": false
}
]
}
}
},
{
"id": "wb000014-0014-0014-0014-000000000014",
"name": "Parse Listing Output",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2600,
300
],
"parameters": {
"language": "javaScript",
"jsCode": "const raw = $input.first().json.text ?? '';\nlet parsed;\ntry {\n // Strip markdown code fences if present\n const cleaned = raw.replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/,'').trim();\n parsed = JSON.parse(cleaned);\n} catch (e) {\n return [{ json: { _parseFailed: true, parseError: e.message, rawText: raw } }];\n}\nreturn [{ json: { _parseFailed: false, ...parsed } }];"
}
},
{
"id": "wb000015-0015-0015-0015-000000000015",
"name": "Parse Succeeded?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
2840,
300
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-parse",
"leftValue": "={{ $json._parseFailed }}",
"rightValue": false,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
]
},
"options": {}
}
},
{
"id": "wb000016-0016-0016-0016-000000000016",
"name": "Log Parse Failure",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
3080,
460
],
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack, input_payload) VALUES ($1, $2, $3, $4, $5, $6::jsonb)",
"options": {
"queryReplacement": "={{ ['Workflow B \u2014 AI Listing Engine', $execution.id, 'Parse Listing Output', ($json.parseError ?? 'JSON parse failed'), ($json.parseError ?? ''), '{\"node\":\"Parse Listing Output\"}'] }}"
}
}
},
{
"id": "wb000017-0017-0017-0017-000000000017",
"name": "Create PARSE_FAILED ReviewTask",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
3320,
460
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"contentType": "json",
"jsonBody": "={{ { query: 'mutation { createReviewTask(data: { kind: \"LISTING_APPROVAL\", subjectProperty: { connect: { where: { id: \"' + $('Merge Notes').first().json.propertyId + '\" } } } }) { id kind status } }' } }}",
"options": {
"timeout": 15000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 2000
}
}
}
},
{
"id": "wb000018-0018-0018-0018-000000000018",
"name": "Return Parse Failed Response",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
3560,
460
],
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "pf-ok",
"name": "ok",
"value": false,
"type": "boolean"
},
{
"id": "pf-err",
"name": "error",
"value": "LISTING_PARSE_FAILED",
"type": "string"
}
]
},
"options": {}
}
},
{
"id": "wb000019-0019-0019-0019-000000000019",
"name": "Validate Outputs",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3080,
260
],
"parameters": {
"language": "javaScript",
"jsCode": "const d = $input.first().json;\nconst validationErrors = [];\n\nconst BANNED = [/\\bDM\\s+us\\b/i, /\\bWhatsApp\\s+us\\b/i, /\\bCall\\s+now\\b/i, /\\bMessage\\s+us\\b/i];\n\nfunction checkBanned(field, text) {\n for (const re of BANNED) {\n if (re.test(text)) validationErrors.push(field + ': contains banned phrase matching ' + re.toString());\n }\n}\n\n// listing_description\nif (!d.listing_description || !d.listing_description.trim()) {\n validationErrors.push('listing_description: missing or empty');\n} else {\n const len = d.listing_description.length;\n if (len < 200 || len > 320) validationErrors.push('listing_description: length ' + len + ' outside 200-320');\n checkBanned('listing_description', d.listing_description);\n // Bedroom count check\n const beds = $('Merge Notes').first()?.json?.bedrooms;\n if (beds !== undefined) {\n const digitPattern = /(\\d+)(?:\\s*-\\s*bed|\\s+bedroom)/i;\n const match = d.listing_description.match(digitPattern);\n if (match && parseInt(match[1], 10) !== beds) {\n validationErrors.push('listing_description: mentions ' + match[1] + ' bed but inputs say ' + beds);\n }\n }\n}\n\n// whatsapp_broadcast\nif (!d.whatsapp_broadcast || !d.whatsapp_broadcast.trim()) {\n validationErrors.push('whatsapp_broadcast: missing or empty');\n} else {\n if (d.whatsapp_broadcast.length > 500) validationErrors.push('whatsapp_broadcast: length ' + d.whatsapp_broadcast.length + ' exceeds 500');\n checkBanned('whatsapp_broadcast', d.whatsapp_broadcast);\n}\n\n// facebook_caption\nif (!d.facebook_caption || !d.facebook_caption.trim()) {\n validationErrors.push('facebook_caption: missing or empty');\n} else {\n const len = d.facebook_caption.length;\n if (len < 280 || len > 520) validationErrors.push('facebook_caption: length ' + len + ' outside 280-520');\n checkBanned('facebook_caption', d.facebook_caption);\n}\n\n// instagram_caption\nif (!d.instagram_caption || !d.instagram_caption.trim()) {\n validationErrors.push('instagram_caption: missing or empty');\n} else {\n if (d.instagram_caption.length > 2200) validationErrors.push('instagram_caption: length ' + d.instagram_caption.length + ' exceeds 2200');\n checkBanned('instagram_caption', d.instagram_caption);\n}\n\n// reel_script\nif (!d.reel_script || !d.reel_script.trim()) {\n validationErrors.push('reel_script: missing or empty');\n} else {\n checkBanned('reel_script', d.reel_script);\n}\n\nreturn [{ json: { ...d, validationErrors } }];"
}
},
{
"id": "wb000020-0020-0020-0020-000000000020",
"name": "Update Property Description",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
3320,
260
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"contentType": "json",
"jsonBody": "={{ (function() { const pid = $('Merge Notes').first().json.propertyId; const desc = $json.listing_description; const safeDesc = JSON.stringify(desc); return { query: 'mutation { updateProperty(id: \"' + pid + '\", data: { description: ' + safeDesc + ' }) { id description } }' }; })() }}",
"options": {
"timeout": 15000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 2000
}
}
}
},
{
"id": "wb000021-0021-0021-0021-000000000021",
"name": "Property Update OK?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
3560,
260
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-prop-ok",
"leftValue": "={{ ($json.errors?.length ?? 0) }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "equals"
}
}
]
},
"options": {}
}
},
{
"id": "wb000022-0022-0022-0022-000000000022",
"name": "Log Property Update Error",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
3800,
380
],
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack, input_payload) VALUES ($1, $2, $3, $4, $5, $6::jsonb)",
"options": {
"queryReplacement": "={{ ['Workflow B \u2014 AI Listing Engine', $execution.id, 'Update Property Description', ($json.errors?.[0]?.message ?? 'Twenty GQL error on updateProperty'), '', '{\"node\":\"Update Property Description\"}'] }}"
}
}
},
{
"id": "wb000023-0023-0023-0023-000000000023",
"name": "Send Telegram \u2014 Property Error",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
4040,
380
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/sendMessage' }}",
"sendBody": true,
"specifyBody": "json",
"contentType": "json",
"jsonBody": "={{ { chat_id: $env.TELEGRAM_CHANNEL_ID, text: '[Workflow B] ERROR: updateProperty failed for ' + $('Merge Notes').first().json.propertyId + '. Check workflow_errors.' } }}",
"options": {
"timeout": 10000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 1000
}
}
}
},
{
"id": "wb000024-0024-0024-0024-000000000024",
"name": "Create SocialPost \u2014 Facebook",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
3800,
200
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"contentType": "json",
"jsonBody": "={{ (function() { const pid = $('Merge Notes').first().json.propertyId; const body = $('Validate Outputs').first().json.facebook_caption; return { query: 'mutation { createSocialPost(data: { body: ' + JSON.stringify(body) + ', platform: \"FACEBOOK\", status: \"PENDING_APPROVAL\", property: { connect: { where: { id: \"' + pid + '\" } } } }) { id platform status } }' }; })() }}",
"options": {
"timeout": 15000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 2000
}
}
}
},
{
"id": "wb000025-0025-0025-0025-000000000025",
"name": "FB Post OK?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
4040,
200
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-fb-ok",
"leftValue": "={{ ($json.errors?.length ?? 0) }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "equals"
}
}
]
},
"options": {}
}
},
{
"id": "wb000026-0026-0026-0026-000000000026",
"name": "Log FB Post Error",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
4280,
300
],
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack, input_payload) VALUES ($1, $2, $3, $4, $5, $6::jsonb)",
"options": {
"queryReplacement": "={{ ['Workflow B \u2014 AI Listing Engine', $execution.id, 'Create SocialPost \u2014 Facebook', ($json.errors?.[0]?.message ?? 'Twenty GQL error on createSocialPost FB'), '', '{\"node\":\"Create SocialPost FB\"}'] }}"
}
}
},
{
"id": "wb000027-0027-0027-0027-000000000027",
"name": "Capture FB Post ID",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
4280,
160
],
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "fb-id",
"name": "fbPostId",
"value": "={{ $json.data?.createSocialPost?.id ?? '' }}",
"type": "string"
}
]
},
"options": {}
}
},
{
"id": "wb000028-0028-0028-0028-000000000028",
"name": "Create SocialPost \u2014 Instagram",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
4520,
160
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"contentType": "json",
"jsonBody": "={{ (function() { const pid = $('Merge Notes').first().json.propertyId; const body = $('Validate Outputs').first().json.instagram_caption; return { query: 'mutation { createSocialPost(data: { body: ' + JSON.stringify(body) + ', platform: \"INSTAGRAM\", status: \"PENDING_APPROVAL\", property: { connect: { where: { id: \"' + pid + '\" } } } }) { id platform status } }' }; })() }}",
"options": {
"timeout": 15000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 2000
}
}
}
},
{
"id": "wb000029-0029-0029-0029-000000000029",
"name": "IG Post OK?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
4760,
160
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-ig-ok",
"leftValue": "={{ ($json.errors?.length ?? 0) }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "equals"
}
}
]
},
"options": {}
}
},
{
"id": "wb000030-0030-0030-0030-000000000030",
"name": "Log IG Post Error",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
5000,
260
],
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack, input_payload) VALUES ($1, $2, $3, $4, $5, $6::jsonb)",
"options": {
"queryReplacement": "={{ ['Workflow B \u2014 AI Listing Engine', $execution.id, 'Create SocialPost \u2014 Instagram', ($json.errors?.[0]?.message ?? 'Twenty GQL error on createSocialPost IG'), '', '{\"node\":\"Create SocialPost IG\"}'] }}"
}
}
},
{
"id": "wb000031-0031-0031-0031-000000000031",
"name": "Capture IG Post ID",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
5000,
120
],
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "ig-id",
"name": "igPostId",
"value": "={{ $json.data?.createSocialPost?.id ?? '' }}",
"type": "string"
}
]
},
"options": {}
}
},
{
"id": "wb000032-0032-0032-0032-000000000032",
"name": "Create SocialPost \u2014 IG Reel (as Instagram)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
5240,
120
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"contentType": "json",
"jsonBody": "={{ (function() { const pid = $('Merge Notes').first().json.propertyId; const body = '[REEL SCRIPT]\\n' + $('Validate Outputs').first().json.reel_script; return { query: 'mutation { createSocialPost(data: { body: ' + JSON.stringify(body) + ', platform: \"INSTAGRAM\", status: \"PENDING_APPROVAL\", property: { connect: { where: { id: \"' + pid + '\" } } } }) { id platform status } }' }; })() }}",
"options": {
"timeout": 15000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 2000
}
}
}
},
{
"id": "wb000033-0033-0033-0033-000000000033",
"name": "Reel Post OK?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
5480,
120
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-reel-ok",
"leftValue": "={{ ($json.errors?.length ?? 0) }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "equals"
}
}
]
},
"options": {}
}
},
{
"id": "wb000034-0034-0034-0034-000000000034",
"name": "Log Reel Post Error",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
5720,
220
],
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack, input_payload) VALUES ($1, $2, $3, $4, $5, $6::jsonb)",
"options": {
"queryReplacement": "={{ ['Workflow B \u2014 AI Listing Engine', $execution.id, 'Create SocialPost \u2014 IG Reel', ($json.errors?.[0]?.message ?? 'Twenty GQL error on createSocialPost Reel'), '', '{\"node\":\"Create SocialPost Reel\"}'] }}"
}
}
},
{
"id": "wb000035-0035-0035-0035-000000000035",
"name": "Capture Reel Post ID",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
5720,
80
],
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "reel-id",
"name": "reelPostId",
"value": "={{ $json.data?.createSocialPost?.id ?? '' }}",
"type": "string"
}
]
},
"options": {}
}
},
{
"id": "wb000036-0036-0036-0036-000000000036",
"name": "Create SocialPost \u2014 WhatsApp Broadcast (as Facebook)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
5960,
80
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"contentType": "json",
"jsonBody": "={{ (function() { const pid = $('Merge Notes').first().json.propertyId; const body = '[WHATSAPP BROADCAST]\\n' + $('Validate Outputs').first().json.whatsapp_broadcast; return { query: 'mutation { createSocialPost(data: { body: ' + JSON.stringify(body) + ', platform: \"FACEBOOK\", status: \"PENDING_APPROVAL\", property: { connect: { where: { id: \"' + pid + '\" } } } }) { id platform status } }' }; })() }}",
"options": {
"timeout": 15000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 2000
}
}
}
},
{
"id": "wb000037-0037-0037-0037-000000000037",
"name": "WA Broadcast Post OK?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
6200,
80
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-wa-ok",
"leftValue": "={{ ($json.errors?.length ?? 0) }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "equals"
}
}
]
},
"options": {}
}
},
{
"id": "wb000038-0038-0038-0038-000000000038",
"name": "Log WA Broadcast Error",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
6440,
180
],
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack, input_payload) VALUES ($1, $2, $3, $4, $5, $6::jsonb)",
"options": {
"queryReplacement": "={{ ['Workflow B \u2014 AI Listing Engine', $execution.id, 'Create SocialPost \u2014 WhatsApp Broadcast', ($json.errors?.[0]?.message ?? 'Twenty GQL error on createSocialPost WA'), '', '{\"node\":\"Create SocialPost WA\"}'] }}"
}
}
},
{
"id": "wb000039-0039-0039-0039-000000000039",
"name": "Capture WA Post ID",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
6440,
40
],
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "wa-id",
"name": "waBroadcastPostId",
"value": "={{ $json.data?.createSocialPost?.id ?? '' }}",
"type": "string"
}
]
},
"options": {}
}
},
{
"id": "wb000040-0040-0040-0040-000000000040",
"name": "Create LISTING_REVIEW ReviewTask",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
6680,
40
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"contentType": "json",
"jsonBody": "={{ (function() { const pid = $('Merge Notes').first().json.propertyId; return { query: 'mutation { createReviewTask(data: { kind: \"LISTING_APPROVAL\", subjectProperty: { connect: { where: { id: \"' + pid + '\" } } } }) { id kind status } }' }; })() }}",
"options": {
"timeout": 15000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 2000
}
}
}
},
{
"id": "wb000041-0041-0041-0041-000000000041",
"name": "ReviewTask Created OK?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
6920,
40
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-rt-ok",
"leftValue": "={{ ($json.errors?.length ?? 0) }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "equals"
}
}
]
},
"options": {}
}
},
{
"id": "wb000042-0042-0042-0042-000000000042",
"name": "Log ReviewTask Error",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
7160,
140
],
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack, input_payload) VALUES ($1, $2, $3, $4, $5, $6::jsonb)",
"options": {
"queryReplacement": "={{ ['Workflow B \u2014 AI Listing Engine', $execution.id, 'Create LISTING_REVIEW ReviewTask', ($json.errors?.[0]?.message ?? 'Twenty GQL error on createReviewTask'), '', '{\"node\":\"Create ReviewTask\"}'] }}"
}
}
},
{
"id": "wb000043-0043-0043-0043-000000000043",
"name": "Capture ReviewTask ID",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
7160,
0
],
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "rt-id",
"name": "reviewTaskId",
"value": "={{ $json.data?.createReviewTask?.id ?? '' }}",
"type": "string"
}
]
},
"options": {}
}
},
{
"id": "wb000044-0044-0044-0044-000000000044",
"name": "Notify Agent \u2014 Telegram",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
7400,
0
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/sendMessage' }}",
"sendBody": true,
"specifyBody": "json",
"contentType": "json",
"jsonBody": "={{ (function() { const pid = $('Merge Notes').first().json.propertyId; const errs = $('Validate Outputs').first().json.validationErrors ?? []; const errNote = errs.length ? ' (' + errs.length + ' validation warning(s))' : ''; return { chat_id: $env.TELEGRAM_CHANNEL_ID, text: 'New listing draft ready for review: Property ' + pid + errNote + '. Open Twenty and review the LISTING_APPROVAL task.' }; })() }}",
"options": {
"timeout": 10000,
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 1000
}
}
}
},
{
"id": "wb000045-0045-0045-0045-000000000045",
"name": "Webhook Success Response",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
7640,
0
],
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "resp-ok",
"name": "ok",
"value": true,
"type": "boolean"
},
{
"id": "resp-pid",
"name": "propertyId",
"value": "={{ $('Merge Notes').first().json.propertyId }}",
"type": "string"
},
{
"id": "resp-fb",
"name": "fbPostId",
"value": "={{ $('Capture FB Post ID').first()?.json?.fbPostId ?? '' }}",
"type": "string"
},
{
"id": "resp-ig",
"name": "igPostId",
"value": "={{ $('Capture IG Post ID').first()?.json?.igPostId ?? '' }}",
"type": "string"
},
{
"id": "resp-reel",
"name": "reelPostId",
"value": "={{ $('Capture Reel Post ID').first()?.json?.reelPostId ?? '' }}",
"type": "string"
},
{
"id": "resp-wa",
"name": "waBroadcastPostId",
"value": "={{ $('Capture WA Post ID').first()?.json?.waBroadcastPostId ?? '' }}",
"type": "string"
},
{
"id": "resp-rt",
"name": "reviewTaskId",
"value": "={{ $('Capture ReviewTask ID').first()?.json?.reviewTaskId ?? '' }}",
"type": "string"
},
{
"id": "resp-verr",
"name": "validationErrors",
"value": "={{ $('Validate Outputs').first().json.validationErrors ?? [] }}",
"type": "array"
}
]
},
"options": {}
}
},
{
"id": "wb000046-0046-0046-0046-000000000046",
"name": "Error Trigger",
"type": "n8n-nodes-base.errorTrigger",
"typeVersion": 1,
"position": [
200,
600
],
"parameters": {}
},
{
"id": "wb000047-0047-0047-0047-000000000047",
"name": "Log B Error",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
440,
600
],
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack, input_payload) VALUES ($1, $2, $3, $4, $5, $6::jsonb)",
"options": {
"queryReplacement": "={{ ['Workflow B \u2014 AI Listing Engine', $json.execution.id, $json.execution.lastNodeExecuted ?? 'unknown', $json.execution.error.message ?? 'unknown error', ($json.execution.error.stack ? $json.execution.error.stack.split('\\n')[0] : ''), '{\"source\":\"error_trigger\"}'] }}"
}
}
}
],
"connections": {
"Listing Input Webhook": {
"main": [
[
{
"node": "Validate Inputs",
"type": "main",
"index": 0
}
]
]
},
"Validate Inputs": {
"main": [
[
{
"node": "Inputs Valid?",
"type": "main",
"index": 0
}
]
]
},
"Inputs Valid?": {
"main": [
[
{
"node": "Voice Note Present?",
"type": "main",
"index": 0
}
],
[
{
"node": "Create INPUTS_INVALID ReviewTask",
"type": "main",
"index": 0
}
]
]
},
"Create INPUTS_INVALID ReviewTask": {
"main": [
[
{
"node": "Return Invalid Inputs Response",
"type": "main",
"index": 0
}
]
]
},
"Voice Note Present?": {
"main": [
[
{
"node": "Transcribe Voice Note \u2014 Groq Whisper",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge Notes",
"type": "main",
"index": 0
}
]
]
},
"Transcribe Voice Note \u2014 Groq Whisper": {
"main": [
[
{
"node": "Whisper Succeeded?",
"type": "main",
"index": 0
}
],
[
{
"node": "Create TRANSCRIPTION_FAILED ReviewTask",
"type": "main",
"index": 0
}
]
]
},
"Whisper Succeeded?": {
"main": [
[
{
"node": "Set Transcribed Text",
"type": "main",
"index": 0
}
],
[
{
"node": "Create TRANSCRIPTION_FAILED ReviewTask",
"type": "main",
"index": 0
}
]
]
},
"Set Transcribed Text": {
"main": [
[
{
"node": "Merge Notes",
"type": "main",
"index": 0
}
]
]
},
"Create TRANSCRIPTION_FAILED ReviewTask": {
"main": [
[
{
"node": "Merge Notes",
"type": "main",
"index": 0
}
]
]
},
"Merge Notes": {
"main": [
[
{
"node": "Build OpenAI Messages",
"type": "main",
"index": 0
}
]
]
},
"Build OpenAI Messages": {
"main": [
[
{
"node": "Compose Listing \u2014 gpt-4o",
"type": "main",
"index": 0
}
]
]
},
"Compose Listing \u2014 gpt-4o": {
"main": [
[
{
"node": "Parse Listing Output",
"type": "main",
"index": 0
}
]
]
},
"Parse Listing Output": {
"main": [
[
{
"node": "Parse Succeeded?",
"type": "main",
"index": 0
}
]
]
},
"Parse Succeeded?": {
"main": [
[
{
"node": "Validate Outputs",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Parse Failure",
"type": "main",
"index": 0
}
]
]
},
"Log Parse Failure": {
"main": [
[
{
"node": "Create PARSE_FAILED ReviewTask",
"type": "main",
"index": 0
}
]
]
},
"Create PARSE_FAILED ReviewTask": {
"main": [
[
{
"node": "Return Parse Failed Response",
"type": "main",
"index": 0
}
]
]
},
"Validate Outputs": {
"main": [
[
{
"node": "Update Property Description",
"type": "main",
"index": 0
}
]
]
},
"Update Property Description": {
"main": [
[
{
"node": "Property Update OK?",
"type": "main",
"index": 0
}
]
]
},
"Property Update OK?": {
"main": [
[
{
"node": "Create SocialPost \u2014 Facebook",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Property Update Error",
"type": "main",
"index": 0
}
]
]
},
"Log Property Update Error": {
"main": [
[
{
"node": "Send Telegram \u2014 Property Error",
"type": "main",
"index": 0
}
]
]
},
"Send Telegram \u2014 Property Error": {
"main": [
[
{
"node": "Create SocialPost \u2014 Facebook",
"type": "main",
"index": 0
}
]
]
},
"Create SocialPost \u2014 Facebook": {
"main": [
[
{
"node": "FB Post OK?",
"type": "main",
"index": 0
}
]
]
},
"FB Post OK?": {
"main": [
[
{
"node": "Capture FB Post ID",
"type": "main",
"index": 0
}
],
[
{
"node": "Log FB Post Error",
"type": "main",
"index": 0
}
]
]
},
"Log FB Post Error": {
"main": [
[
{
"node": "Capture FB Post ID",
"type": "main",
"index": 0
}
]
]
},
"Capture FB Post ID": {
"main": [
[
{
"node": "Create SocialPost \u2014 Instagram",
"type": "main",
"index": 0
}
]
]
},
"Create SocialPost \u2014 Instagram": {
"main": [
[
{
"node": "IG Post OK?",
"type": "main",
"index": 0
}
]
]
},
"IG Post OK?": {
"main": [
[
{
"node": "Capture IG Post ID",
"type": "main",
"index": 0
}
],
[
{
"node": "Log IG Post Error",
"type": "main",
"index": 0
}
]
]
},
"Log IG Post Error": {
"main": [
[
{
"node": "Capture IG Post ID",
"type": "main",
"index": 0
}
]
]
},
"Capture IG Post ID": {
"main": [
[
{
"node": "Create SocialPost \u2014 IG Reel (as Instagram)",
"type": "main",
"index": 0
}
]
]
},
"Create SocialPost \u2014 IG Reel (as Instagram)": {
"main": [
[
{
"node": "Reel Post OK?",
"type": "main",
"index": 0
}
]
]
},
"Reel Post OK?": {
"main": [
[
{
"node": "Capture Reel Post ID",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Reel Post Error",
"type": "main",
"index": 0
}
]
]
},
"Log Reel Post Error": {
"main": [
[
{
"node": "Capture Reel Post ID",
"type": "main",
"index": 0
}
]
]
},
"Capture Reel Post ID": {
"main": [
[
{
"node": "Create SocialPost \u2014 WhatsApp Broadcast (as Facebook)",
"type": "main",
"index": 0
}
]
]
},
"Create SocialPost \u2014 WhatsApp Broadcast (as Facebook)": {
"main": [
[
{
"node": "WA Broadcast Post OK?",
"type": "main",
"index": 0
}
]
]
},
"WA Broadcast Post OK?": {
"main": [
[
{
"node": "Capture WA Post ID",
"typ
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
postgres
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
This workflow automates the creation of high-quality product listings by processing user inputs, including optional voice notes, into structured, SEO-optimised content ready for e-commerce platforms. It's ideal for solopreneurs or small teams managing online stores who want to streamline content generation without manual transcription or formatting hassles. The key step involves validating inputs via a webhook trigger, then using Groq's Whisper API through an httpRequest node to transcribe any voice notes accurately before storing the polished data in Postgres for easy retrieval and deployment.
Use this workflow when handling frequent listing updates with mixed text and audio inputs, such as for marketplaces like Etsy or Shopify, to save hours on repetitive tasks. Avoid it for simple text-only entries or if you lack a Postgres database, as it relies on that for persistence. Common variations include swapping Groq for another transcription service or adding AI summarisation nodes to enhance the output further.
About this workflow
Workflow B — AI Listing Engine. Uses httpRequest, postgres, errorTrigger. Webhook trigger; 47 nodes.
Source: https://github.com/jameszokah/real-estate-n8n-workflow/blob/ff6c75dac6b332a260a52efcb695ac4f3c962ed3/n8n-workflows/b-ai-listing.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.
Scraping. Uses httpRequest, postgres, @apify/n8n-nodes-apify, respondToWebhook. Webhook trigger; 61 nodes.
Mercanta — WA Bot. Uses postgres, httpRequest. Webhook trigger; 24 nodes.
This n8n workflow automates the transformation of raw text ideas into structured visual diagrams and content assets using NapkinAI.
Receive request via webhook with customer question Analyze sentiment and detect urgency using JavaScript Send urgent alerts to Slack for critical cases Search knowledge base and fetch conversation his
Bidirectional Knowledge Sync Between AppFlowy and Affine. Uses postgres, n8n-nodes-appflowy, httpRequest. Webhook trigger; 22 nodes.