Google Sheet
This document explains how to set up Google Apps Script so your Google Sheet can automatically send data into CDP whenever certain cells are updated or when a new row is added.
You will use a simple trigger (onEdit) to automatically run a script whenever a cell in your Google Sheet is edited. This allows the system to detect changes in specific columns or detect new rows, then send an event to your API/webhook.
1. Open the Apps Script Editor
In your Google Sheet, go to Extensions in the top menu.
Select Apps Script.
A new tab will open with the Apps Script code editor.

2. Write Your Apps Script
Replace the existing content in Code.gs with the script below.
/**
* Computes the MD5 hash of a given string.
*
* This helper wraps Google Apps Script's Utilities.computeDigest()
* and returns a lowercase hexadecimal MD5 hash.
*
* @param {string} str - The input string to hash.
* @return {string} The MD5 hash in hex format.
*/
function _cdpMD5(str) {
var bytes = Utilities.computeDigest(
Utilities.DigestAlgorithm.MD5,
str
);
var hex = '';
for (var i = 0; i < bytes.length; i++) {
var v = (bytes[i] + 256) % 256;
hex += ('0' + v.toString(16)).slice(-2);
}
return hex;
}
/**
* Global script configuration.
*
* @property {string} SHEET_NAME - Name of the sheet to monitor, or '*' for any sheet.
* @property {number} HEADER_ROW - Row index of the header row.
* @property {string} LOG_SHEET_NAME - Name of the log sheet for event tracking.
* @property {boolean} MUTUAL_EXCLUSIVE - Placeholder for future logic (unused).
* @property {string[]} MONITORED_HEADERS - Column headers that trigger events on edit.
*/
const CONFIG = {
SHEET_NAME: 'Sheet1',
HEADER_ROW: 1,
LOG_SHEET_NAME: 'Event_Log',
MUTUAL_EXCLUSIVE: false,
MONITORED_HEADERS: ['Status', 'Name', 'Phone']
};
/**
* Trigger function fired when a user edits the spreadsheet.
*
* Detects:
* - Direct edits to monitored columns → COLUMN_CHANGED
* - Completion of all monitored columns → ROW_READY
*
* Logs the event and sends an event payload to an external API.
*
* @param {GoogleAppsScript.Events.SheetsOnEdit} e - The onEdit() event object.
*/
function onEdit(e) {
try {
if (!e) return;
const range = e.range;
const sheet = range.getSheet();
// Ignore edits outside configured sheet
if (CONFIG.SHEET_NAME !== '*' && sheet.getName() !== CONFIG.SHEET_NAME) return;
// Ignore header row
if (range.getRow() <= CONFIG.HEADER_ROW) return;
const headers = sheet.getRange(CONFIG.HEADER_ROW, 1, 1, sheet.getLastColumn()).getValues()[0];
// Get column indices for monitored headers
const monitoredCols = CONFIG.MONITORED_HEADERS
.map(h => headers.indexOf(h) + 1)
.filter(colIdx => colIdx > 0);
if (monitoredCols.length === 0) return;
const editedCol = range.getColumn();
const row = range.getRow();
const rowValues = sheet.getRange(row, 1, 1, sheet.getLastColumn()).getValues()[0];
let eventType = null;
let meta = {};
// Detect individual monitored column change
if (range.getNumRows() === 1 && range.getNumColumns() === 1 &&
monitoredCols.includes(editedCol)) {
const newValue = e.value;
const oldValue = e.oldValue;
eventType = 'COLUMN_CHANGED';
meta = {
editedColumnHeader: headers[editedCol - 1],
oldValue: oldValue,
newValue: newValue
};
/**
* Optional: Mutual exclusive behavior for checkbox-like columns.
*
* When enabled (CONFIG.MUTUAL_EXCLUSIVE === true), if the user checks a
* monitored checkbox column, all other monitored columns in the same row
* will be unchecked automatically.
*
* This behaves like radio-button style selection but using checkboxes.
*/
if (CONFIG.MUTUAL_EXCLUSIVE && (newValue === 'TRUE' || newValue === true)) {
monitoredCols.forEach(col => {
if (col !== editedCol) {
const otherCell = sheet.getRange(row, col);
// Uncheck other monitored checkboxes
if (otherCell.isChecked && otherCell.isChecked()) {
otherCell.setValue(false);
}
}
});
}
}
// Detect completion of all monitored columns
const monitoredValues = monitoredCols.map(c => rowValues[c - 1]);
const allFilled =
monitoredValues.length > 0 &&
monitoredValues.every(v => v !== '' && v !== null && v !== undefined);
if (!eventType && allFilled) {
eventType = 'ROW_READY';
meta = { completedColumns: CONFIG.MONITORED_HEADERS };
}
if (!eventType) return;
// Send event to API
sendRowEvent_(headers, rowValues, {
eventType,
rowNumber: row,
meta
});
} catch (err) {
Logger.log(err);
}
}
/**
* Sends data about the edited row to an external API endpoint.
*
* This includes:
* - Event category & action
* - MD5-based UID
* - Customer dimensions (hashed phone)
* - All row data in "extra"
*
* @param {string[]} headers - Column headers from the sheet.
* @param {Array} rowValues - Values from the edited row.
* @param {Object} context - Event metadata such as type and row number.
*/
function sendRowEvent_(headers, rowValues, context) {
const EVENT_ENDPOINT_URL =
'https://a.cdp.asia/event?resp_type=json&is_server=true&portal_id={{PORTAL_ID}}&prop_id={{PROP_ID}}';
const data = {};
headers.forEach((h, idx) => { if (h) data[h] = rowValues[idx]; });
const phone = data['Phone'] || '';
const name = data['Name'] || '';
const customerId = phone ? _cdpMD5(phone) : null;
var apiDataDims = {
customers: {
customer_id: customerId,
name: name,
phone: phone
}
};
var options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify({
ec: '', // UPDATE VALUE HERE
ea: '', // UPDATE VALUE HERE
uid: _cdpMD5(phone),
dims: apiDataDims,
items: [],
extra: data
})
};
try {
const response = UrlFetchApp.fetch(EVENT_ENDPOINT_URL, options);
const responseCode = response.getResponseCode();
const responseBody = response.getContentText();
console.log('Response Code:', responseCode);
console.log('Response Body:', responseBody);
} catch (e) {
console.log('Error calling API:', e.message);
}
}
3. Create a trigger for onEdit
onEditIn the Apps Script editor, open the left sidebar and click Triggers (⏰ icon).
Click the “+ Add Trigger” button in the bottom-right corner.
In the popup, configure:
Choose which function to run:
onEditChoose which deployment should run:
HeadSelect event source:
From spreadsheetSelect event type:
On edit
Click Save.
From now on, every time a user edits the sheet:
Google Sheets fires the on edit event.
The trigger calls your
onEdit(e)function.Your script checks which row/column changed and decides whether to send an event to your API/CDP.

4. Verify that everything works
Go back to your Google Sheet.
Edit one of the columns that your script is watching (for example: update a status or tick a payment checkbox).
Return to Apps Script → Executions (left sidebar):
Check that there is a new successful run of
onEdit.
Check log in CDP
Verify that the event was received with the correct row data.
Last updated