Back to n8n Fundamentals
n8n Fundamentals

How to Connect Any CRM via HTTP Request Node

Generic guide for Clio, Karbon, ServiceNow, Cosential, etc. REST API basics, auth headers, POST requests.

How to Connect Any CRM
via HTTP Request Node

Most professional services CRMs

(Clio, Karbon, ServiceNow, Cosential, Unanet) don't have native n8n nodes. You'll connect them using the HTTP Request node and their REST APIs
. This guide shows you exactly how to authenticate, structure requests, and handle responses for any CRM
.

What You Need Before Starting

Pull up your CRM

's API documentation. You need four pieces of information:

Base URL: The root endpoint for all API

calls. Examples:

  • Clio: https://app.clio.com/api/v4
  • Karbon: https://api.karbonhq.com/v3
  • ServiceNow: https://[your-instance].service-now.com/api/now
  • Cosential: https://[your-firm].cosential.com/api

Authentication method: Most use one of three approaches:

  • API
    key in header (Clio, Karbon)
  • OAuth 2.0 with bearer token (ServiceNow, some Clio instances)
  • Basic auth with username/password (older systems)

Rate limits: Note the requests-per-minute cap. Clio allows 500/min, Karbon allows 120/min. You'll need this for error handling.

Endpoint paths: Identify the exact paths for your use case. Creating a contact in Clio uses /contacts.json, while Karbon uses /contacts.

Setting Up Authentication

Authentication goes wrong more than anything else. Here's how to configure each type in n8n.

API
Key in Header

Most common for legal and accounting CRMs

. Add the HTTP Request node, then:

  1. Set Authentication to "Generic Credential Type"
  2. Click "Create New Credential"
  3. Select "Header Auth"
  4. Name: Authorization
  5. Value: Bearer [your-api-key] or Token [your-api-key] (check your CRM
    's docs for exact format)

For Clio specifically:

  • Name: Authorization
  • Value: Bearer YOUR_ACCESS_TOKEN

For Karbon:

  • Name: AccessKey
  • Value: YOUR_API_KEY
  • Add second header: Accept with value application/json

OAuth 2.0

ServiceNow and enterprise CRMs

typically use OAuth. You need client ID, client secret, and token URL from your CRM
admin panel.

  1. Set Authentication to "OAuth2"
  2. Grant Type: "Authorization Code" (most common) or "Client Credentials"
  3. Authorization URL: https://[your-instance].service-now.com/oauth_auth.do
  4. Access Token URL: https://[your-instance].service-now.com/oauth_token.do
  5. Client ID: From your CRM
    's API
    settings
  6. Client Secret: From your CRM
    's API
    settings
  7. Scope: Check docs (ServiceNow uses useraccount)

Click "Connect my account" and complete the OAuth flow in the popup window.

Basic Auth

Older systems or internal APIs

. Least secure, but simplest.

  1. Set Authentication to "Basic Auth"
  2. Username: Your API
    username
  3. Password: Your API
    password or token

Creating Records (POST Requests)

Let's create a contact in Clio as a complete example. This pattern works for any CRM

.

HTTP Request Node Configuration:

Method: POST URL: https://app.clio.com/api/v4/contacts.json Authentication: Header Auth (configured above)

Headers to Add:

  • Content-Type: application/json
  • Accept: application/json

Body (JSON format):

{
  "data": {
    "first_name": "`{{ $json.firstName }}`",
    "last_name": "`{{ $json.lastName }}`",
    "type": "Person",
    "email_addresses": [
      {
        "name": "Work",
        "address": "`{{ $json.email }}`",
        "default_email": true
      }
    ],
    "phone_numbers": [
      {
        "name": "Mobile",
        "number": "`{{ $json.phone }}`",
        "default_number": true
      }
    ]
  }
}

For Karbon (different structure):

{
  "PreferredName": "`{{ $json.firstName }}`",
  "FamilyName": "`{{ $json.lastName }}`",
  "EmailAddresses": [
    {
      "Email": "`{{ $json.email }}`",
      "IsPrimary": true
    }
  ]
}

Notice the differences: Clio wraps everything in a data object and uses snake_case. Karbon uses PascalCase with no wrapper. Always check your CRM

's example requests.

Reading Records (GET Requests)

Retrieving data requires query parameters for filtering and pagination.

Fetch all contacts modified in last 24 hours (Clio):

Method: GET URL: https://app.clio.com/api/v4/contacts.json Query Parameters:

  • updated_since: {{ $now.minus({hours: 24}).toISO() }}
  • fields: id,first_name,last_name,email_addresses,updated_at
  • limit: 200

Fetch specific contact by email (Karbon):

Method: GET URL: https://api.karbonhq.com/v3/Contacts Query Parameters:

  • $filter: EmailAddresses/any(e: e/Email eq '{{ $json.email }}')
  • $select: ContactKey,PreferredName,FamilyName

Karbon uses OData query syntax. ServiceNow uses sysparm_query. Read your CRM

's filtering documentation carefully.

Updating Records (PUT/PATCH Requests)

Most CRMs

use PUT for full updates, PATCH for partial updates.

Update contact phone number (Clio):

Method: PUT URL: https://app.clio.com/api/v4/contacts/{{ $json.contactId }}.json Body:

{
  "data": {
    "phone_numbers": [
      {
        "id": "`{{ $json.phoneNumberId }}`",
        "number": "`{{ $json.newPhone }}`"
      }
    ]
  }
}

You need the contact ID and the phone number ID. Always fetch the record first to get nested object IDs.

Handling Pagination

CRMs

return 50-200 records per request. Loop through pages to get everything.

Clio pagination pattern:

  1. Add Loop node after HTTP Request
  2. Set mode to "Run Once for Each Item"
  3. In HTTP Request, add query parameter:
    • cursor: {{ $json.meta.paging.next }}
  4. Loop continues until meta.paging.next is null

Karbon pagination pattern:

  1. Query parameter: $skip: {{ $runIndex * 100 }}
  2. Query parameter: $top: 100
  3. Loop until response returns fewer than 100 records

Error Handling You Actually Need

Add an IF node immediately after your HTTP Request node.

Condition 1 (Success):

  • {{ $json.statusCode }} equals 200 or 201
  • Route to your success path

Condition 2 (Rate Limit):

  • {{ $json.statusCode }} equals 429
  • Add Wait node: 60 seconds
  • Add Loop node to retry the request

Condition 3 (Auth Failure):

  • {{ $json.statusCode }} equals 401 or 403
  • Send alert via Slack/email
  • Stop workflow

Condition 4 (Not Found):

  • {{ $json.statusCode }} equals 404
  • Handle gracefully (maybe create the record instead of updating)

Everything Else:

  • Log the full response: {{ JSON.stringify($json) }}
  • Send to error tracking system

Real-World Example: Sync Form Submission to CRM

Complete workflow for capturing a website form and creating a CRM

contact.

Trigger: Webhook (receives form POST)

Node 1: HTTP Request (Check if contact exists)

  • Method: GET
  • URL: https://app.clio.com/api/v4/contacts.json?query={{ $json.email }}

Node 2: IF (Contact exists?)

  • Condition: {{ $json.data.length > 0 }}

Node 3a: HTTP Request (Update existing - true branch)

  • Method: PUT
  • URL: https://app.clio.com/api/v4/contacts/{{ $json.data[0].id }}.json
  • Body: Updated fields only

Node 3b: HTTP Request (Create new - false branch)

  • Method: POST
  • URL: https://app.clio.com/api/v4/contacts.json
  • Body: Full contact object

Node 4: Set (Extract contact ID)

  • contactId: {{ $json.data.id }}
  • contactUrl: https://app.clio.com/contacts/{{ $json.data.id }}

Node 5: Slack (Notify team)

  • Message: "New contact created: {{ $json.data.first_name }} {{ $json.data.last_name }}"

This pattern works for any CRM

. Swap the URLs and body structure for your system.

Testing Your Integration

Before running in production:

  1. Test with a sandbox/test account (Clio and ServiceNow provide these)
  2. Use n8n's "Execute Node" to test individual requests
  3. Check the "Binary Data" tab in n8n to see raw responses
  4. Verify rate limits by running 10 requests in quick succession
  5. Test error scenarios by using invalid IDs or malformed JSON

Create a test contact with email test+[timestamp]@yourfirm.com so you can identify and delete test data easily.

Common Mistakes to Avoid

Hardcoding IDs: Always use expressions like {{ $json.id }} instead of copying IDs from your CRM

. IDs change between environments.

Ignoring nested objects: Phone numbers, addresses, and custom fields are usually arrays of objects. You can't just pass a string.

Skipping field validation: CRMs

reject requests with invalid data types. If a field expects a number, send {{ parseInt($json.value) }}, not {{ $json.value }}.

Not handling duplicates: Most CRMs

don't prevent duplicate records. Always search before creating.

Forgetting timezones: Use ISO 8601 format for dates: {{ $now.toISO() }}. Don't send MM/DD/YYYY strings.

You now have everything needed to connect any REST API

-based CRM
to n8n. Start with read-only GET requests to verify authentication, then move to creating and updating records once you're confident in your setup.

Revenue Institute

Reviewed by Revenue Institute

This guide is actively maintained and reviewed by the implementation experts at Revenue Institute. As the creators of The AI Workforce Playbook, we test and deploy these exact frameworks for professional services firms scaling without new headcount.

Revenue Institute

Need help turning this guide into reality? Revenue Institute builds and implements the AI workforce for professional services firms.

RevenueInstitute.com