Back to Blog
Clayn8nHubSpot

How to connect Clay and n8n to HubSpot: the webhook handoff

Abhishek Singla Nov 10, 2026 12 min read

This is a build guide for the webhook handoff between Clay, n8n and HubSpot. Clay enriches the record. n8n orchestrates the flow. HubSpot stores the result. The steps below walk through the sending workflow, the Clay enrichment table, the receiving workflow and the write-back with deduplication.

The short answer

n8n catches a HubSpot event, fetches the contact and POSTs it to a Clay webhook. Clay enriches, scores and POSTs the result back. A second n8n workflow receives the callback and upserts the enriched fields to HubSpot. The whole loop runs on Clay Growth ($495/mo monthly, $446/mo annual) and n8n Starter (€20/mo annual) or the self-hosted Community Edition at no licence cost.

Who does what in this build

Clay finds and enriches the data: email, company, technographics, headcount, scoring. n8n moves and routes it: listening for HubSpot events, sending records to Clay, receiving the callback, writing to HubSpot and notifying the team.

The two tools connect through webhooks and HTTP. n8n does not ship a native Clay node as of November 2026 (a community feature request exists). n8n sends a record to Clay's webhook source URL via an HTTP Request node. Clay sends the enriched result back to an n8n webhook URL via an HTTP action.

For the pricing logic, worked examples and the scenarios where you need only one of the two, see the Clay vs n8n decision guide.

Prerequisites

Clay Growth or higher. Webhook sources, HTTP API integrations and CRM integrations are Growth-plan features, not available on Launch or Free. Growth is $495/mo on monthly billing or $446/mo billed annually and includes 6,000 Data Credits and 40,000 Actions per month.

n8n Starter, Pro or Community Edition. The cloud Starter plan is €20/mo billed annually. The self-hosted Community Edition is free to self-host with no per-execution billing. Any tier works for this build.

HubSpot with a private app. You need a HubSpot account where you can create a private app and generate an access token. HubSpot retired API keys in November 2022; use a private app access token or OAuth via n8n's built-in HubSpot credential for all API access. The email-based contact deduplication described later in this guide is built into HubSpot on every plan (source).

The sending workflow in n8n

This workflow listens for new contacts in HubSpot and sends them to Clay for enrichment.

Trigger. A HubSpot Trigger node fires on new contact creation. The trigger delivers event metadata including the contact's Object ID, but not the contact's properties. You need a second step to fetch them.

Get the contact. A HubSpot node with the "Get a contact" operation fetches the record by ID. Set the Contact ID to the trigger's {{ $json.objectId }} and request the email property (add others you need, such as company). This step returns the full contact record so the next node can read its properties.

HTTP POST to Clay. An HTTP Request node POSTs a JSON body to the Clay webhook source URL. Include the fields Clay needs for enrichment:

{
  "email": "{{ $json.properties.email.value }}",
  "hubspot_object_id": "{{ $json.vid }}",
  "callback_url": "https://your-n8n.example.com/webhook/clay-callback"
}

The email and vid expressions read from the output of the Get node. The n8n HubSpot node's Get operation calls HubSpot's legacy v1 endpoint, so it returns the contact ID as vid and properties nested as properties.<name>.value. If your HubSpot contacts store a company domain property, include it as a separate field so Clay can run a company enrichment waterfall on the domain directly. Otherwise Clay can derive the domain from the email address.

The callback_url field tells Clay where to send the enriched result. If your Clay table uses a fixed HTTP action URL instead of a per-row callback, omit this field and hardcode the destination in the Clay HTTP action.

The Clay table

Create a table in Clay with a webhook source. Each incoming POST creates a new row.

Columns in order:

  1. Webhook source. Receives the n8n POST and populates email, hubspot_object_id and callback_url.
  2. Email enrichment. A waterfall column that looks up the person by email. Runs providers in sequence; each provider that returns a result charges Data Credits at that provider's rate. A provider that returns no result charges nothing.
  3. Company enrichment. A waterfall column that looks up the company by domain (derived from the email or passed explicitly). Same billing: Data Credits charged per provider hit at that provider's rate.
  4. AI scoring. An AI column or formula column that assigns a lead score based on the enrichment results (job title, company size, funding stage, technology stack). An AI model column charges Data Credits per run: fixed-price models cost a set number of credits per task, and variable-price models bill by token usage (source). A formula column avoids the AI model's Data Credits.
  5. HTTP action. Posts the entire enriched row back to the n8n callback URL as a JSON payload. This step costs one Action.

Credit rules on Clay Growth (source):

  • Data Credits roll over on Launch and Growth. Actions reset monthly and do not roll over.
  • A provider lookup that returns no result charges zero Data Credits and zero Actions.
  • A bring-your-own-key enrichment costs one Action and no Data Credits.

The receiving workflow in n8n

A second n8n workflow starts with a Webhook node listening at /clay-callback. When Clay's HTTP action fires, this workflow receives the enriched record and writes it to HubSpot.

The n8n Webhook node wraps the incoming POST body under body in its output, so downstream nodes read fields as $json.body.<field>.

The n8n HubSpot node offers a Create or Update operation for contacts: an upsert keyed on email. If a contact with that email already exists, it updates the record. If not, it creates one. This removes the need for a search-then-branch flow.

The logic has two branches:

  1. Enrichment returned data. If Clay returned a company name (or another enrichment output you rely on), upsert the contact to HubSpot with the enriched fields.
  2. No enrichment data. If Clay returned an empty result, skip the record and log it for manual review.

Here is a simplified outline of the receiving workflow. Adapt the field mappings and node configuration to your HubSpot property set.

{
  "name": "Clay callback: write to HubSpot",
  "nodes": [
    {
      "name": "Receive from Clay",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "path": "clay-callback",
        "httpMethod": "POST"
      },
      "position": [240, 300]
    },
    {
      "name": "Has enrichment data",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.body.company_name }}",
              "operation": "isNotEmpty"
            }
          ]
        }
      },
      "position": [440, 300]
    },
    {
      "name": "Log skipped row",
      "type": "n8n-nodes-base.noOp",
      "parameters": {},
      "position": [640, 480]
    },
    {
      "name": "Upsert to HubSpot",
      "type": "n8n-nodes-base.hubspot",
      "parameters": {
        "resource": "contact",
        "operation": "upsert",
        "email": "={{ $json.body.email }}",
        "additionalFields": {
          "companyName": "={{ $json.body.company_name }}",
          "jobTitle": "={{ $json.body.job_title }}"
        }
      },
      "position": [640, 200]
    }
  ],
  "connections": {
    "Receive from Clay": {
      "main": [
        [
          {
            "node": "Has enrichment data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has enrichment data": {
      "main": [
        [
          {
            "node": "Upsert to HubSpot",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log skipped row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

The Webhook node receives Clay's POST. The IF checks whether Clay returned a company name, routing empty enrichment results to a No-Op node you can replace with a Slack alert or logging step. The true branch upserts the contact to HubSpot using the Create or Update operation, which matches on email and either updates the existing record or creates a new one.

Deduplication and the HubSpot write

HubSpot deduplicates contacts by email address on every plan (source). How that works depends on how the contact arrives:

  • Forms and imports. A contact arriving through a form submission or CSV import with an email that already exists updates the existing record automatically.
  • API create endpoint. The Contacts API create call (POST /crm/v3/objects/contacts) rejects a contact whose email already exists with a 409 Conflict error. It does not silently update. The response includes the existing record's ID so you can fall back to an update.
  • API upsert. The batch upsert endpoint and n8n's Create or Update operation handle deduplication natively: they match on email, update if the contact exists and create if it does not. The workflow above uses this path, so it never hits the 409.

What HubSpot does not catch:

  • Companies by domain. HubSpot deduplicates companies by the Company Domain Name property during imports and automatic company creation. API calls to create a company with a domain that already exists will create a duplicate unless you search first.
  • Contacts with no email. If a contact record has no email address, HubSpot has no dedupe key. Two records for the same person will coexist until someone merges them manually.

The receiving workflow uses the Create/Update upsert, so records that already exist in HubSpot get updated and new emails get created. This covers the two most common paths: enrichment of existing HubSpot contacts and enrichment of new leads imported into Clay from other sources.

Routing to the owner

After the HubSpot write, add a Switch node that assigns the lead to a rep based on territory, tier, round-robin or any field the scoring step populated. Follow the Switch with a Slack node or email notification so the assigned rep knows an enriched lead arrived.

Error handling and monitoring

n8n side. Enable error workflows in your n8n settings. When the receiving workflow fails (Clay's POST times out, HubSpot returns a rate-limit error, a field mapping breaks), n8n logs the execution with the full error trace and can trigger a separate workflow that posts to Slack or sends an email.

Clay side. Rows that fail enrichment show an error state in the table. You can re-run individual cells by clicking the cell, or force-run the entire column from the column header. Assign a human owner to the Clay table who checks for failed rows regularly.

The gap between the two. If Clay enriches a row but the HTTP callback to n8n fails (network error, n8n downtime), the enriched data sits in Clay with nothing written to HubSpot. Monitor for this by comparing Clay row counts to HubSpot write counts in a weekly check, or by adding a status column in Clay that the n8n receiving workflow updates via a return POST.

Testing with a few records

Before running the full pipeline on live data:

  1. Send 3 to 5 test records through the n8n sending workflow. Use real email addresses from contacts you already have in HubSpot so you can verify the update path.
  2. Watch the Clay table. Confirm each row enriches without errors and the HTTP callback fires. Check the HTTP action's response code in Clay's run log.
  3. Check the n8n execution log. Confirm the receiving workflow ran, the IF node branched correctly and the HubSpot write succeeded.
  4. Verify in HubSpot. Open the test contacts and confirm the enriched fields (company, job title, lead score) are populated. Check that no duplicate records were created.
  5. Test the empty-enrichment path. Send a record with a deliberately invalid email (e.g. test@example.invalid) and confirm the workflow routes it to the skip/log branch instead of writing garbage to HubSpot.

Cost per enriched record

Data Credits per record depend on which providers run and return results. Each provider charges a different number of Data Credits per hit, and providers that return no result charge nothing. AI model columns also consume Data Credits.

Cost formula:

  • Data Credits per record = sum of each provider's credit cost when it returns a result, plus AI model credits if applicable.
  • Actions per record = number of Clay steps that execute (enrichment columns, AI scoring, HTTP callback, etc.).

Assumed example (your numbers will differ):

Suppose an email waterfall hits on the second provider at 1 credit, a company waterfall hits on the first provider at 2 credits, and a fixed-price AI scoring column costs 1 credit per run. That record costs 4 Data Credits and 4 Actions (email waterfall, company waterfall, AI scoring, HTTP callback). A formula-only scoring column instead of an AI model avoids the scoring credit, bringing Data Credits to 3. Check your table's run log for the exact Action count.

On Clay Growth at $495/mo monthly billing or $446/mo annual (6,000 Data Credits, 40,000 Actions), Data Credits are typically the binding constraint. At 4 credits per record in this example, the monthly allotment supports roughly 1,500 records. Your actual cost per record depends on which providers you configure, their individual credit costs and their hit rates.

On n8n Starter at €20/mo annual: the sending workflow and the receiving workflow each count as one execution per lead, so each enriched record costs 2 executions. On the self-hosted Community Edition, there is no per-execution billing.

This arithmetic is on published list prices as of November 2026. It does not account for annual discounts beyond those stated, enterprise negotiation or overage pricing.

Frequently asked questions

Which Clay plan do I need for webhooks?

Growth or higher. Webhook sources, HTTP API integrations and CRM integrations are Growth-plan features, not available on Launch or Free. Launch ($185/mo monthly, $167/mo annual) includes the data marketplace and email enrichment but not the webhook and HTTP automation this build requires. Growth is $495/mo monthly or $446/mo annual with 6,000 Data Credits and 40,000 Actions per month.

Does n8n have a native Clay node?

No. As of November 2026, n8n has no built-in Clay node. The connection uses an HTTP Request node on the n8n side and a webhook source plus HTTP action on the Clay side. A community feature request exists for one.

How do I avoid duplicate contacts in HubSpot?

Use n8n's HubSpot node with the Create or Update operation, which upserts contacts keyed on email. If a contact with that email exists it updates the record; if not it creates one. This avoids the 409 Conflict error that the plain create endpoint returns when the email already exists. For contacts with no email address, HubSpot has no dedupe key, so those require manual review.

What does each enriched record cost?

It depends on which Clay providers you use and their per-provider credit costs. See the cost per record section above for the formula and a worked example. Data Credits are typically the binding constraint on the Growth plan.

How do I handle Clay rows that never call back?

If a Clay row enriches but the HTTP callback to n8n fails, the data stays in Clay with nothing written to HubSpot. Check for this by comparing the row count in Clay to the execution count in n8n's receiving workflow. For rows stuck in a pending or error state, re-run the HTTP action column from the column header. If the n8n webhook endpoint was down, fix the endpoint first and then re-trigger from Clay.

Every system in this guide is a tool Ziel Lab builds with. If the webhook handoff needs building or the enrichment-to-CRM pipeline needs tuning for your stack, that is the work we do.

Talk to us