Leads in Google Sheets

A sheet that fills itself with your leads every 10 minutes. No other software needed.

  1. Make a new Google Sheet. Name the first tab Leads.
  2. Extensions, Apps Script. Replace the code with the script below and save.
  3. Project Settings (gear), Script Properties, add LEADS_KEY = your API key. A read-only key is enough.
  4. Run syncLeads once and approve access. Then Triggers (clock), Add Trigger, syncLeads, time-driven, every 10 minutes.
const BASE = 'https://zqawbbylldymdfzineqq.supabase.co/functions/v1/pro-api/v1/leads';
const COLS = ['id','received_at','updated_at','service','zip','status','accepted','declined','lead_type','contact_via','first_name','last_name','phone','email','price','details'];

function syncLeads() {
  const props = PropertiesService.getScriptProperties();
  const key = props.getProperty('LEADS_KEY');
  let since = props.getProperty('SINCE') || '2000-01-01T00:00:00Z';
  const sheet = SpreadsheetApp.getActive().getSheetByName('Leads');
  if (sheet.getLastRow() === 0) sheet.appendRow(COLS);
  const ids = sheet.getLastRow() > 1 ? sheet.getRange(2, 1, sheet.getLastRow() - 1, 1).getValues().map(r => r[0]) : [];
  let cursor = null;
  do {
    const url = BASE + (cursor ? '?cursor=' + encodeURIComponent(cursor) : '?since=' + encodeURIComponent(since));
    const res = UrlFetchApp.fetch(url, { headers: { Authorization: 'Bearer ' + key }, muteHttpExceptions: true });
    if (res.getResponseCode() === 429) return;
    if (res.getResponseCode() !== 200) throw new Error('Leads API ' + res.getResponseCode() + ': ' + res.getContentText());
    const data = JSON.parse(res.getContentText());
    data.leads.forEach(function (l) {
      const c = l.contact || {};
      const row = [l.id, l.received_at, l.updated_at, l.service, l.zip, l.status, l.accepted, l.declined, l.lead_type, l.contact_via,
        c.first_name || '', c.last_name || '', c.phone || '', c.email || '', (l.price_cents || 0) / 100, l.details];
      const at = ids.indexOf(l.id);
      if (at >= 0) sheet.getRange(at + 2, 1, 1, row.length).setValues([row]);
      else { sheet.appendRow(row); ids.push(l.id); }
    });
    cursor = data.has_more ? data.next_cursor : null;
    if (data.next_since) since = data.next_since;
  } while (cursor);
  props.setProperty('SINCE', since);
}

The sheet holds customer contact details: share it only with people who need them, and remove old rows when you no longer need them.

Apps Script web apps can receive webhooks but cannot read request headers, so they cannot check our signature. Use the polling script above for Sheets.