Automation
Automating Client Onboarding: From Contract Signature to Live Workspace in Minutes
A build log from a multi-client agency: how an n8n workflow replaced 2–4 hours of manual client setup with a single-trigger automation that provisions Google Drive, populates documents, and creates CRM records in under 5 minutes.
The problem
Every new client triggered the same sequence of manual work.
Create a Google Drive folder structure. Copy the master document templates into it. Open each document and replace placeholder text — client name, start date, contacts, deliverables — one field at a time. Create the CRM contact record. Set the intake fields. Send the welcome email with the Drive links. Tag the relevant team members.
Done manually, this ran 2–4 hours per client. Done under time pressure after a deal close — because the celebration has barely ended and the client is already expecting a response — it was error-prone. Wrong names in documents. Missing folders. CRM fields left blank. Deliverable descriptions that didn't match what was actually sold.
As the agency grew, onboarding became a recurring ops tax. The work wasn't creative. It was data entry with a high cost of error. It needed to go away.
The trigger
The automation starts the moment a contract is signed. The agency used PandaDoc for
contracts; PandaDoc fires a webhook on document.completed events.
The n8n webhook node receives the event and extracts the fields from the document data payload: client name, primary contact, email, phone, start date, package tier, assigned account manager. These fields come directly from the PandaDoc template variables — no manual parsing required.
// Fields extracted from PandaDoc webhook payload
const client = {
name: $json.data.fields.client_name.value,
contact: $json.data.fields.primary_contact.value,
email: $json.data.fields.client_email.value,
phone: $json.data.fields.client_phone.value,
startDate: $json.data.fields.start_date.value,
packageTier: $json.data.fields.package.value,
accountManager: $json.data.fields.account_manager.value,
};If your contracts aren't in PandaDoc, the same pattern works with any e-signature platform that supports webhooks (DocuSign, HelloSign, Dropbox Sign). The output is the same: a structured JSON object representing the new client.
The Drive folder structure
Each client gets a top-level folder under the agency's shared drive, organised by year:
Clients/
2026/
{ClientName}/
01 - Discovery & Strategy/
02 - Creative Assets/
03 - Reporting/
04 - Contracts & Invoices/
05 - Internal Notes/
The n8n Google Drive node creates folders via the Google Drive API. Create them
sequentially — the parent must exist before you can create children. A simple chain of
"Create Folder" nodes works for a fixed structure. If your folder hierarchy varies by
package tier, use a Switch node to route to the correct creation chain.
The client's email is added as an editor on the top-level folder automatically. This
grants them access to everything you want them to see (typically 02 - Creative Assets
and 03 - Reporting) while leaving internal folders unshared — which you handle by
adjusting permissions on those subfolders specifically.
Document population
Template documents live in a _Templates folder in the shared drive. The workflow
copies each template into the new client's folder, then opens it and replaces
placeholder text using the Google Docs API.
n8n's Google Docs node has a "Replace text in document" action that accepts a list of
find/replace pairs. The template uses {{placeholders}}:
const replacements = [
{ find: "{{CLIENT_NAME}}", replace: client.name },
{ find: "{{PRIMARY_CONTACT}}", replace: client.contact },
{ find: "{{START_DATE}}", replace: formatDate(client.startDate) },
{ find: "{{ACCOUNT_MANAGER}}", replace: client.accountManager },
{ find: "{{PACKAGE}}", replace: client.packageTier },
{ find: "{{CURRENT_YEAR}}", replace: new Date().getFullYear().toString() },
];Run the replacements on every document in the client's folder. The "Copy File" + "Replace Text" sequence runs once per template document. For a standard onboarding package with 4–5 template documents, the full document population step takes under 30 seconds.
One important note: Google Docs API batch updates are atomic per request. If you have
30 placeholder instances across a document, send them all in a single batchUpdate
call rather than 30 individual calls. n8n's Google Docs node does this automatically
when you pass an array of replacements.
CRM record creation
With the Drive workspace ready, the workflow creates the CRM record in GoHighLevel. A new contact is created with all intake fields pre-populated:
// POST /contacts
{
locationId: GHL_LOCATION_ID,
firstName: client.contact.split(' ')[0],
lastName: client.contact.split(' ').slice(1).join(' '),
email: client.email,
phone: client.phone,
tags: ['client', `package:${client.packageTier}`, 'onboarding'],
customFields: [
{ id: 'cf_start_date', value: client.startDate },
{ id: 'cf_account_manager', value: client.accountManager },
{ id: 'cf_drive_folder', value: driveFolderUrl },
{ id: 'cf_package_tier', value: client.packageTier },
]
}The driveFolderUrl — the URL of the newly created client folder — is stored in the
CRM record. Anyone who opens the contact in GHL can navigate directly to the client's
workspace with one click.
After creating the contact, the workflow creates a GHL opportunity in the "Active Clients" pipeline, sets the stage to "Onboarding", and assigns it to the account manager.
The welcome email
The final step sends the client a branded welcome email via Resend. The email includes:
- A welcome message personalised with the client's name and contact
- A direct link to their Google Drive workspace
- Their account manager's name and email
- Expected next steps and timeline
The email sends from a [email protected] address, not from an n8n-generated address.
Resend handles delivery with proper SPF/DKIM authentication.
// Email template (simplified)
const html = `
<h1>Welcome to the team, ${client.name}!</h1>
<p>Your workspace is ready. ${client.accountManager} will be in touch
within 24 hours to kick things off.</p>
<p><a href="${driveFolderUrl}">Access your client workspace →</a></p>
`;A Slack notification fires simultaneously to the #new-clients channel tagging
the account manager: "🎉 New client onboarded: {ClientName}. Workspace ready.
[Drive link] — assigned to @{accountManager}"
Validation and error handling
Between each major step, a validation check confirms the previous step completed successfully before proceeding:
- After folder creation: Verify the folder ID returned by Drive API is non-null
- After document population: Confirm document revision ID changed (indicates replacements were written)
- After CRM creation: Verify contact ID returned; store it for the opportunity creation step
- After email send: Check Resend returned 200; log the message ID
If any step fails, an Error Trigger node fires a Slack alert to the ops channel
with the specific step that failed, the client name, and the raw error message. The
partially completed steps are logged — most of the time, a human can complete the
remaining steps manually in under 10 minutes because the automation got most of the
way there.
The result
New clients now have a fully configured workspace within 5 minutes of signing.
The ops team's involvement dropped from 2–4 hours per client to reviewing the
#new-clients Slack notification and optionally following up with a personal call.
Document accuracy improved to near-100% because population is automated from a single source of truth: the signed contract. No more wrong names, missing start dates, or placeholder text that was never replaced.
The workflow has handled every client onboarding since deployment without an ops team member touching it. One exception: a client signed with a company name that contained a special character that broke the folder name. The error fired a Slack alert within seconds, a team member manually created that folder, and the client never noticed.
What I'd add next
Conditional folder structures by package. Right now all clients get the same
folder hierarchy. A basic package client doesn't need a 02 - Creative Assets folder.
A Switch node routing to different folder chains based on packageTier would clean
this up.
Automatic task creation in ClickUp. The onboarding checklist — kickoff call scheduled, credentials shared, campaign brief sent — could auto-populate as ClickUp tasks assigned to the account manager. The n8n ClickUp node supports task creation; it's a natural extension of this workflow.
Contract data via API instead of webhook. PandaDoc's webhook delivers contract
data, but not all fields are always included. Calling GET /documents/{id}/fields
after the webhook fires gives you the full field set with guaranteed completeness.
Related Reading
Build Log
Why AI Wrappers Won't Kill Your n8n Workflows (and When to Go Agentic)
A practical decision playbook for ops leaders facing the agentic coding vs. n8n debate.
Build Log
Your Ops Team Doesn't Need Another AI Agent—It Needs Boring Automations
Why the most useful automations are boring—and how to find, build, and price them.