Ayush Paul
Back to Blog

How I Automated My Entire Client Onboarding Process

avatarAyush PaulOctober 29, 2025 (2w ago)8 min read

After 5 years of freelancing, I've onboarded maybe 200+ clients. Each time, the same boring process: copy details into CRM, create customer in accounting software, generate invoice, send personalized email. Rinse and repeat.

It was eating up hours every week. Hours I could spend actually working on projects or finding new clients.

So I finally sat down and automated the whole thing. Now when a client fills out my onboarding form, everything happens automatically. Deal created. Contact added. Invoice generated and sent. All in under 2 seconds.

Here's exactly how I built it.

The Old Way (That Was Killing My Productivity)

Every new client meant:

  1. Open their form submission email
  2. Log into Zoho CRM, manually create a deal with their company name, amount, closing date
  3. Switch to Zoho Books, create a new contact with all their details
  4. Create an invoice, select the right service item, add the amount
  5. Write a personalized email and send the invoice
  6. Hope I didn't miss anything or make a typo

Time taken? 30 minutes minimum. If I got distracted or had to handle multiple clients, easily 2 hours.

That's 2 hours of pure administrative work. Not client work. Not creative work. Just data entry.

The New Way (That Runs While I am Drinking Chai!)

Now? Client fills form → everything happens automatically → they receive their invoice email.

I built this using n8n, which is like Zapier but way more powerful and you can self-host it. The workflow connects to both Zoho CRM and Zoho Books, handling everything from deal creation to invoice delivery.

Let me walk you through each piece.

The Workflow Breakdown

Webhook: Where It All Starts

Endpoint: /client-onboarding
Method: POST
Response Mode: Wait for workflow completion

This creates a URL that receives form submissions. I connected it to my Webflow contact form. When someone submits, it hits this endpoint with all their data.

The key setting here is "responseNode" mode. This means the form doesn't respond immediately. It waits for the entire workflow to finish first. This way, clients get a confirmation only after their invoice is actually sent.

Respond to Form: The Confirmation

Simple but crucial. This sends back a success response to the form. Without this, the form would just hang there loading forever. Not a good first impression for a new client.

It's set to respond with all incoming items, which basically echoes back the data they submitted as confirmation.

Creating the Deal in Zoho CRM

I use the native Zoho CRM node for this. It creates a new deal with:

  • Deal Name: Company Name - First Name Last Name (makes it easy to scan my pipeline)
  • Stage: "Proposal/Price Quote" (my starting stage for all new leads)
  • Amount: From the form
  • Closing Date: The due date they selected

This automatically adds the client to my sales pipeline. I can track their progress, set follow-up tasks, and see everything in one place.

Creating the Contact in Zoho Books (HTTP Node)

This is where things get a little technical. There are no native Zoho Books nodes. So I use an HTTP Request node to hit the Zoho Books API directly.

Request Setup:

Method: POST
URL: https://www.zohoapis.in/books/v3/contacts
Authentication: OAuth2 (custom credential)

Query Parameter:

organization_id: XXXXXXXXXXX;

This tells Zoho Books which organization to create the contact under. If you run multiple businesses through Zoho, this is essential.

Headers:

Content-Type: application/json

The JSON Payload:

{
  "contact_name": "{{ $json.body['First Name'] }}",
  "company_name": "{{ $json.body['Company Name'] }}",
  "contact_type": "customer",
  "contact_persons": [
    {
      "first_name": "{{ $json.body['First Name'] }}",
      "last_name": "{{ $json.body['Last Name'] }}",
      "email": "{{ $json.body.Email }}",
      "phone": "{{ $json.body['Phone Number'] }}",
      "is_primary_contact": true,
      "outstanding_receivable_amount": "{{ $json.body.Amount }}"
    }
  ]
}

The contact_persons array is critical. This creates the actual person record linked to the company. I set is_primary_contact to true because this is who I'll be communicating with.

The outstanding_receivable_amount automatically shows what they owe in my accounting dashboard. Game changer for tracking payments.

Why HTTP Nodes?

I've learned that HTTP nodes give you way more control:

1. Full API Access Native nodes only expose common features. HTTP nodes let you use the entire API. For Zoho Books, I need to create contacts with specific structures that the native node doesn't support.

2. Complex Data Structures That contact_persons array? Impossible with the native node. HTTP lets me send exactly the JSON structure Zoho expects.

3. Future-Proofing When Zoho updates their API, I just update my JSON. Don't have to wait for n8n to update their node.

4. Debugging HTTP responses show you exactly what went wrong. Native nodes sometimes fail silently or give vague errors.

How I Set Up HTTP Nodes:

Authentication: I use OAuth2 with a custom credential. This handles token refresh automatically. Setting it up takes 10 minutes but then you never think about it again.

Query Parameters: Never hardcode them in the URL. Use the dedicated query parameters section. Makes debugging way easier.

Body Structure: Use n8n's expression syntax ={{ }} to pull data from previous nodes. This evaluates the expression and inserts the actual value.

Error Handling: HTTP nodes fail loudly if the API returns an error. This is good. You want to know immediately if something's wrong, not discover it weeks later.

Creating the Invoice (Another HTTP Node)

Method: POST
URL: https://www.zohoapis.in/books/v3/invoices

The Payload:

{
  "customer_id": "{{ $json.contact.contact_id }}",
  "due_date": "{{ $('Respond to Form').item.json.body['Due Date'] }}",
  "line_items": [
    {
      "item_id": "{{ $('Respond to Form').item.json.body['Item ID (Zoho Books)'] }}",
      "quantity": 1,
      "rate": "{{ $('Respond to Form').item.json.body.Amount }}"
    }
  ],
  "notes": "{{ $('Respond to Form').item.json.body['Project Description'] || 'No additional notes' }}",
  "send": true
}

Key points:

The customer_id comes from the previous node's output: {{ $json.contact.contact_id }}. This links the invoice to the contact we just created.

The item_id comes from the form. I have pre-configured items in Zoho Books like "Web Development", "SEO Services", "Design Package". The client selects their service, and the form sends the corresponding item ID.

Setting "send": true marks the invoice as sent immediately, which updates its status in Zoho Books.

Referencing Previous Nodes:

Notice the syntax $('Respond to Form').item.json.body.Email. This is n8n's way of grabbing data from previous nodes. The $('Node Name') syntax is incredibly powerful. You can reference any node's output from any other node.

I reference the "Respond to Form" node instead of the current node because I need the original form data, not the transformed data from intermediate nodes.

Sending the Invoice Email (Final HTTP Node)

Method: POST
URL: https://www.zohoapis.in/books/v3/invoices/{{ $json.invoice.invoice_id }}/email

The URL uses the invoice ID from the previous step. This tells Zoho which invoice to send.

Email Structure:

{
  "to_mail_ids": ["{{ $('Respond to Form').item.json.body.Email }}"],
  "cc_mail_ids": [],
  "subject": "Your Project Invoice - {{ $('Respond to Form').item.json.body['Company Name'] }}",
  "body": "Dear {{ $('Respond to Form').item.json.body['First Name'] }} {{ $('Respond to Form').item.json.body['Last Name'] }},<br><br>Thank you for choosing us. Please find your invoice attached for the amount of ₹{{ $('Respond to Form').item.json.body.Amount }} due on {{ $('Respond to Form').item.json.body['Due Date'] }}.<br><br>Project Description: {{ $('Respond to Form').item.json.body['Project Description'] || 'N/A' }}<br><br>Best regards,<br>Ayush Paul"
}

The email is completely personalized with their name, company, amount, and project details. It looks like I wrote it manually 😂.

The Real Impact

Time saved per client: 28–40 minutes Clients per month: 8–15 Time saved per month: 3.7–10 hours

Time I can spend on actual client work or business development.

But the real win isn't just time. It's mental energy. I'm not context-switching between tabs. I'm not worrying about forgetting a step. I'm not manually typing the same email over and over.

It's one less thing to think about. And when you're freelancing.

This is just a peek, the automation i made covers a lot more than this

  • Checking for existing customers: Query Zoho Books first to see if the email already exists if yes then create a new invoice under the same contact
  • Human intervention: Ask for any missing data or confirm the invoice that was created
  • Conditional logic: Different invoice templates based on service type
  • Error notifications: Send myself a Slack message if any step fails
  • Backup logging: Write every submission to a Google Sheet as backup
  • Follow-up automation: Create a task to check in after 7 days

And much more.....

Setting This Up Yourself

You'll need:

  • n8n instance (I use the cloud version, but self-hosted works too)
  • Zoho CRM account
  • Zoho Books account
  • OAuth2 credentials for both Zoho services

The OAuth setup is the hardest part. Zoho makes you create a "Connected Application" in their api console, get client ID and secret, set redirect URLs. Takes about 10 minutes if you follow their docs. You can visit API console to get the credentials.

After that, the workflow itself takes maybe an hour or two to build and test. But that hour saves you hundreds of hours over the year.

Final Thoughts

Automation isn't about replacing human work. It's about eliminating the boring, repetitive stuff so you can focus on the work that actually matters.

I've been freelancing since 2019. For the first 4 years, I did everything manually.

I wish I would built this 3 years ago. I would have saved literal weeks of my life.

If you're doing any repetitive process more than twice a week, automate it. Your future self will thank you.

Or you can book a meeting with me to discuss if you wantsome one to handle all of these for you. Book a meeting

Contact

Get in Touch

Got an idea you want to bring to life? Just shoot me a dm directly on linkedin or drop me an email and I'll respond as soon as possible

Ayush Paul © 2025