AutomationFlowsMarketing & Ads › Poststack: Leady Z Dm → Google Sheets

Poststack: Leady Z Dm → Google Sheets

PostStack: Leady z DM → Google Sheets. Uses httpRequest, googleSheets. Webhook trigger; 18 nodes.

Webhook trigger★★★★☆ complexity18 nodesHTTP RequestGoogle Sheets
Marketing & Ads Trigger: Webhook Nodes: 18 Complexity: ★★★★☆ Added:

This workflow follows the Google Sheets → 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 →

Download .json
{
  "name": "PostStack: Leady z DM \u2192 Google Sheets",
  "nodes": [
    {
      "id": "sticky-overview",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -760,
        -380
      ],
      "parameters": {
        "color": 1,
        "width": 700,
        "height": 440,
        "content": "## Zapisuj leady z DM-\u00f3w Instagrama i Facebooka do Google Sheets\n\n### Jak to dzia\u0142a\n[PostStack](https://poststack.techskills.academy) - self-hostowana platforma automatyzacji social media (publikacja, auto-odpowiedzi, CRM kontakt\u00f3w) - potrafi poprosi\u0107 kontakt o email wprost w DM na Instagramie lub Facebooku (regu\u0142y auto-odpowiedzi ze zbieraniem emaila). Przy ka\u017cdym utworzeniu lub aktualizacji kontaktu PostStack wysy\u0142a podpisany webhook. Ten workflow weryfikuje nag\u0142\u00f3wek `X-PostStack-Signature`, pobiera pe\u0142ny kontakt z REST API PostStacka (event niesie tylko id kontaktu) i - je\u015bli email zosta\u0142 zebrany - dopisuje lub aktualizuje lead w arkuszu Google. Ca\u0142a reszta jest pomijana.\n\n### Konfiguracja\n1. W PostStacku otw\u00f3rz **Webhooks \u2192 Outbound webhooks**, kliknij **Add endpoint** z adresem Production URL tego workflowa (node **PostStack Webhook**) i skopiuj signing secret. Webhooki wychodz\u0105ce i dost\u0119p API to funkcje PostStack Pro.\n2. Utw\u00f3rz klucz API w PostStacku (**Settings \u2192 API keys**) ze scope `contacts:read`.\n3. Uzupe\u0142nij **Configuration (EDIT ME)**: adres PostStacka, klucz API, sekret webhooka, URL arkusza Google i nazw\u0119 zak\u0142adki.\n4. Pod\u0142\u0105cz konto Google w node **Save Lead to Google Sheets** i dodaj w zak\u0142adce wiersz nag\u0142\u00f3wkowy: `Email, Name, Phone, Platform, Username, Captured at, Contact ID`.\n5. Czerwona notka przy **Verify Signature** opisuje jedno wymagane ustawienie \u015brodowiska n8n.\n\n### Mo\u017cliwo\u015bci modyfikacji\nPodmie\u0144 node Google Sheets na Listmonk, Mailchimp, Airtable albo dowolny CRM - wszystko wcze\u015bniej zostaje bez zmian."
      }
    },
    {
      "id": "sticky-receive",
      "name": "Section: Receive & Verify",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -760,
        340
      ],
      "parameters": {
        "color": 7,
        "width": 1120,
        "height": 380,
        "content": "## Odbi\u00f3r i weryfikacja\nPrzyjmuje webhook PostStacka i sprawdza podpis HMAC, zanim zaufa danym."
      }
    },
    {
      "id": "sticky-filter",
      "name": "Section: Acknowledge & Filter",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        440,
        340
      ],
      "parameters": {
        "color": 7,
        "width": 820,
        "height": 220,
        "content": "## Potwierdzenie i filtr\nOdpowiada od razu, potem przepuszcza tylko eventy `contact.created` / `contact.updated` i buduje zapytanie do API."
      }
    },
    {
      "id": "sticky-fetch",
      "name": "Section: Fetch the Contact",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1340,
        340
      ],
      "parameters": {
        "color": 7,
        "width": 520,
        "height": 220,
        "content": "## Pobranie kontaktu\nEvent niesie tylko id kontaktu - pe\u0142ny rekord (email, imi\u0119, kana\u0142y) pobieramy z API PostStacka."
      }
    },
    {
      "id": "sticky-save",
      "name": "Section: Save the Lead",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1940,
        180
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 540,
        "content": "## Zapis leada\nKontakty ze zebranym emailem s\u0105 dopisywane lub aktualizowane w arkuszu (dopasowanie po emailu); reszta jest pomijana."
      }
    },
    {
      "id": "sticky-crypto",
      "name": "Warning: crypto module",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -130,
        370
      ],
      "parameters": {
        "color": 3,
        "width": 260,
        "height": 160,
        "content": "## Wymagane ustawienie\nTen node Code potrzebuje `require('crypto')`. Self-hosted n8n: ustaw `NODE_FUNCTION_ALLOW_BUILTIN=crypto` w \u015brodowisku n8n i zrestartuj."
      }
    },
    {
      "id": "webhook-1",
      "name": "PostStack Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -700,
        400
      ],
      "parameters": {
        "path": "poststack-dm-leads",
        "httpMethod": "POST",
        "responseMode": "responseNode",
        "options": {
          "rawBody": true
        }
      },
      "notes": "Paste this node's Production URL into PostStack \u2192 Webhooks \u2192 Outbound webhooks \u2192 Add endpoint.",
      "onError": "continueRegularOutput"
    },
    {
      "id": "config-1",
      "name": "Configuration (EDIT ME)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -400,
        400
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "postStackUrl",
              "value": "https://your-poststack.example.com",
              "type": "string"
            },
            {
              "id": "a2",
              "name": "apiKey",
              "value": "rs_live_your-api-key",
              "type": "string"
            },
            {
              "id": "a3",
              "name": "webhookSecret",
              "value": "paste-the-endpoint-signing-secret",
              "type": "string"
            },
            {
              "id": "a4",
              "name": "googleSheetUrl",
              "value": "https://docs.google.com/spreadsheets/d/your-sheet-id/edit",
              "type": "string"
            },
            {
              "id": "a5",
              "name": "sheetTabName",
              "value": "Leads",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {
          "stripBinary": false
        }
      },
      "notes": "apiKey needs the contacts:read scope. webhookSecret is the endpoint's signing secret from PostStack \u2192 Webhooks \u2192 Outbound webhooks."
    },
    {
      "id": "verify-1",
      "name": "Verify Signature",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -100,
        400
      ],
      "parameters": {
        "jsCode": "const crypto = require('crypto');\nconst config = $('Configuration (EDIT ME)').first().json;\nconst item = $input.first();\n\nconst header = (item.json.headers && item.json.headers['x-poststack-signature']) || '';\nconst rawBody = item.binary && item.binary.data\n  ? Buffer.from(item.binary.data.data, 'base64').toString('utf8')\n  : JSON.stringify(item.json.body || {});\n\n// Header format (Stripe/Svix style): t=<unix>,v1=<hex>[,v1=<hex2 during secret rotation>]\nlet t = null;\nconst sigs = [];\nfor (const part of header.split(',')) {\n  const eq = part.indexOf('=');\n  if (eq === -1) continue;\n  const key = part.slice(0, eq).trim();\n  const value = part.slice(eq + 1).trim();\n  if (key === 't') t = value;\n  else if (key === 'v1') sigs.push(value);\n}\n\nlet valid = false;\nif (t && sigs.length && config.webhookSecret) {\n  const expected = crypto.createHmac('sha256', config.webhookSecret).update(`${t}.${rawBody}`).digest('hex');\n  const withinTolerance = Math.abs(Math.floor(Date.now() / 1000) - Number(t)) <= 300;\n  valid = withinTolerance && sigs.some((v1) =>\n    v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)));\n}\n\nreturn [{ json: { valid, body: JSON.parse(rawBody) } }];"
      }
    },
    {
      "id": "if-valid",
      "name": "Signature Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        200,
        400
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "leftValue": "={{ $json.valid }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "respond-401",
      "name": "Respond Unauthorized",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        200,
        560
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "{\"success\":false,\"error\":\"invalid signature\"}",
        "options": {
          "responseCode": 401
        }
      }
    },
    {
      "id": "respond-ok",
      "name": "Respond OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        500,
        400
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "{\"status\":\"ok\"}"
      },
      "notes": "Acknowledge before processing - PostStack retries deliveries that don't get a 2xx within 10s."
    },
    {
      "id": "switch-contact",
      "name": "Contact Event?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        800,
        400
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "leftValue": "={{ $json.body.type }}",
                    "rightValue": "contact.",
                    "operator": {
                      "type": "string",
                      "operation": "startsWith"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "contact.*"
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      },
      "notes": "Matches contact.created and contact.updated. Other events (post.*, channel.*, source.*) are dropped."
    },
    {
      "id": "prepare-1",
      "name": "Prepare Contact Lookup",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        400
      ],
      "parameters": {
        "jsCode": "const config = $('Configuration (EDIT ME)').first().json;\nconst event = $input.first().json.body;\n\nconst baseUrl = String(config.postStackUrl || '').replace(/\\/+$/, '');\n\nreturn [{ json: {\n  url: `${baseUrl}/api/v1/contacts/${event.data.id}`,\n  contactId: event.data.id,\n  eventType: event.type,\n  occurredAt: event.created_at,\n} }];"
      },
      "notes": "The URL is assembled here because n8n template validation dislikes multi-part expressions in HTTP URL fields."
    },
    {
      "id": "http-contact",
      "name": "Get Contact from PostStack",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1400,
        400
      ],
      "parameters": {
        "method": "GET",
        "url": "={{ $json.url }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $('Configuration (EDIT ME)').item.json.apiKey }}"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      }
    },
    {
      "id": "if-email",
      "name": "Has Email?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1700,
        400
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "leftValue": "={{ $json.data.email }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "sheets-1",
      "name": "Save Lead to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2000,
        240
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "={{ $('Configuration (EDIT ME)').item.json.googleSheetUrl }}"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Configuration (EDIT ME)').item.json.sheetTabName }}"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Email": "={{ $json.data.email }}",
            "Name": "={{ $json.data.display_name || '' }}",
            "Phone": "={{ $json.data.phone || '' }}",
            "Platform": "={{ $json.data.contact_channels && $json.data.contact_channels[0] ? $json.data.contact_channels[0].channel.platform : '' }}",
            "Username": "={{ $json.data.contact_channels && $json.data.contact_channels[0] ? ($json.data.contact_channels[0].platform_username || '') : '' }}",
            "Captured at": "={{ $('Prepare Contact Lookup').item.json.occurredAt }}",
            "Contact ID": "={{ $json.data.id }}"
          },
          "matchingColumns": [
            "Email"
          ],
          "schema": []
        },
        "options": {}
      },
      "notes": "Upserts by Email so a contact.updated re-delivery updates the row instead of duplicating it."
    },
    {
      "id": "noop-skip",
      "name": "Skip (No Email)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        2000,
        560
      ],
      "parameters": {},
      "notes": "Contact exists but no email was captured yet - nothing to save. A later contact.updated will bring it in."
    }
  ],
  "connections": {
    "PostStack Webhook": {
      "main": [
        [
          {
            "node": "Configuration (EDIT ME)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuration (EDIT ME)": {
      "main": [
        [
          {
            "node": "Verify Signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Signature": {
      "main": [
        [
          {
            "node": "Signature Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Signature Valid?": {
      "main": [
        [
          {
            "node": "Respond OK",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Unauthorized",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond OK": {
      "main": [
        [
          {
            "node": "Contact Event?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Contact Event?": {
      "main": [
        [
          {
            "node": "Prepare Contact Lookup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Contact Lookup": {
      "main": [
        [
          {
            "node": "Get Contact from PostStack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Contact from PostStack": {
      "main": [
        [
          {
            "node": "Has Email?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Email?": {
      "main": [
        [
          {
            "node": "Save Lead to Google Sheets",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip (No Email)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": true
  }
}
Pro

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

About this workflow

PostStack: Leady z DM → Google Sheets. Uses httpRequest, googleSheets. Webhook trigger; 18 nodes.

Source: https://github.com/jurczykpawel/poststack/blob/abefa3719bb75ed21b1cb7858532ad7fe8bdeb23/examples/n8n/dm-leads-to-google-sheets-PL.json — original creator credit. Request a take-down →

More Marketing & Ads workflows → · Browse all categories →

Related workflows

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

Marketing & Ads

Automatically qualify, score, and route inbound B2B leads using GPT-4o-mini — no manual review needed.

HTTP Request, Slack, Gmail +1
Marketing & Ads

This workflow uses KlickTipp community nodes, available for self-hosted n8n instances only.

HTTP Request, Google Sheets
Marketing & Ads

This workflow receives new lead form submissions via webhook, uses Groq (OpenAI-compatible Chat Completions) to score and draft a reply, then sends the email via Gmail, logs the lead to Google Sheets

HTTP Request, Gmail, Google Sheets +1
Marketing & Ads

This n8n workflow automates the process of finding ecommerce seller leads, enriching them with product and business details, discovering company websites, and extracting contact information such as em

Google Sheets, N8N Nodes Mrscraper, HTTP Request
Marketing & Ads

This template is for B2B sales teams, SDRs, growth marketers, and founders who maintain a spreadsheet of prospects and need verified contact details -- emails and mobile numbers -- without manual rese

Google Sheets, HTTP Request