Feature Gaps in Startup CRMs That Break Automations Fast
1. Bulk contact imports almost always mess up custom fields
So I tried switching a client from HubSpot’s forever-free plan (that showed popups like it was haunted) to Zoho CRM. Import looked clean until four of the custom boolean fields were suddenly strings and Zapier refused to trigger off them. No errors. Just didn’t detect the filter to run the next action. Fun.
The issue came down to Zoho treating true/false fields differently depending on how the data was uploaded. If you import from CSV, and the column says “Yes”, it gets cast to a stringified key. But when added via their API (like through Make or a form submit), those same fields come in as booleans. So your automations behave differently depending on where the contact came from.
Easiest fix? Force a formatter step before the filter and set a static conversion of the value, like:
{{zap_data.custom_field}} == 'Yes' ? true : false
It’s dumb. But unless you want to rebuild all your source triggers, it’s faster than reimporting with standardized values. Ironically, Airtable played nicer here because it just stores everything as strings anyway — maybe not elegant, but consistent.
2. CRM stages do not map cleanly across platforms
Pipedrive’s “Deals” vs Close’s “Opportunities” vs HubSpot’s “Pipelines”—they all pretend to be the same thing. Until you try to copy logic between them.
One time I mapped out a stage-based automation to send follow-up messages when a deal moved to “Proposal Sent”. In Pipedrive that made sense. In Close, the exact same label existed but the trigger fired twice — once on stage transition, and again when a note got added within that stage.
Turns out Close treats activity within a stage as valid change signals. So the webhook is honest, but not useful. Zapier doesn’t always catch the nuance because it’s surface-level polling, not delta checking.
I had to add a timestamp compare inside a code step to prevent double-firing across silent transitions. If last_updated
is less than 10 seconds after stage_changed_at
, then ignore the run.
Messy. But it held. For a week, anyway. Then someone edited a note right after moving the deal, and both updates happened within the same second. Back to the drawing board.
3. Webforms tied to CRMs rarely validate the same way
You’ve got a landing page with a form hooked to Zoho or Freshsales or even something like Streak. User submits. CRM shows missing phone number. But the frontend clearly required it. So…who lost it?
If you’re using Typeform or Tally, chances are it submitted as a plain text payload without always labeling input types clearly. I watched a webhook log from Tally that sent country codes and numbers in separate fields — but the CRM wanted them combined. No errors. It just dropped the field silently. So your “new lead” has no number.
Freshsales has one of the stricter field validation gates, but oddly, it doesn’t always fail the call. You’ll get a 200 OK, but the lead will be created without half your fields. I only noticed because the sales guy said, “Why does this one have nothing except the email?”
Hidden fix: use a JSON Compose block before the HTTP request and stringify every field. Even numbers. Even booleans. Freshsales accepts them more reliably if you send them all as strings. Don’t ask me why. The API guide doesn’t mention it anywhere.
4. Activity tracking often fakes accuracy with time shifting
This happened inside Copper CRM. A client wanted to filter deals where the last activity was older than 10 days. Obvious logic…except it kept grabbing deals that did have recent activity—just not logged through the CRM directly.
If someone sent an email via their Gmail, the Chrome extension logged the activity with a timestamp. Buuuut… it backdated it to the email’s send time, not when it synced. So a deal might show an email from yesterday, but Zapier sees the sync time as two days ago. Now your time-based filters wobble, badly.
The workaround? Don’t use last_activity_at
. Pull the activity feed via API and inspect the created_at
of individual actions. Add a code step to check if any valid activity exists in the last 10 days — actual clock time, not faked log dates.
Longer to build, arguably overkill, but unless you want daily reports to be full of lies, it’s necessary. At least until Copper fixes the backfill clock stuff, which… don’t count on it.
5. CRM permissions block API flows in team accounts silently
I assumed “works for me” meant “works for everyone.” Bad assumption. This was with Zoho again, although I’ve seen it in Salesforce too.
Built an automation where a deal gets created when a rep books a meeting. Trigger was Calendly → webhooks → Zoho deal create. Worked fine in my sandbox. Then the client said they couldn’t see the new deals. No errors, nothing was missing. But the data only showed for my login — not theirs.
Turns out the API key I used had Deal module access across all subgroups. Their user didn’t. So when the Zap ran under a team member’s OAuth, it created blank entries, never filled them, then hid them from other views based on ACL (access control lists) I never saw.
If your CRM lets permissions block visibility without rejecting the API call, brace for ghost data.
The not-documented fix: create a “System user” in the CRM with full CRUD, and use its API context for all automations — never rotate by user. In Zoho, you have to do this through the Developer Console under connected apps. Took longer than I want to admit.
6. Mobile adds to CRMs often bypass automation logging
The salesperson logs a call while walking to their car. They open the CRM app, tap “Add note”, jot down “Left voicemail,” boom. Done.
Except that note never hits the webhook chain. I saw this first in Pipedrive: mobile note entries aren’t structured the same way as desktop, and they bypass certain triggers. In Zapier, my automation was watching for “New Note Added”. But unless the note was typed on desktop, nothing fired.
I wish I were kidding. Even Zapier’s support shrugged and escalated it to “vendor-specific behavior.”
I asked the rep to log the same note on desktop and mobile. Only one hit. Eventually switched to Make, because their webhook saw lower-level signals. Still, I had to query the RelatedObjects manually to find which notes tied to which deals.
Here’s the working loop
GET note feed → filter for type == 'note' and source == 'app'
THEN match parentObjectId to Deal ID manually
It’s gross. But at least I can stop asking reps if they “really added it,” when the CRM just failed to care.
7. Pricing tiers hide or break identical features in separate plans
Looked at CapsuleCRM because their UI hadn’t changed since 2012 (kind of comforting). Found their Teams plan handled multiple pipelines fine. But when I dropped to the Starter tier, the webhook system just stopped sending updates on opportunity status. No errors. Not-tiered by webhook type. It just…didn’t send them anymore.
Official support said: “Webhook access is available across all plans.” Which was technically true. But not all fields are available. On the basic plan, the payloads exclude some internal IDs and status transitions, so my automations broke mid-filter. The deal status field was just missing.
If your automation filters on something invisible, the run silently ends with no log.
Try this workaround before assuming you broke something:
- Use webhook test mode to grab a raw payload
- Run a dummy deal through each plan level
- Compare payload structures line by line
- If a field goes missing, don’t filter on it
- Instead, add a secondary logging note to that deal
- Trigger on the note contents, not CRM status
Not elegant, but at least visible. Automations die more from partial truths than flat errors.
8. Duplicate prevention logic always fights with merge automations
There’s no CRM that handles deduping well. Not even close. I tried syncing leads from LinkedIn via PhantomBuster to Airtable to Close. Used email as the merge key. First lead passed. Second lead, different source, same email? Blocked.
Close tried to preserve source priority. So if the first record had minimal data and the second one was rich, the merge would “prefer” the older one. But Zapier had no idea that happened. It just saw a successful POST, assumed it worked, and kept moving. So the next three steps wrote to fields that weren’t there. Technically they ran. Just not on the record I expected.
What saved me was a tip from someone half-screaming on a webinar rewatch: “Always read the response object, even on success.”
// Look for this key in the response:
"duplicateFound": true
If present, it includes a redirect ID you can use to update the existing record instead. Not in their docs. Absolutely critical.
I now add a short filter step on every create action that watches for duplicateFound
. And yeah, it’s extra cognitive weight. But once you’ve had five leads ghosted in a buried record by accident, you’ll set it up too.