惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Proofpoint News Feed
L
Lohrmann on Cybersecurity
S
Secure Thoughts
Attack and Defense Labs
Attack and Defense Labs
人人都是产品经理
人人都是产品经理
Stack Overflow Blog
Stack Overflow Blog
W
WeLiveSecurity
O
OpenAI News
SecWiki News
SecWiki News
博客园 - Franky
NISL@THU
NISL@THU
Microsoft Azure Blog
Microsoft Azure Blog
T
Tor Project blog
Microsoft Security Blog
Microsoft Security Blog
aimingoo的专栏
aimingoo的专栏
Security Latest
Security Latest
H
Hacker News: Front Page
Google Online Security Blog
Google Online Security Blog
P
Privacy & Cybersecurity Law Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Darknet – Hacking Tools, Hacker News & Cyber Security
月光博客
月光博客
李成银的技术随笔
Spread Privacy
Spread Privacy
F
Full Disclosure
F
Fortinet All Blogs
T
The Exploit Database - CXSecurity.com
Vercel News
Vercel News
AWS News Blog
AWS News Blog
WordPress大学
WordPress大学
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
V
Visual Studio Blog
J
Java Code Geeks
博客园 - 三生石上(FineUI控件)
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
博客园 - 司徒正美
Engineering at Meta
Engineering at Meta
Last Week in AI
Last Week in AI
P
Palo Alto Networks Blog
宝玉的分享
宝玉的分享
T
True Tiger Recordings
N
News and Events Feed by Topic
酷 壳 – CoolShell
酷 壳 – CoolShell
Cisco Talos Blog
Cisco Talos Blog
N
News | PayPal Newsroom
S
SegmentFault 最新的问题
Jina AI
Jina AI

DEV Community

If your AI initiative is pending for 6 months, the bottleneck is probably not technology Hermes Agent Under the Hood: The Open-Source Runtime for Autonomous AI Systems Expert Systems -The AI That Existed Before AI Was Cool AI-generated accessibility, an update — frontier models still fail, but skills change the game My HTML Learning Journey 🚀 The Day PayPal Failed and the Rust Rewrite Saved the Product Launch BrontoScope: AI-Powered Error Investigations The job of an AI engineer inside a 40-person company is not what most CEOs think it is Building a Clinical Speech-Therapy App With a Real SLP: 4 Lessons From PhoenixSteps 7 overlooked .Net features How Stripe Took 48 Hours and 3 API Calls to Break My Freelance Income Stream in Lagos Pretty normal Both Camps in the 'Left Behind' Argument Are Right About Each Other Flutter MCP Toolkit v3 Google Just Shipped Gemini 3.5 Flash. Here's What Developers Actually Need to Know. 🔐 Working with Private Symfony Recipes Rate limiting in web apps: what to protect before picking a library Rate limiting en aplicaciones web: qué proteger antes de elegir una librería What Are Lakehouse Catalogs? The Role of Catalogs in Apache Iceberg What It Really Takes to Become a Senior Software Engineer Microservices Were Never About Technology JS Crime Scene: The Misleading Array Project-as-code for a Directus v9 backend When the API literally burned your database after a typo COOKIES DPRK Hacking Trends 2026: AI‑Powered Supply Chain and Developer Environment Attacks Phone control for AI coding sessions is not a tiny terminal PayPal and Crypto Are Not Equals: How I Built a Gumroad Alternative for Restricted Countries Exploring Tech as a Content Writer I Raised Gemma 4's Token Cap. The Dense Model Stopped Refusing. React Server Components Don't Make Your App Fast by Default Multi-Stage Builds for a Next.js App — Reduce Image Size by 70% I Built a Chrome Extension That Teaches Vocabulary While You Browse Why I Walked Back from Next.js and RSC to a Plain SPA and a Separate Backend NeuralPocket: Private On-Device AI with Gemma 4 — Android & Web Github Speckit: Revolucionando o Desenvolvimento com SDD Cloud Cost Elasticity I Built a Payment System for Bangladesh—Heres Why Stripe Failed Us Polyglot Persistence in Microservices: Choosing the Right Database for Each Service Centralized Authentication for a Multi-Brand Laravel Ecosystem How I made a perfect recording button. Simple yet complex thing. Mumbli – my personal Wispr Flow Getting Paid Should Not Be a Geopolitical Nightmare: My NOWPayments Integration Story Four Layers of Validation in Kubernetes with Claude Code Prompt Flow — a visual side project for flow design, trace, and integration steps (looking for feedback) AI Citation Registry: Temporal Gaps in Government Publishing Cycles ShowDev: I built a 100% local, zero-upload PDF editor using WebAssembly JavaC Written by an AI Pipeline, Verified by Three Models. Is It Slop? Part1 Vulkan: Drawing Triangle 1 Why I Stopped Using useEffect to Sync State — and What I Use Instead Por qué dejé de usar useEffect para sincronizar estado y qué uso ahora Migrating a Long-Running WordPress Site to Payload CMS (And All The Chaos That Came With It) Hidden Partitioning: How Iceberg Eliminates Accidental Full Table Scans Azure DevOps Structure Explained: Organizations, Projects, and Repos Without the Mess A Simple React Hook for localStorage State, Expiry, and Sync I sold you on /scratchpad. Then I migrated to /note. Fixing WSL Errors on Windows 11 Your app is not Netflix. Stop building like it is. Resolving inter-service communication issue I built an email cleaner. CSV parsing took longer than the actual validators. How I Would Learn Full-Stack Development in 2026 If I Started From Zero Partition Evolution: Change Your Partitioning Without Rewriting Data What Google Play's I/O 2026 Updates Look Like From a Solo Indie Puzzle Developer Forgetting the Myth of "Ease of Integration" When Selling Digital Products with Bitcoin My 4-Step Regex Debugging Workflow (That Actually Saves Time) Stop Scraping Betting Sites: How to Build a Real-Time Sports Tracker in Python Civic Identity and Responsibility in Modern Democracy OLTP vs OLAP Are binaries really executable code ? The lie of the 80%: why software progress charts don't work What a Datacenter in Space Actually Buys You: Three Server Racks Is AI Actually Citing Your Site? How to Measure What Google Rankings Can't Accessibility - This looks like a job for a developer advocate! I built a Mac app that turns web pages into live widgets How to Teach Source Evaluation When Your Students Use ChatGPT More Context Does Not Mean More Trust RAG Series (24): Code RAG — Teaching AI to Understand Your Codebase Past the JVM Design decisions behind my “Irregular German Verbs” iOS app WordPress 7.0 "Armstrong" Is Live — Post-Release Deep Dive 🎺 Performance and Apache Iceberg's Metadata I Shipped a Bug to Production That Cost Us 3 Hours of Downtime 程序人生:在代码与时间之间 The Wrong Way to Think About XRPL Event Infrastructure What I Learned About MND, Voice Banking, and Why Assistive Tech Is Personal $1.50/Month Email Infrastructure That Beats Your $20 SendGrid Plan Cloud Unit Economics: The Metrics DevOps and FinOps Teams Actually Need Bypassing Payment Platform Restrictions Was The Best Decision I Ever Made For My Digital Product Business The Hidden Life of a Container: A Complete Lifecycle When a port is already in use, there is no interactive way to find it — so I built `port-peek` Como Sumir com o Barulho do Teclado Mecânico no Ubuntu Usando o NoiseTorch Google I/O 2026 dropped a bomb on Android tooling, and nobody's talking about it (or maybe they are 😅) Mentoring Junior Developers: What Actually Works How I Prevented Claude Code from Breaking My Architecture with 18 Tests That Run in 0.4 Seconds I Controlled an ESP32 Drone Using Only My Voice vite HMR is silently the reason ur laptop fan wont stop AI Agents Security for Developers: Don't Let Your Agents Become a Liability Single List Keyboard Handling 9 SaaS development companies worth knowing (a technical look)
Google Sheets CRM: 4 Ways I've Actually Done It (with Apps Script Code)
TrackStack · 2026-05-21 · via DEV Community

Every SMB I've worked with treats a Google Sheet as their first real database — leads, deals, inventory, campaign tracking. Then they buy a CRM and ask "how do we get the sheet into HubSpot/Pipedrive/Zoho?" There are four ways I've actually shipped this, and which one is right depends on volume, two-way needs, and how much code you want to maintain.

Here's the decision tree I use:

Need two-way sync?
├── No  → Is your CRM's native Sheets connector free on your tier?
│        ├── Yes → Use that (15-min setup)
│        └── No  → Make.com Free tier (~300 rows/month free)
└── Yes → How much volume?
         ├── < 1k rows/day → Google Apps Script (free, requires code)
         ├── 1k-10k/day    → Make.com Core ($10.59/mo) or n8n self-hosted
         └── > 10k/day     → Custom API integration (eng. effort)

Enter fullscreen mode Exit fullscreen mode

Method 1: Native CRM marketplace integration

HubSpot ships a Google Sheets workflow action. Pipedrive has Marketplace connectors (Coupler.io, Surveyform). Zoho uses Zoho Flow. Salesforce has AppExchange options.

What you get: 15-minute setup, the CRM handles auth, field mapping uses the CRM's property picker.

What you don't get: two-way sync on most free tiers (HubSpot wants Operations Hub Starter at $20/mo). Conditional filtering is rare. Errors are silent — failed rows go to a log nobody reads.

Use this only when you need one-way "new row → new contact" and your CRM tier includes the integration. Otherwise jump to Method 2.

Method 2: Automation platforms (Zapier / Make / n8n)

The path of least resistance for two-way sync with logic. In n8n, a Sheet-to-HubSpot workflow is roughly this shape:

{
  "nodes": [
    {
      "name": "Google Sheets Trigger",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "parameters": {
        "event": "rowAdded",
        "documentId": "your-sheet-id",
        "sheetName": "Leads",
        "pollTimes": { "item": [{ "mode": "everyMinute" }] }
      }
    },
    {
      "name": "Filter",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "string": [{ "value1": "={{ $json.email }}", "operation": "isNotEmpty" }]
        }
      }
    },
    {
      "name": "HubSpot",
      "type": "n8n-nodes-base.hubspot",
      "parameters": {
        "resource": "contact",
        "operation": "upsert",
        "email": "={{ $json.email }}",
        "additionalFields": {
          "firstName": "={{ $json.firstname }}",
          "lifecyclestage": "lead"
        }
      }
    }
  ]
}

Enter fullscreen mode Exit fullscreen mode

15-minute build. The trigger node polls every minute. The IF filters out empty rows. The HubSpot node uses upsert on email, which kills the duplicate-creation problem on retry. Make.com and Zapier work the same way with different UIs.

Costs in 2026: Make Free covers ~300 rows/month, Make Core is $10.59/mo for 10k ops. n8n self-hosted is free on a $6 VPS. Zapier Free is useless for this (100 tasks, no filters, no multi-step).

Method 3: Google Apps Script (the free code path)

When you have someone on the team who writes JavaScript, this is the cheapest production option. Code lives inside the Sheet, runs on Google's infra, no external dependencies.

Here's a complete Apps Script that pushes new/edited rows to HubSpot with upsert by email — drop it into your Sheet's Apps Script editor, set the HUBSPOT_TOKEN script property, and add a Synced At column:

// Open the Sheet → Extensions → Apps Script → paste below.
// Project Settings → Script Properties → add HUBSPOT_TOKEN (a Private App token).

const HUBSPOT_BASE = 'https://api.hubapi.com';

function getToken() {
  return PropertiesService.getScriptProperties()
    .getProperty('HUBSPOT_TOKEN');
}

// Trigger this from a time-based trigger or onEdit.
function syncRowsToHubspot() {
  const sheet = SpreadsheetApp
    .getActiveSpreadsheet()
    .getSheetByName('Leads');

  // Assumes columns: A=email, B=firstname, C=lastname,
  // D=company, E=lifecyclestage, F=synced_at
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return;

  const data = sheet.getRange(2, 1, lastRow - 1, 6).getValues();

  data.forEach((row, i) => {
    const [email, firstname, lastname, company, stage, syncedAt] = row;
    if (!email || syncedAt) return; // skip empty / already-synced

    const result = upsertContact({
      email, firstname, lastname, company,
      lifecyclestage: stage || 'lead'
    });

    if (result.ok) {
      // Mark as synced (column F, row i+2)
      sheet.getRange(i + 2, 6).setValue(new Date().toISOString());
    } else {
      Logger.log(`Row ${i + 2} failed: ${result.error}`);
    }
  });
}

function upsertContact(props) {
  const url = `${HUBSPOT_BASE}/crm/v3/objects/contacts/` +
              `${encodeURIComponent(props.email)}?idProperty=email`;

  // Try PATCH first (update if exists)
  const patchOptions = {
    method: 'patch',
    contentType: 'application/json',
    headers: { Authorization: `Bearer ${getToken()}` },
    payload: JSON.stringify({ properties: props }),
    muteHttpExceptions: true
  };

  let res = UrlFetchApp.fetch(url, patchOptions);
  if (res.getResponseCode() === 200) return { ok: true };

  // 404 → contact doesn't exist, POST to create
  if (res.getResponseCode() === 404) {
    const postOptions = {
      ...patchOptions,
      method: 'post',
      payload: JSON.stringify({ properties: props })
    };
    res = UrlFetchApp.fetch(
      `${HUBSPOT_BASE}/crm/v3/objects/contacts`,
      postOptions
    );
    if (res.getResponseCode() === 201) return { ok: true };
  }

  return {
    ok: false,
    error: `${res.getResponseCode()}: ${res.getContentText()}`
  };
}

Enter fullscreen mode Exit fullscreen mode

Wire it to a trigger. In the Apps Script editor: clock icon → Add Trigger → choose syncRowsToHubspot → time-based → every 5 minutes. Now your sheet syncs automatically.

Known limits: 6-min execution cap per run, ~90 minutes total runtime/day on free Google accounts, 20,000 UrlFetch calls/day. For 1,000 rows/day this is fine. For 5,000+ rows, batch by synced_at window and process in chunks.

Method 4: Custom API integration

When volume is real (10k+ rows/day) or you need conflict resolution on two-way sync, you build a small backend. A minimal HubSpot → Sheets webhook receiver in Node.js with signature verification:

import express from 'express';
import crypto from 'crypto';
import { google } from 'googleapis';

const app = express();

// Raw body needed for signature verification
app.post('/hubspot-webhook',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const sig = req.headers['x-hubspot-signature-v3'];
    const ts = req.headers['x-hubspot-request-timestamp'];
    const body = req.body.toString('utf8');

    const computed = crypto
      .createHmac('sha256', process.env.HUBSPOT_CLIENT_SECRET)
      .update(`POST${req.originalUrl}${body}${ts}`)
      .digest('base64');

    if (sig !== computed) return res.status(401).end();

    const events = JSON.parse(body);
    for (const ev of events) {
      if (ev.subscriptionType === 'contact.propertyChange') {
        await pushUpdateToSheet(ev.objectId, ev.propertyName, ev.propertyValue);
      }
    }
    res.status(200).end();
  }
);

app.listen(3000);

Enter fullscreen mode Exit fullscreen mode

The other half — pushUpdateToSheet — uses the Sheets API to find the row by HubSpot contact ID and update the column matching the property name. With 80–100 lines total, you've got two-way sync that handles thousands of records/day.

Trade-offs: 1–3 weeks of initial build, OAuth refresh logic, observability, the bus factor problem. Don't go here until automation platforms have failed you.

Gotchas every method shares

Duplicates. Re-running creates duplicate contacts unless you upsert by email or an external ID. The Apps Script above does the PATCH-then-POST dance; n8n's HubSpot node has an "upsert" mode; Make has a "search then create or update" pattern. Use one.

Types. Sheets stores everything as a string. CRMs have types — number, date, boolean, dropdown enum. 5/4/2026 is May 4 in US locale, April 5 elsewhere. A blank cell is empty string "", not null. Normalize before the API call.

Rate limits. HubSpot allows 100 req/10s on standard plans. Pipedrive's are tighter (10 req/2s in some endpoints). Add Utilities.sleep(100) between Apps Script calls when batching, or use the platform's built-in throttling.

OAuth expiry. Tokens die. Native integrations refresh silently. Apps Script using a Private App token in HubSpot doesn't expire (yay). Custom Node.js code with OAuth needs refresh logic — and the first sign it's broken is the sync just stops working with no alert. Wire failure notifications to Slack or email.

Polling latency. Apps Script time-trigger minimum is every minute. Automation platforms poll every 1–15 minutes depending on tier. Webhooks are the only sub-second option, and Google Sheets doesn't emit them natively (onEdit runs server-side but isn't a webhook). For "true real-time," you need the CRM's webhook in the other direction, plus a way to push edits back.

TL;DR

  • One-way, low volume, native integration free? Use it.
  • Two-way, low-medium volume, no code? Make.com or n8n.
  • Have a JS-comfortable team and want $0 ongoing? Apps Script (copy the snippet above).
  • 10k+ rows/day or complex conflict resolution? Custom backend.

Most SMBs land at Make.com or Apps Script. Custom API is reserved for when you've genuinely outgrown both.


Originally published on trackstack.tech with a fuller decision framework and FAQ.