Integrations

WhatsApp API with Google Sheets: send from the spreadsheet and log replies

With Google Apps Script, a spreadsheet sends messages through the D-API WhatsApp API and receives replies back on another tab. Sending uses UrlFetchApp, receiving uses a Web App with doPost, and none of it depends on a ready-made connector.

By D-API engineering team5 min read

When the spreadsheet is the right tool

A lot of small operations live in Google Sheets: the class roster, customers with an installment coming due, event attendees. Sending WhatsApp messages to that list from the spreadsheet itself avoids exporting a CSV to another tool and keeps track of who already got the message in the same place.

There is no native integration between D-API and Google Sheets. The connection is made with the Apps Script that comes with every spreadsheet: it calls the WhatsApp API over HTTP and publishes a URL to receive the webhook. It works well for dozens or a few hundred sends per day. Beyond that, Apps Script limits start to get in the way, and we cover them below.

Set up the spreadsheet before the code

Create two tabs. The first, called Sends, with these columns in row 1:

ColumnContentExample
A: phoneDigits only, with country code14155550123
B: nameFirst name for personalizationCarla
C: messageText to sendYour workshop registration is confirmed.
D: optinYES when the person agreedYES
E: statusFilled in by the scriptsent
F: sent_atFilled in by the scriptdate and time

The second tab, Replies, receives what people write back. Then open Extensions, Apps Script and, under Project Settings, create the script properties DAPI_KEY with your API key and DAPI_SESSION with the connection ID.

Send with UrlFetchApp

The function below walks the Sends tab, skips anyone without opt-in or who already got the message, sends in small batches and records the result of each row. muteHttpExceptions turns an error into text in the spreadsheet instead of crashing the whole run:

const BATCH = 40 // fits comfortably within a 6-minute execution

function sendPending() {
  const props = PropertiesService.getScriptProperties()
  const sheet = SpreadsheetApp.getActive().getSheetByName('Sends')
  const rows = sheet.getDataRange().getValues()
  let sent = 0

  for (let i = 1; i < rows.length && sent < BATCH; i++) {
    const [phone, name, message, optin, status] = rows[i]
    if (optin !== 'YES' || status) continue

    const resp = UrlFetchApp.fetch('https://api.d-api.cloud/api/v1/messages/send/text', {
      method: 'post',
      contentType: 'application/json',
      headers: { Authorization: props.getProperty('DAPI_KEY') },
      payload: JSON.stringify({
        sessionId: props.getProperty('DAPI_SESSION'),
        to: String(phone),
        text: 'Hi ' + name + '! ' + message,
      }),
      muteHttpExceptions: true,
    })

    const ok = resp.getResponseCode() < 300
    sheet.getRange(i + 1, 5, 1, 2).setValues([[ok ? 'sent' : 'error ' + resp.getContentText(), new Date()]])
    sent++
    Utilities.sleep(4000 + Math.random() * 4000) // irregular interval between sends
  }
}

To run it automatically, create a time-driven trigger under Triggers that calls sendPending every 15 minutes. Each run takes the next batch of rows with no status, so a large list is worked through over the day without exceeding the time limit of a single run.

Receive replies with doPost

In the same project, add the function Google calls when someone POSTs to the Web App URL. It logs only messages received from contacts, outside groups, and ignores repeated IDs:

function doPost(e) {
  const event = JSON.parse(e.postData.contents)
  const d = event.data
  if (event.event !== 'messages.received' || d.fromMe || d.is_group) return ok()

  const cache = CacheService.getScriptCache()
  if (cache.get(d.id)) return ok()
  cache.put(d.id, '1', 21600) // 6 hours

  SpreadsheetApp.getActive().getSheetByName('Replies').appendRow([
    new Date(d.timestamp),
    d.from.jid.split('@')[0],
    d.from_name,
    d.type === 'text' ? d.message : '[' + d.type + ']',
  ])
  return ok()
}

function ok() {
  return ContentService.createTextOutput('ok')
}

Publish under Deploy, New deployment, type Web app, executing as you and with access for anyone, since D-API calls the URL without a Google login. Copy the URL ending in /exec and register it as the connection’s webhook:

curl -X POST https://api.d-api.cloud/api/v1/sessions/YOUR_SESSION/webhook \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "webhookUrl": "https://script.google.com/macros/s/YOUR_ID/exec" }'

Keep the Web App URL private. To go further, check in doPost that the event’s sessionId matches your connection. The full event format is in the WhatsApp webhooks guide.

Apps Script quotas and volume

  • Time per execution: 6 minutes. That is why sending happens in batches with a trigger, not in a loop over the whole spreadsheet.
  • UrlFetch calls: on a personal account, 20,000 per day, counting everything your scripts do. Google Workspace accounts have a higher quota.
  • Triggers: personal accounts have a daily limit on total trigger runtime. Long intervals between sends consume that time.
  • Concurrent writes: if many replies arrive at once, parallel appendRow calls can slow down. At high volume, the spreadsheet stops being the right database.

The limit that matters most, though, is WhatsApp’s. Irregular intervals between sends, personalized messages and sending only to people who asked are what keep the number alive. The topic has its own guide: how to avoid bans.

When to move off the spreadsheet

If the list has grown past a few thousand contacts or the send needs to happen the second something changes in your system, the spreadsheet has become a bottleneck. For large, recurring sends, see bulk messaging with the API. For date-driven reminders, such as due dates, the payment reminders guide shows the design with the source system. And if you prefer visual, no-code flows, n8n combines the D-API node with the Google Sheets node it already ships with.

Frequently asked questions

Do I need an extension or add-on in Google Sheets?
No. Everything runs in Google Apps Script, which comes with the spreadsheet under Extensions, Apps Script. Sending uses UrlFetchApp to call the D-API API, and receiving uses a Web App with the doPost function.
How many messages can I send per day from the spreadsheet?
The practical limit comes from Apps Script, not the spreadsheet: on a personal Google account the current quota is 20,000 UrlFetch calls per day and each execution stops at 6 minutes. Check the official quota table, because Google revises these numbers.
Where do I store the API key securely?
In the script properties, under Project Settings. That way the key is not in a cell anyone with access to the spreadsheet can read, nor in code someone can copy.
Why did a reply show up twice on the replies tab?
Because D-API retries when it does not get an acknowledgment in time, and the Web App can be slow. The example on this page stores each message ID in the script cache for a few hours and ignores repeats.
Can I message a contact list I bought?
You should not. Messages to people who did not ask for them get reported, and reports get the number blocked. Send only to people who agreed to be contacted on WhatsApp and record that consent in the spreadsheet itself.

Try D-API's WhatsApp API

3-day trial with full access. No credit card, no lock-in.