Most African SMEs take 2โ3 days to fully onboard a new client. Document collection goes back and forth on WhatsApp. The contract lives in a Word file on someone's desktop. The ID gets checked manually โ or not at all. By the time everything is signed, the client's enthusiasm has cooled.
This guide shows you exactly how to compress that entire process into under 24 hours โ using tools you already have: WhatsApp, Google Sheets, and Google Apps Script. No paid software. No technical degree required.
What you'll build: A WhatsApp bot that collects client details, validates their SA ID, auto-generates a contract, sends it for signature, and notifies you when it's complete โ all while you sleep.
Why most onboarding processes are broken
Here's the typical flow for a professional services business in South Africa:
- Client messages on WhatsApp
- You reply manually (often hours later)
- You ask them to email documents
- They email some, forget others
- You follow up 2โ3 times
- You manually copy their ID number into a spreadsheet
- You create a contract in Word, attach their details
- You send the PDF via email
- You wait for a signature
- You file everything manually
That's 10 steps, most of which require your personal attention. If you're onboarding 10 clients a month, that's a part-time job just in admin.
The problem isn't that you're slow โ it's that the process has no automation built into it. Every step requires a human decision. We're going to change that.
The 5-step automated onboarding flow
Here's what your new flow will look like after you follow this guide:
โ Average time from first message to signed contract using this system: under 4 hours. Compared to the industry average of 2โ3 days.
What you need to build this
- WhatsApp Business API account โ we recommend 360dialog (starts at ~R180/month for African businesses)
- Google account โ for Sheets, Drive, and Apps Script (free)
- A contract template โ your existing Word or Google Doc contract works
- About 3โ5 hours to set up โ or 5 days if NanoLeap builds it for you
Note on WhatsApp Business API: The free WhatsApp Business App cannot run automated flows. You need API access. 360dialog is the most affordable SA-friendly option. Meta (Facebook) also offers direct access but requires more setup.
Step 1: Set up your Google Sheet
Create a new Google Sheet with the following columns. This is your client database โ every onboarding will log here automatically.
Column A: Timestamp
Column B: WhatsApp Number
Column C: Full Name
Column D: Company Name
Column E: Email Address
Column F: SA ID Number (restricted access โ see POPIA note below)
Column G: Service Requested
Column H: Status (INTAKE / VERIFIED / CONTRACT_SENT / ONBOARDED)
Column I: Contract URL
Column J: Owner Notified (Yes/No)POPIA note: Restrict access to Column F (SA ID numbers) to yourself only. In Google Sheets, right-click the column header โ "Protect range" โ add only your email as an editor. SA ID numbers are personal information under POPIA and must be stored with appropriate access controls.
Step 2: Create your contract template in Google Docs
Take your existing contract and create a Google Doc version. Replace the client-specific fields with placeholders in double curly braces:
This agreement is entered into between {{BUSINESS_NAME}} ("Service Provider")
and {{CLIENT_NAME}} ("Client"), ID number {{CLIENT_ID}},
on {{DATE_TODAY}}.
Email: {{CLIENT_EMAIL}}
Service requested: {{SERVICE_TYPE}}
...Note the Google Doc ID from its URL โ you'll need it in the next step. The URL looks like: docs.google.com/document/d/DOCUMENT_ID_HERE/edit
Step 3: Write the Apps Script
In your Google Sheet, go to Extensions โ Apps Script. This is where the automation lives. Here's the core logic for the ID validation function โ the most technically interesting part:
// SA ID Luhn validation
function validateSAID(idNumber) {
if (!/^\d{13}$/.test(idNumber)) return false;
// Extract date of birth
const year = parseInt(idNumber.substring(0, 2));
const month = parseInt(idNumber.substring(2, 4));
const day = parseInt(idNumber.substring(4, 6));
if (month < 1 || month > 12) return false;
if (day < 1 || day > 31) return false;
// Luhn algorithm check
let sum = 0;
for (let i = 0; i < 12; i++) {
let digit = parseInt(idNumber[i]);
if (i % 2 !== 0) {
digit *= 2;
if (digit > 9) digit -= 9;
}
sum += digit;
}
const checkDigit = (10 - (sum % 10)) % 10;
return checkDigit === parseInt(idNumber[12]);
}This function checks that the ID number has the right format, a valid date of birth, and passes the Luhn checksum โ the same algorithm used by South African banks to verify ID numbers.
Step 4: Handle the WhatsApp conversation flow
Your WhatsApp API will send a webhook to your Apps Script whenever a message arrives. Here's the state machine โ the logic that decides what question to ask next based on where the client is in the flow:
function handleIncomingMessage(phoneNumber, messageText) {
const sheet = SpreadsheetApp.getActiveSheet();
const row = findOrCreateClientRow(phoneNumber);
const status = sheet.getRange(row, 8).getValue(); // Column H: Status
if (status === '' || status === 'NEW') {
// First message โ start intake
sendWhatsApp(phoneNumber,
"Hi! ๐ I'm the NanoLeap assistant. Let's get you set up.\n\n" +
"First, what's your full name?"
);
sheet.getRange(row, 8).setValue('ASKED_NAME');
} else if (status === 'ASKED_NAME') {
sheet.getRange(row, 3).setValue(messageText); // Column C: Name
sendWhatsApp(phoneNumber, `Thanks ${messageText}! What's your company name?`);
sheet.getRange(row, 8).setValue('ASKED_COMPANY');
} else if (status === 'ASKED_COMPANY') {
sheet.getRange(row, 4).setValue(messageText); // Column D: Company
sendWhatsApp(phoneNumber, "What's your email address?");
sheet.getRange(row, 8).setValue('ASKED_EMAIL');
} else if (status === 'ASKED_EMAIL') {
sheet.getRange(row, 5).setValue(messageText); // Column E: Email
sendWhatsApp(phoneNumber, "And your SA ID number? (13 digits)");
sheet.getRange(row, 8).setValue('ASKED_ID');
} else if (status === 'ASKED_ID') {
if (validateSAID(messageText)) {
sheet.getRange(row, 6).setValue(messageText); // Column F: ID
sheet.getRange(row, 8).setValue('VERIFIED');
generateAndSendContract(row, phoneNumber);
} else {
sendWhatsApp(phoneNumber,
"That ID number doesn't look right. Please check and try again.");
}
}
}Step 5: Auto-generate and send the contract
When the ID is verified, the system automatically creates a personalised contract PDF:
function generateAndSendContract(row, phoneNumber) {
const sheet = SpreadsheetApp.getActiveSheet();
const clientName = sheet.getRange(row, 3).getValue();
const companyName = sheet.getRange(row, 4).getValue();
const clientId = sheet.getRange(row, 6).getValue();
const clientEmail = sheet.getRange(row, 5).getValue();
const templateId = 'YOUR_GOOGLE_DOC_TEMPLATE_ID_HERE';
const templateDoc = DriveApp.getFileById(templateId);
const newDoc = templateDoc.makeCopy(`Contract - ${clientName}`);
// Replace placeholders
const doc = DocumentApp.openById(newDoc.getId());
const body = doc.getBody();
body.replaceText('{{CLIENT_NAME}}', clientName);
body.replaceText('{{BUSINESS_NAME}}', companyName);
body.replaceText('{{CLIENT_ID}}', clientId);
body.replaceText('{{DATE_TODAY}}', new Date().toLocaleDateString('en-ZA'));
doc.saveAndClose();
// Convert to PDF and store link
const pdfBlob = DriveApp.getFileById(newDoc.getId()).getAs('application/pdf');
const pdfFile = DriveApp.createFile(pdfBlob);
const pdfUrl = pdfFile.getUrl();
sheet.getRange(row, 9).setValue(pdfUrl); // Column I: Contract URL
sheet.getRange(row, 8).setValue('CONTRACT_SENT');
// Send to client
sendWhatsApp(phoneNumber,
`Hi ${clientName}! Your service agreement is ready:\n\n` +
`๐ ${pdfUrl}\n\n` +
`Please review and reply *I AGREE* to confirm your signature and complete your onboarding.`
);
}Common issues and how to fix them
Client doesn't respond to intake questions
Set up a stall detection function that fires after 2 hours, 24 hours, and 72 hours of inactivity. Each follow-up message should be warmer and more personal. After 72 hours with no response, notify the owner to follow up personally.
ID validation fails for a valid ID
Common causes: spaces in the 13-digit number, a dash included, or a typo. The bot should strip spaces and dashes automatically before validating. You can also add a fallback: after 2 failed attempts, send the client your direct contact to complete manually.
Contract template not filling correctly
Make sure your placeholder text matches exactly โ including the double curly braces and the exact same capitalisation as in your code. Google Docs replaceText is case-sensitive.
What this system can't do (yet)
This guide covers the foundation. A full production system also needs:
- Error handling and logging โ what happens if the API is down, if Drive is full, if a message fails to send
- POPIA-compliant data deletion โ automated purging of ID numbers after retention period
- Multi-service routing โ different contracts for different services
- Integration with your accounting software โ creating the client invoice automatically
- Stall detection sequences โ the follow-up timing logic
Want us to build this for you?
The full production system โ with all the edge cases handled, POPIA-compliant, tested and live in 5 days. You only pay after delivery.
Book a Free Audit โThe bottom line
Client onboarding is the first impression your business makes. A 3-day manual process tells clients that your operations are slow. A 24-hour automated process tells them you're professional, efficient, and ready to deliver.
The good news: you don't need expensive software to do this. WhatsApp Business API, Google Sheets, and Apps Script are either free or nearly free. The investment is in the setup โ and this guide gives you the foundation.
If you want the full production version without the setup time, book a free audit. We'll tell you exactly what we'd build for your specific business and what it would cost. You pay nothing until it's live and working.