n4nAI

Building a Slack-to-CRM AI workflow with Zapier

Step-by-step tutorial to build a reliable Slack to CRM AI workflow Zapier automation that uses an LLM to parse messages and create HubSpot contacts.

n4n Team3 min read692 words

Audio narration

Coming soon — every post will get a voice note here.

A Slack to CRM AI workflow Zapier setup lets support and sales teams turn casual Slack conversations into structured CRM records without manual copy-paste. This tutorial builds that automation end to end, using a Python code step to call an LLM for extraction and a CRM API to persist the result. You will end with a Zap that watches a channel, parses lead intent, and creates a HubSpot contact in seconds.

Prerequisites

  • A Zapier account with access to Multi-step Zaps (paid plan or trial).
  • Admin rights to install a Slack app in your workspace.
  • A HubSpot account with a private app token (Settings → Integrations → Private Apps).
  • An API key for an OpenAI-compatible LLM endpoint. We’ll use n4n.ai’s gateway, which fronts 240+ models with automatic fallback when a provider is degraded.
  • Basic comfort reading Python and JSON.

If you prefer no-code CRM steps, Zapier’s native HubSpot action works, but we’ll show the API call so you can adapt to Salesforce or Pipedrive.

Architecture overview

The flow is linear:

  1. Slack trigger fires on new message in #sales-intake.
  2. Filter step drops messages that don’t start with !lead.
  3. Python code step calls an LLM to extract full_name, company, email, intent, deal_value.
  4. Python code step (or native action) posts the contact to HubSpot.
  5. Optional: lookup before create to avoid duplicates.

The Slack to CRM AI workflow Zapier pattern lives or dies on extraction quality, so we spend most effort on step 3.

Step 1: Capture Slack messages

In Zapier, create a Zap. Choose Slack → New Message Posted to Channel. Connect your workspace and pick #sales-intake (or a private channel).

Zapier delivers a payload like this:

{
  "channel": "C0123ABC",
  "user": "U0456DEF",
  "text": "!lead Jane Doe from Acme wants pricing for 50 seats, jane@acme.com",
  "ts": "1718200000.000100"
}

Test the trigger with a real message so Zapier caches a sample.

Step 2: Filter and preprocess

Add a Filter step: Text (from Slack) starts with !lead. This keeps the LLM spend focused on real intent.

If you want to strip the prefix before extraction, add a Code (Python) step:

text = input_data.get("text", "")
cleaned = text.replace("!lead", "", 1).strip()
return {"cleaned_text": cleaned}

Expected trigger payload after filter

Only messages like !lead Jane Doe from Acme... pass. The cleaned_text field becomes the LLM input.

Step 3: Extract CRM fields with an LLM

Add another Code (Python) step. We call an OpenAI-compatible /chat/completions endpoint. Using n4n.ai here means a rate-limited upstream model won’t break the Zap—its gateway automatically falls back to another provider behind the same endpoint.

import os
import json
import requests

def extract_crm_fields(text):
    api_key = os.environ["N4N_API_KEY"]
    url = "https://api.n4n.ai/v1/chat/completions"
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    system = (
        "You are a CRM data extractor. Return strict JSON with keys: "
        "full_name (string), company (string), email (string), "
        "intent (string, one of 'pricing','support','partnership'), "
        "deal_value (number or null)."
    )
    payload = {
        "model": "gpt-4o-mini",
        "messages": [
            {"role": "system", "content": system},
            {"role": "user", "content": text}
        ],
        "response_format": {"type": "json_object"}
    }
    r = requests.post(url, headers=headers, json=payload, timeout=30)
    r.raise_for_status()
    content = r.json()["choices"][0]["message"]["content"]
    return json.loads(content)

slack_text = input_data.get("cleaned_text", "")
result = extract_crm_fields(slack_text)
return result

Set N4N_API_KEY in Zapier’s environment variables (the key icon on the left panel).

Expected output

For input Jane Doe from Acme wants pricing for 50 seats, jane@acme.com, the step returns:

{
  "full_name": "Jane Doe",
  "company": "Acme",
  "email": "jane@acme.com",
  "intent": "pricing",
  "deal_value": null
}

If the model infers a number from “50 seats at $20/seat”, deal_value becomes 1000. The structured contract is what makes the Slack to CRM AI workflow Zapier approach robust.

Step 4: Write to CRM

You can use Zapier’s HubSpot “Create Contact” action mapped to those fields. For full control, add a final Code (Python) step:

import os
import requests

def create_hubspot_contact(data):
    token = os.environ["HUBSPOT_TOKEN"]
    url = "https://api.hubapi.com/crm/v3/objects/contacts"
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    name_parts = (data.get("full_name") or "").split(" ", 1)
    props = {
        "email": data.get("email"),
        "firstname": name_parts[0] if name_parts else "",
        "lastname": name_parts[1] if len(name_parts) > 1 else "",
        "company": data.get("company"),
        "hs_lead_status": "new",
        "intent": data.get("intent", "unknown")
    }
    r = requests.post(url, headers=headers, json={"properties": props}, timeout=20)
    r.raise_for_status()
    return r.json()

contact = create_hubspot_contact(input_data)
return {"hubspot_id": contact.get("id")}

Expected response

HubSpot returns 201 with an object containing id. The code step outputs:

{
  "hubspot_id": "9011"
}

Your sales team now sees Jane Doe in the CRM within seconds of the Slack post.

Step 5: Handle failures and idempotency

Zapier retries code steps on exceptions, but duplicate contacts are a real risk if a message is edited. Before creating, do a lookup by email:

def find_contact(email):
    token = os.environ["HUBSPOT_TOKEN"]
    url = f"https://api.hubapi.com/crm/v3/objects/contacts/{email}?idProperty=email"
    r = requests.get(url, headers={"Authorization": f"Bearer {token}"}, timeout=10)
    return r.status_code == 200

if not find_contact(input_data.get("email")):
    create_hubspot_contact(input_data)
else:
    return {"status": "skipped_existing"}

Wrap the LLM call in a try/except to push a Slack alert to #zap-errors if extraction fails. The Slack to CRM AI workflow Zapier build should degrade by notifying humans, not by silently dropping leads.

Production considerations

  • Model choice: gpt-4o-mini is cheap and fast; switch to a larger model in the same endpoint if extraction accuracy drops.
  • Cache hints: If you batch similar messages, forward provider cache-control hints via the gateway to cut token cost.
  • Rate limits: Zapier runs steps serially; if you expect bursts, add a Queue step or use a Zapier delay.
  • PII: Slack messages may contain unredacted emails. Restrict the trigger channel and filter on a prefix as shown.
  • Testing: Send five real-style messages with missing fields (!lead Bob from Globex, no email) and confirm the LLM returns null without throwing.

The Slack to CRM AI workflow Zapier pattern is not magic—it is a disciplined pipeline from unstructured text to a typed record. Get the extraction contract right and the rest is CRUD.

Tagszapierslackcrmtutorial

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All llm workflow automation: n8n, zapier, make posts →