DEV Community

Ted
Ted

Posted on • Originally published at tedagentic.com

How I Wired Google Search Console to Telegram on Day One

Most people check Google Search Console by logging in, navigating to the property, clicking through the date filters, and reading off numbers. That works — but you only do it when you remember, and you only see what you think to look for.

This is a different approach. The GSC data comes to you, every morning, in the same Telegram thread as every other system alert. No browser, no login, no forgetting. And because it's API-driven, you pull exactly what you need — top queries, top pages, position changes — formatted the way you want it, not the way Google's UI presents it.

I wired this up on day three of launching tedagentic.com — before a single organic click. The Telegram delivery layer was already in place from the agent setup. Adding GSC to it was the natural next step: one more data source piped into the same channel.

Building it before any traffic exists means the baseline is captured from zero. When the first impression shows up, it's logged. When something drops later, there's something to measure against. Most monitoring gets built reactively — after something falls. This flips that.

This is what I built and how it works.


The stack

Google Search Console API
        ↓
  Node.js script (monitor.js)
        ↓
  cron (8:25am daily)
        ↓
  Telegram bot → phone
Enter fullscreen mode Exit fullscreen mode

No dashboard. No third-party service. No browser required. The briefing arrives in the same Telegram thread as every other system alert — rankings, RAM, errors, competitor movements. One channel, everything piped in.


Step 1: GSC service account

I already had a Google service account from an earlier project. If you're starting from scratch, the setup is:

  1. Go to console.cloud.google.com → create a project
  2. Enable the Google Search Console API
  3. IAM & Admin → Service Accounts → Create service account
  4. Download the JSON credentials key

The credentials file looks like this:

{
  "type": "service_account",
  "project_id": "your-project-id",
  "client_email": "your-account@your-project.iam.gserviceaccount.com",
  "private_key": "..."
}
Enter fullscreen mode Exit fullscreen mode

Save it somewhere secure. Mine lives at ~/.gsc-credentials.json.

Then go to Google Search Console, open your property, and add the client_email as a user with Full permission. The service account won't show in the autocomplete — paste the email directly and save.

One thing to watch: GSC doesn't always confirm the save visually. Go back to Users and permissions and verify the email is listed before moving on. If it's not there, add it again.


Step 2: The monitor script

The script pulls three things from the Search Console API: 7-day totals, top pages, and top queries. Then formats it and sends to Telegram.

const { google } = require('googleapis');
const https = require('https');

const GSC_SITE = 'sc-domain:yourdomain.com';
const CREDENTIALS_PATH = '/home/user/.gsc-credentials.json';
const TELEGRAM_TOKEN = 'YOUR_BOT_TOKEN';
const TELEGRAM_CHAT_ID = 'YOUR_CHAT_ID';

async function query(sc, requestBody) {
  const res = await sc.searchanalytics.query({ siteUrl: GSC_SITE, requestBody });
  return res.data.rows || [];
}

async function run() {
  const auth = await new google.auth.GoogleAuth({
    keyFile: CREDENTIALS_PATH,
    scopes: ['https://www.googleapis.com/auth/webmasters.readonly']
  }).getClient();

  const sc = google.searchconsole({ version: 'v1', auth });

  // GSC has a ~3-day data lag — pull days 3-9 ago as "last 7 days"
  const end = new Date(Date.now() - 3 * 86400000).toISOString().split('T')[0];
  const start = new Date(Date.now() - 9 * 86400000).toISOString().split('T')[0];

  const [totals, pages, queries] = await Promise.all([
    query(sc, { startDate: start, endDate: end, dimensions: ['date'], rowLimit: 7 }),
    query(sc, { startDate: start, endDate: end, dimensions: ['page'], rowLimit: 10,
      orderBy: [{ fieldName: 'impressions', sortOrder: 'DESCENDING' }] }),
    query(sc, { startDate: start, endDate: end, dimensions: ['query'], rowLimit: 10,
      orderBy: [{ fieldName: 'impressions', sortOrder: 'DESCENDING' }] }),
  ]);

  const totalClicks = totals.reduce((s, r) => s + r.clicks, 0);
  const totalImpr = totals.reduce((s, r) => s + r.impressions, 0);
  const avgPos = totals.length
    ? (totals.reduce((s, r) => s + r.position, 0) / totals.length).toFixed(1)
    : '';

  let msg = `📡 tedagentic.com — ${new Date().toDateString()}\n\n`;
  msg += `Clicks: ${totalClicks} | Impressions: ${totalImpr} | Avg pos: ${avgPos}\n\n`;

  msg += queries.length
    ? `Top queries:\n` + queries.slice(0, 5).map(r =>
        `  "${r.keys[0]}" — ${r.impressions} impr | pos ${r.position.toFixed(1)}`
      ).join('\n')
    : `Queries: none yet`;

  await sendTelegram(msg);
}
Enter fullscreen mode Exit fullscreen mode

The full script with error handling and page breakdown is at seo-monitor/tedagentic/monitor.js in the repo.

Two things worth noting:

The 3-day lag. GSC data isn't real-time — it runs 2-3 days behind. If you pull "yesterday," you'll get empty rows. The script accounts for this by offsetting: end = today - 3 days, start = end - 7 days. This is why the date window in the Telegram message will always look 3 days behind your actual calendar.

Empty rows aren't errors. A new site will return zero rows from the API for days or weeks. The script handles this cleanly — if rows is empty it reports "none yet" rather than crashing. Early on that's most of what you'll see.


Step 3: Cron

25 8 * * * cd /home/aiserver/seo-monitor && node tedagentic/monitor.js >> /home/aiserver/seo-monitor/tedagentic_monitor.log 2>&1
Enter fullscreen mode Exit fullscreen mode

8:25am daily. Runs after the other site monitors (7am–8:20am) so alerts don't pile up at the same time. The log captures everything — errors, output, the full run — so if a briefing doesn't arrive in Telegram I can check what happened without re-running anything.


What day one looks like

Telegram briefing from tedagentic.com GSC monitor — day one, all zeros

Zero clicks. Zero impressions. No queries. No pages.

That's the point.

This isn't a failure state — it's the starting gun. The monitoring stack is live before the data exists. When the first impression shows up in GSC, it'll be logged. When the first query appears, I'll see it in the morning briefing the same day it lands. When something drops later, I'll have the baseline to measure against.

Most SEO monitoring setups are built reactively — something drops, you build monitoring to catch the next drop. Building it before any traffic exists flips that. You get the full picture from zero.


What's next

The current script is a daily briefing — totals and top items, no intelligence layer. The next step is adding an agent commentary pass: the raw GSC data goes to the LLM, which adds a short interpretation. Not a dashboard, not a report — a one-line read from the agent on whether the numbers look normal, surprising, or worth attention.

That's the difference between monitoring and an AI agent doing monitoring. The script already runs the former. Part 3 covers wiring in the latter.

The same agent that delivers this briefing can now post to X directly from Telegram — the infrastructure compounds as each piece connects.

For now: the baseline exists. The system is watching. The data will come.

Top comments (0)