{
  "name": "Webhook to PostSider",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "postsider-ai",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "00000000-0000-4000-8000-000000000057",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "postsider-ai"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "00000000-0000-4000-8000-000000000058",
              "name": "idea",
              "value": "={{ $json.body.topic || $json.body.idea || $json.body.text }}",
              "type": "string"
            },
            {
              "id": "00000000-0000-4000-8000-000000000059",
              "name": "tone",
              "value": "={{ $json.body.tone || 'confident and friendly' }}",
              "type": "string"
            },
            {
              "id": "00000000-0000-4000-8000-000000000060",
              "name": "mode",
              "value": "={{ $json.body.mode || 'approval' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "00000000-0000-4000-8000-000000000061",
      "name": "1. Post idea",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "00000000-0000-4000-8000-000000000062",
              "leftValue": "={{ ($json.mode || '').toLowerCase() }}",
              "rightValue": "approval",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "00000000-0000-4000-8000-000000000063",
      "name": "2. Mode?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://api.postsider.com/public/v1/find-slot/{{ $json.channelId }}"
      },
      "id": "00000000-0000-4000-8000-000000000064",
      "name": "3a. Find best slot",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        -180
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "1",
          "name": "PostSider API Key"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"gpt-4o-mini\",\n  \"temperature\": 0.8,\n  \"messages\": [\n    { \"role\": \"system\", \"content\": \"You write social media posts for the requested platform. Rules: Match the platform tone and limits. If the topic is a simple command like \"post this\", write a short clean post. Respond with the post text only.\" },\n    { \"role\": \"user\", \"content\": \"Topic: {{ $('1. Post idea').first().json.idea }}. Tone: {{ $('1. Post idea').first().json.tone }}. Write the post.\" }\n  ]\n}"
      },
      "id": "00000000-0000-4000-8000-000000000065",
      "name": "4a. Generate post (AI)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        -180
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "2",
          "name": "OpenAI API Key"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const row = $('1. Post idea').first().json; // idea, tone, mode\nconst channelId = $('Webhook').first().json.body.channelId;\nconst slot = $('3a. Find best slot').first().json; // { date } UTC, append 'Z'\nconst ai = $input.first().json;\nconst content = (ai.choices?.[0]?.message?.content || '').trim();\nreturn [{\n  json: {\n    body: {\n      type: 'draft',\n      date: slot.date + 'Z',\n      shortLink: false,\n      tags: [],\n      posts: [{\n        integration: { id: channelId },\n        value: [{ content, image: [] }],\n        settings: {}\n      }]\n    }\n  }\n}];"
      },
      "id": "00000000-0000-4000-8000-000000000066",
      "name": "5a. Build draft",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        -180
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.postsider.com/public/v1/posts",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Idempotency-Key",
              "value": "=ps-{{ $now.toISO() }}"
            }
          ]
        }
      },
      "id": "00000000-0000-4000-8000-000000000067",
      "name": "6a. Create draft",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        -180
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "1",
          "name": "PostSider API Key"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.postsider.com/public/v1/posts/{{ $json.postId }}/request-approval"
      },
      "id": "00000000-0000-4000-8000-000000000068",
      "name": "7a. Request approval",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1540,
        -180
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "1",
          "name": "PostSider API Key"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"gpt-4o-mini\",\n  \"temperature\": 0.8,\n  \"messages\": [\n    { \"role\": \"system\", \"content\": \"You write social media posts for the requested platform. Rules: Match the platform tone and limits. If the topic is a simple command like \"post this\", write a short clean post. Respond with the post text only.\" },\n    { \"role\": \"user\", \"content\": \"Topic: {{ $('1. Post idea').first().json.idea }}. Tone: {{ $('1. Post idea').first().json.tone }}. Write the post.\" }\n  ]\n}"
      },
      "id": "00000000-0000-4000-8000-000000000069",
      "name": "4b. Generate post (AI)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        180
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "2",
          "name": "OpenAI API Key"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const row = $('1. Post idea').first().json; // idea, tone, mode\nconst channelId = $('Webhook').first().json.body.channelId;\nconst ai = $input.first().json;\nconst content = (ai.choices?.[0]?.message?.content || '').trim();\nreturn [{\n  json: {\n    body: {\n      type: 'now',\n      date: '',\n      shortLink: false,\n      tags: [],\n      posts: [{\n        integration: { id: channelId },\n        value: [{ content, image: [] }],\n        settings: {}\n      }]\n    }\n  }\n}];"
      },
      "id": "00000000-0000-4000-8000-000000000070",
      "name": "5b. Build post",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        180
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.postsider.com/public/v1/posts",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Idempotency-Key",
              "value": "=ps-{{ $now.toISO() }}"
            }
          ]
        }
      },
      "id": "00000000-0000-4000-8000-000000000071",
      "name": "6b. Publish now",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        180
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "1",
          "name": "PostSider API Key"
        }
      }
    },
    {
      "parameters": {
        "content": "WHAT THIS DOES\nA webhook endpoint that turns any incoming JSON into an AI-written post published through PostSider. Send {\"topic\": \"...\", \"channelId\": \"...\", \"mode\": \"approval\"} from Zapier, Make, your backend or an AI agent.\n\nSETUP (once, about 4 minutes)\n1. PostSider API key: app.postsider.com > Settings > API > Create key (ps_...).\n2. n8n credentials (Header Auth): \"PostSider API Key\" (Authorization = ps_...) and \"OpenAI API Key\" (Authorization = Bearer sk-...).\n3. Activate the workflow; the Webhook node shows the production URL to call.\n\nHOW TO RUN\n1. POST JSON to the webhook URL: {\"topic\": \"Our new n8n workflows\", \"channelId\": \"[YOUR CHANNEL ID]\", \"tone\": \"confident\", \"mode\": \"approval\"}.\n2. mode = \"approval\" creates a draft and requests approval; mode = \"now\" publishes immediately.\n3. channelId is taken from the incoming JSON, so one webhook serves any channel.\n\nSELF-HOSTED POSTSIDER\nReplace api.postsider.com with your domain; add /api if your install serves the API under /api.",
        "height": 640,
        "width": 620,
        "color": 3
      },
      "id": "00000000-0000-4000-8000-000000000072",
      "name": "How to use",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -460
      ]
    },
    {
      "parameters": {
        "content": "SETUP STEP BY STEP\nFollow in order. Do this once per workflow.\n\nSTEP 1 - Create the n8n credentials (Header Auth):\n  a. \"PostSider API Key\": Name = Authorization, Value = your ps_ key (app.postsider.com > Settings > API > Create key).\n  b. \"OpenAI API Key\": Name = Authorization, Value = Bearer sk-... (platform.openai.com).\n\nSTEP 2 - Select the \"PostSider API Key\" credential on:\n  - \"3a. Find best slot\"\n  - \"6a. Create draft\"\n  - \"7a. Request approval\"\n  - \"6b. Publish now\"\n\nSTEP 3 - Select the \"OpenAI API Key\" credential on:\n  - \"4a. Generate post (AI)\"\n  - \"4b. Generate post (AI)\"\n\nSTEP 4 - Activate the workflow:\n  Toggle Active on. The \"Webhook\" node shows the production URL.\n\nSTEP 5 - Test:\n  POST JSON to the webhook URL: {\"topic\": \"Your topic\", \"channelId\": \"[YOUR CHANNEL ID]\", \"mode\": \"approval\"}.\n  The channelId comes from the JSON, so the same webhook serves any channel. mode \"approval\" queues the draft, mode \"now\" publishes immediately.\n\nNOTE\nSelf-hosted PostSider? Replace api.postsider.com with your domain (add /api if your install serves the API under /api).",
        "height": 700,
        "width": 640,
        "color": 3
      },
      "id": "00000000-0000-4000-8000-000000000073",
      "name": "Setup step by step",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -1180
      ]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "1. Post idea",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Post idea": {
      "main": [
        [
          {
            "node": "2. Mode?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Mode?": {
      "main": [
        [
          {
            "node": "3a. Find best slot",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "4b. Generate post (AI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3a. Find best slot": {
      "main": [
        [
          {
            "node": "4a. Generate post (AI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4a. Generate post (AI)": {
      "main": [
        [
          {
            "node": "5a. Build draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5a. Build draft": {
      "main": [
        [
          {
            "node": "6a. Create draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6a. Create draft": {
      "main": [
        [
          {
            "node": "7a. Request approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4b. Generate post (AI)": {
      "main": [
        [
          {
            "node": "5b. Build post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5b. Build post": {
      "main": [
        [
          {
            "node": "6b. Publish now",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "active": false,
  "tags": [
    "PostSider",
    "Webhook",
    "AI",
    "API"
  ]
}