AutomationFlowsAI & RAG › AI Linkedin Poster

AI Linkedin Poster

AI LinkedIn Poster. Uses openAi, httpRequest. Webhook trigger; 34 nodes.

Webhook trigger★★★★★ complexityAI-powered34 nodesOpenAIHTTP Request
AI & RAG Trigger: Webhook Nodes: 34 Complexity: ★★★★★ AI nodes: yes Added:
AI Linkedin Poster — n8n workflow card showing OpenAI, HTTP Request integration

This workflow follows the HTTP Request → OpenAI 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 →

Download .json
{
  "name": "AI LinkedIn Poster",
  "nodes": [
    {
      "parameters": {
        "content": "## AI LinkedIn Poster v1.0\n\n`POST /webhook/linkedin-ai-post`\n\n1. Set env vars in **Configuration**\n2. Assign OpenAI + LinkedIn OAuth credentials\n3. Set `WEBHOOK_SECRET` for production\n4. Import `linkedin-ai-poster-errors.json` (optional)\n\nDocs: https://github.com/Ai-assistant-KIRA/n8n-ai-linkedin-poster",
        "height": 300,
        "width": 400,
        "color": 4
      },
      "id": "sticky-main",
      "name": "Workflow Notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -200,
        80
      ]
    },
    {
      "parameters": {
        "content": "## Env vars\n| Variable | Required |\n|----------|----------|\n| `LINKEDIN_PERSON_ID` | \u2705 |\n| `WEBHOOK_SECRET` | prod \u2705 |\n| `LINKEDIN_API_VERSION` | optional (202502) |\n\n**Credentials:** OpenAI API, LinkedIn OAuth2",
        "height": 280,
        "width": 340,
        "color": 5
      },
      "id": "sticky-config",
      "name": "Configuration Notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        300,
        60
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "linkedin-ai-post",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        400
      ],
      "notesInFlow": true,
      "notes": "Pass X-Webhook-Secret header when WEBHOOK_SECRET is set."
    },
    {
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "personId",
              "name": "personId",
              "value": "={{ $env.LINKEDIN_PERSON_ID || 'YOUR_PERSON_ID' }}",
              "type": "string"
            },
            {
              "id": "linkedinApiVersion",
              "name": "linkedinApiVersion",
              "value": "={{ $env.LINKEDIN_API_VERSION || '202502' }}",
              "type": "string"
            },
            {
              "id": "webhookSecret",
              "name": "webhookSecret",
              "value": "={{ $env.WEBHOOK_SECRET || '' }}",
              "type": "string"
            },
            {
              "id": "httpMaxRetries",
              "name": "httpMaxRetries",
              "value": 3,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "configuration",
      "name": "Configuration",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        400,
        400
      ],
      "notesInFlow": true
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const raw = $input.first().json;\nconst body = raw.body ?? raw;\nconst headers = raw.headers || {};\nconst config = $('Configuration').first().json;\n\nconst topic = (body.topic || '').trim();\nconst caption = (body.caption || body.text || '').trim();\nconst imagePrompt = (body.image_prompt || body.imagePrompt || '').trim();\nconst imageUrl = (body.image_url || body.imageUrl || '').trim();\nconst imageBase64 = body.image_base64 || body.imageBase64 || null;\nconst tone = (body.tone || 'professional').trim();\nconst hashtags = Array.isArray(body.hashtags) ? body.hashtags : [];\nconst dryRun = Boolean(body.dry_run ?? body.dryRun ?? false);\nconst publish = body.publish !== false;\nconst forceImage = Boolean(body.generate_image ?? body.generateImage ?? false);\n\nconst secret = (config.webhookSecret || '').trim();\nif (secret && secret !== 'DISABLED') {\n  const provided = (\n    headers['x-webhook-secret'] ||\n    headers['X-Webhook-Secret'] ||\n    body.webhook_secret ||\n    body.webhookSecret ||\n    ''\n  ).trim();\n  if (provided !== secret) {\n    return [{\n      json: {\n        unauthorized: true,\n        success: false,\n        error: 'Invalid or missing X-Webhook-Secret header',\n        hint: 'Set WEBHOOK_SECRET in env and pass header X-Webhook-Secret on every request.',\n      },\n    }];\n  }\n}\n\nconst hasImageUrl = !!imageUrl;\nconst hasImageBase64 = !!imageBase64;\nconst skipAiImage = dryRun && !forceImage;\nconst needsAiImage = !hasImageUrl && !hasImageBase64 && !skipAiImage && (imagePrompt || topic || forceImage);\nconst needsCaption = !caption && (topic || body.generate_caption);\nconst shouldPost = !dryRun && publish;\nconst captionOnlyPreview = dryRun && !hasImageUrl && !hasImageBase64 && !needsAiImage;\n\nreturn [{\n  json: {\n    topic,\n    caption,\n    imagePrompt: imagePrompt || (topic ? `Professional LinkedIn infographic about: ${topic}. Clean, modern, high-contrast, no text overlays.` : ''),\n    imageUrl,\n    imageBase64,\n    tone,\n    hashtags,\n    dryRun,\n    publish,\n    shouldPost,\n    forceImage,\n    needsCaption,\n    hasImageUrl,\n    hasImageBase64,\n    needsAiImage,\n    captionOnlyPreview,\n    personId: config.personId,\n    linkedinApiVersion: config.linkedinApiVersion,\n    httpMaxRetries: config.httpMaxRetries,\n    mediaTitle: (body.media_title || body.mediaTitle || topic || 'AI Generated Post').slice(0, 200),\n    visibility: body.visibility || 'PUBLIC',\n  },\n}];"
      },
      "id": "parse",
      "name": "Parse Webhook Input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        580,
        400
      ],
      "onError": "continueErrorOutput",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "unauth",
              "leftValue": "={{ $json.unauthorized }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-unauthorized",
      "name": "Unauthorized?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        760,
        400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c",
              "leftValue": "={{ $json.needsCaption }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-needs-caption",
      "name": "Needs Caption?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        960,
        400
      ]
    },
    {
      "parameters": {
        "resource": "chat",
        "operation": "complete",
        "chatModel": "gpt-4o-mini",
        "prompt": {
          "messages": [
            {
              "role": "system",
              "content": "You write high-performing LinkedIn posts. Output ONLY the post text \u2014 no titles, no markdown fences. Use short paragraphs, 1-2 emojis max, 3-5 relevant hashtags at the end."
            },
            {
              "role": "user",
              "content": "=Write a LinkedIn post.\n\nTopic: {{ $json.topic || 'automation and AI productivity' }}\nTone: {{ $json.tone }}\nExtra hashtags: {{ $json.hashtags.join(' ') }}\n\nKeep under 1300 characters. Start with a strong hook."
            }
          ]
        },
        "options": {
          "temperature": 0.7,
          "maxTokens": 800
        }
      },
      "id": "openai-caption",
      "name": "AI Generate Caption",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        1180,
        280
      ],
      "onError": "continueErrorOutput",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const parsed = $('Parse Webhook Input').first().json;\nconst ai = $input.first().json;\nconst generated = ai.message?.content || ai.text || ai.output || '';\nif (!parsed.caption && !generated.trim()) throw new Error('AI caption generation returned empty text.');\nreturn [{ json: { ...parsed, caption: (parsed.caption || generated).trim(), captionSource: parsed.caption ? 'provided' : 'ai-generated' } }];"
      },
      "id": "merge-caption",
      "name": "Merge Caption",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1400,
        280
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const p = $input.first().json; return [{ json: { ...p, captionSource: 'provided' } }];"
      },
      "id": "pass-caption",
      "name": "Use Provided Caption",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1180,
        520
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      },
      "id": "merge-branches",
      "name": "Merge Caption Branches",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        1620,
        400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "b",
              "leftValue": "={{ $json.hasImageBase64 }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-base64",
      "name": "Has Base64 Image?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1840,
        400
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const item = $input.first().json;\nconst raw = item.imageBase64 || '';\nconst dataUriMatch = raw.match(/^data:(image\\/[a-zA-Z0-9.+-]+);base64,(.+)$/);\nconst mimeType = dataUriMatch ? dataUriMatch[1] : 'image/jpeg';\nconst b64 = dataUriMatch ? dataUriMatch[2] : raw.replace(/^data:image\\/\\w+;base64,/, '');\nif (!b64) throw new Error('image_base64 is empty');\nconst ext = mimeType.includes('png') ? 'png' : 'jpg';\nreturn [{\n  json: { ...item, imageSource: 'base64' },\n  binary: {\n    data: {\n      data: b64,\n      mimeType,\n      fileName: `linkedin-post.${ext}`,\n    },\n  },\n}];"
      },
      "id": "decode-base64",
      "name": "Decode Base64 Image",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2060,
        280
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "u",
              "leftValue": "={{ $json.hasImageUrl }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-url",
      "name": "Has Image URL?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2060,
        480
      ]
    },
    {
      "parameters": {
        "url": "={{ $json.imageUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "id": "download-url",
      "name": "Download Image URL",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2280,
        400
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "a",
              "leftValue": "={{ $json.needsAiImage }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-ai-image",
      "name": "Needs AI Image?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2280,
        580
      ]
    },
    {
      "parameters": {
        "resource": "image",
        "operation": "generate",
        "prompt": "={{ $json.imagePrompt }}",
        "options": {
          "size": "1024x1024",
          "style": "vivid"
        }
      },
      "id": "openai-image",
      "name": "AI Generate Image",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        2500,
        500
      ],
      "onError": "continueErrorOutput",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "={{ $json.data[0].url || $json.url }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "id": "download-ai",
      "name": "Download AI Image",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2720,
        500
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "co",
              "leftValue": "={{ $json.captionOnlyPreview }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-caption-only",
      "name": "Caption Only Preview?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2500,
        700
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const item = $input.first().json;\nif (!item.caption && !item.topic) {\n  throw new Error('Preview requires a topic or caption.');\n}\nreturn [{\n  json: {\n    success: true,\n    dryRun: true,\n    captionOnly: true,\n    caption: item.caption || `(caption will be AI-generated from topic: ${item.topic})`,\n    captionSource: item.captionSource || (item.caption ? 'provided' : 'ai-generated'),\n    imageSource: null,\n    imageSkipped: true,\n    mediaTitle: item.mediaTitle,\n    linkedinApiVersion: item.linkedinApiVersion,\n    message: 'Caption-only preview \u2014 set generate_image: true to preview with AI image, or dry_run: false to publish.',\n  },\n}];"
      },
      "id": "caption-preview",
      "name": "Build Caption Preview",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2720,
        820
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const content = $('Merge Caption Branches').first().json;\nconst input = $input.first();\nconst binary = input.binary;\n\nif (!binary) {\n  throw new Error('No image binary available. Provide image_url, image_base64, or enable AI image generation.');\n}\n\nconst binaryKey = Object.keys(binary).find((k) => binary[k]?.data) || 'data';\nconst bin = binary[binaryKey];\nif (!bin?.data) throw new Error('Image binary is empty.');\n\nlet imageContentType = bin.mimeType || 'application/octet-stream';\nconst fileName = bin.fileName || 'linkedin-post.jpg';\nif (!imageContentType || imageContentType === 'application/octet-stream') {\n  const ext = (fileName.split('.').pop() || 'jpg').toLowerCase();\n  const map = { jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif', webp: 'image/webp' };\n  imageContentType = map[ext] || 'image/jpeg';\n}\n\nconst linkedinAccepted = ['image/jpeg', 'image/png'];\nconst imageFormatWarning = linkedinAccepted.includes(imageContentType)\n  ? null\n  : `LinkedIn accepts JPEG/PNG only. Detected ${imageContentType}. Add a conversion step or override to image/jpeg.`;\n\nconst personId = (content.personId || '').trim();\nif (!personId || personId === 'YOUR_PERSON_ID') {\n  throw new Error('Set LINKEDIN_PERSON_ID in docker-compose/.env or the Configuration node.');\n}\n\nconst imageSource = content.imageSource || (content.imageUrl ? 'url' : content.imageBase64 ? 'base64' : 'ai-generated');\n\nreturn [{\n  json: {\n    ...content,\n    personUrn: `urn:li:person:${personId}`,\n    imageFileName: fileName,\n    imageContentType,\n    imageFormatWarning,\n    imageSource,\n    linkedinApiVersion: content.linkedinApiVersion || '202502',\n  },\n  binary: { data: bin },\n}];"
      },
      "id": "prepare",
      "name": "Prepare LinkedIn Payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2940,
        400
      ],
      "onError": "continueErrorOutput",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "sp",
              "leftValue": "={{ $json.shouldPost }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-should-post",
      "name": "Should Post?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        3160,
        400
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json.captionOnly ? $json : { success: true, dryRun: true, caption: $json.caption, captionSource: $json.captionSource, imageSource: $json.imageSource, imageContentType: $json.imageContentType, imageFormatWarning: $json.imageFormatWarning, mediaTitle: $json.mediaTitle, linkedinApiVersion: $json.linkedinApiVersion, message: 'Preview only \u2014 set dry_run: false and publish: true to post.' } }}",
        "options": {
          "responseCode": 200
        }
      },
      "id": "respond-preview",
      "name": "Respond Preview",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        3380,
        280
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.linkedin.com/rest/images?action=initializeUpload",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "linkedInOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "LinkedIn-Version",
              "value": "={{ $json.linkedinApiVersion || '202502' }}"
            },
            {
              "name": "X-Restli-Protocol-Version",
              "value": "2.0.0"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ initializeUploadRequest: { owner: $json.personUrn } }) }}",
        "options": {}
      },
      "id": "linkedin-init",
      "name": "LinkedIn Init Upload",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3380,
        520
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000,
      "onError": "continueErrorOutput",
      "credentials": {
        "linkedInOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const prepared = $('Prepare LinkedIn Payload').first();\nconst init = $input.first();\nconst value = init.json.value || init.json.body?.value || init.json;\nif (!value?.uploadUrl || !value?.image) throw new Error(`LinkedIn init upload failed: ${JSON.stringify(init.json).slice(0, 500)}`);\nreturn [{ json: { ...prepared.json, uploadUrl: value.uploadUrl, imageUrn: value.image }, binary: prepared.binary }];"
      },
      "id": "merge-upload",
      "name": "Merge Upload Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3600,
        520
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "={{ $json.uploadUrl }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "={{ $json.imageContentType || 'image/jpeg' }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "binaryData",
        "inputDataFieldName": "data",
        "options": {}
      },
      "id": "linkedin-upload",
      "name": "LinkedIn Upload Image",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3820,
        520
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.linkedin.com/rest/posts",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "linkedInOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "LinkedIn-Version",
              "value": "={{ $('Merge Upload Data').first().json.linkedinApiVersion || '202502' }}"
            },
            {
              "name": "X-Restli-Protocol-Version",
              "value": "2.0.0"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ author: $('Merge Upload Data').first().json.personUrn, commentary: $('Merge Upload Data').first().json.caption, visibility: $('Merge Upload Data').first().json.visibility, distribution: { feedDistribution: 'MAIN_FEED', thirdPartyDistributionChannels: [] }, lifecycleState: 'PUBLISHED', content: { media: { title: $('Merge Upload Data').first().json.mediaTitle, id: $('Merge Upload Data').first().json.imageUrn } } }) }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        }
      },
      "id": "linkedin-post",
      "name": "LinkedIn Create Post",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        4040,
        520
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000,
      "onError": "continueErrorOutput",
      "credentials": {
        "linkedInOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const merged = $('Merge Upload Data').first().json;\nconst post = $input.first();\nconst headers = post.json.headers || {};\nconst postUrn = headers['x-restli-id'] || headers['X-Restli-Id'];\nif (!postUrn) throw new Error('LinkedIn create post missing x-restli-id header');\nreturn [{ json: { success: true, dryRun: false, postUrn, shareUrl: `https://www.linkedin.com/feed/update/${encodeURIComponent(postUrn)}/`, caption: merged.caption, captionSource: merged.captionSource, imageSource: merged.imageSource, imageContentType: merged.imageContentType, mediaTitle: merged.mediaTitle, linkedinApiVersion: merged.linkedinApiVersion, api: `linkedin-rest-v${merged.linkedinApiVersion || '202502'}` } }];"
      },
      "id": "finalize",
      "name": "Finalize Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4260,
        520
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {
          "responseCode": 200
        }
      },
      "id": "respond-success",
      "name": "Respond Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        4480,
        520
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const item = $input.first();\nconst j = item.json || {};\nif (j.unauthorized) {\n  return [{ json: { ...j, statusCode: 401 } }];\n}\nconst executionError = j.error || j;\nconst message =\n  executionError.message ||\n  executionError.description ||\n  j.message ||\n  (typeof executionError === 'string' ? executionError : null) ||\n  'Workflow execution failed';\nreturn [{\n  json: {\n    success: false,\n    error: String(message).slice(0, 1000),\n    failedNode: String(j.$node?.name || j.node || 'unknown'),\n    hint: 'See docs/troubleshooting.md \u2014 verify LINKEDIN_PERSON_ID, OAuth, WEBHOOK_SECRET, LINKEDIN_API_VERSION.',\n    docs: 'https://github.com/Ai-assistant-KIRA/n8n-ai-linkedin-poster/blob/main/docs/troubleshooting.md',\n    statusCode: j.statusCode || 500,\n  },\n}];"
      },
      "id": "format-error",
      "name": "Format Error Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4260,
        720
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "e401",
              "leftValue": "={{ $json.statusCode }}",
              "rightValue": 401,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-error-code",
      "name": "Error Status?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        4480,
        720
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {
          "responseCode": 500
        }
      },
      "id": "respond-error",
      "name": "Respond Error",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        4700,
        820
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {
          "responseCode": 401
        }
      },
      "id": "respond-unauthorized",
      "name": "Respond Unauthorized",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        4700,
        620
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "throw new Error('Image required to publish. Provide image_url, image_base64, or enable AI image generation (generate_image: true).');"
      },
      "id": "format-missing-image",
      "name": "Missing Image Error",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2720,
        700
      ],
      "onError": "continueErrorOutput"
    }
  ],
  "connections": {
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuration": {
      "main": [
        [
          {
            "node": "Parse Webhook Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Webhook Input": {
      "main": [
        [
          {
            "node": "Unauthorized?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Unauthorized?": {
      "main": [
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Needs Caption?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs Caption?": {
      "main": [
        [
          {
            "node": "AI Generate Caption",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Use Provided Caption",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Generate Caption": {
      "main": [
        [
          {
            "node": "Merge Caption",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Caption": {
      "main": [
        [
          {
            "node": "Merge Caption Branches",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Use Provided Caption": {
      "main": [
        [
          {
            "node": "Merge Caption Branches",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge Caption Branches": {
      "main": [
        [
          {
            "node": "Has Base64 Image?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Base64 Image?": {
      "main": [
        [
          {
            "node": "Decode Base64 Image",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Has Image URL?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decode Base64 Image": {
      "main": [
        [
          {
            "node": "Prepare LinkedIn Payload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Image URL?": {
      "main": [
        [
          {
            "node": "Download Image URL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Needs AI Image?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Image URL": {
      "main": [
        [
          {
            "node": "Prepare LinkedIn Payload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs AI Image?": {
      "main": [
        [
          {
            "node": "AI Generate Image",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Caption Only Preview?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Generate Image": {
      "main": [
        [
          {
            "node": "Download AI Image",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download AI Image": {
      "main": [
        [
          {
            "node": "Prepare LinkedIn Payload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Caption Only Preview?": {
      "main": [
        [
          {
            "node": "Build Caption Preview",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Missing Image Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Caption Preview": {
      "main": [
        [
          {
            "node": "Respond Preview",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Missing Image Error": {
      "main": [
        [],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare LinkedIn Payload": {
      "main": [
        [
          {
            "node": "Should Post?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Should Post?": {
      "main": [
        [
          {
            "node": "LinkedIn Init Upload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Preview",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn Init Upload": {
      "main": [
        [
          {
            "node": "Merge Upload Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Upload Data": {
      "main": [
        [
          {
            "node": "LinkedIn Upload Image",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn Upload Image": {
      "main": [
        [
          {
            "node": "LinkedIn Create Post",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn Create Post": {
      "main": [
        [
          {
            "node": "Finalize Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Finalize Result": {
      "main": [
        [
          {
            "node": "Respond Success",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Error Response": {
      "main": [
        [
          {
            "node": "Error Status?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Status?": {
      "main": [
        [
          {
            "node": "Respond Unauthorized",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "errorWorkflow": "",
    "callerPolicy": "workflowsFromSameOwner"
  },
  "active": false,
  "versionId": "1.2.0",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": [
    {
      "name": "linkedin"
    },
    {
      "name": "ai"
    },
    {
      "name": "social-media"
    }
  ]
}

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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

AI LinkedIn Poster. Uses openAi, httpRequest. Webhook trigger; 34 nodes.

Source: https://github.com/Ai-assistant-KIRA/n8n-ai-linkedin-poster/blob/main/workflows/linkedin-ai-poster.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This workflow captures new leads via webhook, enriches and scores them with Apollo and Google Gemini, logs everything in Google Sheets, and automates outreach, reply handling, follow-ups, meeting prep

HTTP Request, Google Gemini, Google Sheets +3
AI & RAG

This powerful n8n automation workflow is designed to execute advanced B2B lead enrichment and hyper-personalization for cold email outreach. By orchestrating a complex chain of data scraping, AI analy

OpenAI, HTTP Request, Airtable
AI & RAG

Propulsar — Content Engine v3. Uses openAi, httpRequest, googleSheets. Webhook trigger; 73 nodes.

OpenAI, HTTP Request, Google Sheets
AI & RAG

Eu Clara – Funil Kiwify Completo. Uses postgres, openAi, httpRequest, gmail. Webhook trigger; 70 nodes.

Postgres, OpenAI, HTTP Request +1
AI & RAG

Postagem Redes — Portal: Ações. Uses openAi, googleGemini, ollama, httpRequest. Webhook trigger; 58 nodes.

OpenAI, Google Gemini, Ollama +4