adlibrary.com Logoadlibrary.com
Share
Competitive Research,  Guides & Tutorials

How to Automate Competitor Ad Monitoring End to End

Manual competitor checks die after week three. To automate competitor ad monitoring for real, build a boring pipeline: resolve brands to IDs, save them, curate on a schedule, diff against what you know, and alert your team automatically.

Automated competitor ad monitoring pipeline dashboard showing brand data flowing through five stages, flat vector illustration

Slack Competitor Ad Alerts: A 30-Minute Build

Slack competitor ad alerts fix the oldest failure in competitive intelligence: the data exists, but nobody looks at it in time. Your team checks a rival's ads the week before the quarterly review. Meanwhile that rival shipped fourteen new creatives, killed nine, and scaled two. By the time anyone notices, the angle is saturated and the auction has already repriced.

TL;DR: Wire the AdLibrary API to a Slack incoming webhook with a roughly 60-line Node script. Poll each competitor on a schedule, filter results by first_seen, dedupe on ad_key, and post every new launch into one channel with ad copy, platform, and landing page. Build time: about 30 minutes. Running cost for ten competitors polled daily: around 300 credits a month, well inside the Business plan's 1000+.

The fastest competitive signal is never a dashboard. Dashboards depend on someone remembering to open them, and under launch pressure nobody does. A Slack message lands in the channel where your pod already argues about creative. This guide builds the whole Slack competitor ad alerts pipeline end to end: webhook setup, the poll script with first_seen filtering, a dedup store, message formatting, scheduling options, and the escalation patterns that turn a notification into a workflow.

It assumes you lead a performance team and can run a Node script. Nothing here needs a backend engineer. If you want the broader monitoring strategy first, read how to monitor competitor ads and the automated competitor ad monitoring use case, then come back for the build.

Why Slack Competitor Ad Alerts Beat a Dashboard Nobody Opens

Every team that takes competitor ad research seriously eventually builds a dashboard. Almost every dashboard dies the same death. Week one, everyone checks it. Week four, only the media buyer checks it. Week eight, it silently breaks and nobody files a ticket for a month.

The mechanics explain why. A dashboard is pull-based. It asks people to spend attention speculatively, on the chance something changed. Most days nothing changed, so the habit decays. An alert is push-based. It spends attention only when there is something to see, and it spends it where decisions already happen.

There is a second, sneakier benefit. A launch alert carries a timestamp the whole pod sees at once. When a competitor's new hook shows up in-channel within hours, the conversation starts while a response still matters. You can spot the angle early, check it against creative fatigue in your own account, and counter-test within days. The same launch surfacing three weeks later in a manual sweep reads as trivia.

Speed also compounds. Runtime is the strongest public signal that an ad converts, because advertisers kill losers fast. Catching a launch on day one means you can watch its runtime grow and judge commitment in real time instead of reconstructing the story later. That logic, and what to do with it, is the core of scaling decisions with ad library signals.

What You Are Building: The Five-Part Architecture

A working Slack competitor ad alerts pipeline has five parts, none of them exotic:

  1. A data source that returns competitor ads as JSON, with a reliable first-seen timestamp on every ad.
  2. A Slack incoming webhook, a single URL that turns a JSON POST into a channel message.
  3. A poll script that runs on a schedule, pulls the latest ads per competitor, and keeps only what is new.
  4. A dedup store, so the same ad never alerts twice.
  5. A scheduler. GitHub Actions, n8n, or plain cron all work.

Total surface area: one script and one schedule definition, plus two secrets. If you have already worked through the full adlibrary API documentation and implementation guide, parts one and three will feel familiar. Everything else is plumbing, and the next sections walk it in build order.

Step 0: Pick a Data Source You Can Actually Poll

Slack alerts for new competitor ads are only as good as the data source behind them, so this choice earns its own step.

Meta's Ad Library API is the originator here and deserves the first look. It is free and official. It is also built for transparency rather than monitoring. Full coverage applies to political and social-issue ads, with all-ad coverage only for the EU and UK over the last 12 months. Access requires identity confirmation plus an app review, and access tokens expire every 60 days. We mapped those boundaries in Meta Ad Library free API in 2026. Google's Ads Transparency Center is the equivalent window into Google and YouTube ads, and it ships no public API at all.

Meta's free API is fine if your competitors only buy Meta and you only need what transparency rules expose. The moment you want TikTok, YouTube, or LinkedIn launches in the same alert channel, you need something else.

This build uses the AdLibrary API, the paid power-user route on top of those free libraries. Three properties make it the right backbone for alerting:

  • One key, no review queue. You create an adl_ key in your dashboard and send it as a bearer token. No app review and no 60-day token expiry to babysit. The contrast with Meta's queue is the whole subject of ad library alternative with API access.
  • Multi-platform in one call. Eleven platforms, including Facebook, Instagram, TikTok, YouTube, Google, and LinkedIn, sit behind one search endpoint. One poll covers your competitor's whole footprint.
  • Performance signals on every ad. Each result carries impressions, a spend estimate, a heat score, and runtime. Your alert can say "new launch, already trending" instead of just "new launch."

API access is part of the Business plan (€329/mo, 1000+ credits). An honest note before you commit a card: if political Meta ads are genuinely all you need, use Meta's free API and save the money. This build is for teams whose competitors sell things commercially, across platforms.

Create the Slack Incoming Webhook (5 Minutes)

Slack's incoming webhooks are the shortest path from script to channel. There is no bot user to configure and nothing to rotate. Five steps:

  1. Create the channel first. Call it #competitor-launches and invite the pod.
  2. Go to api.slack.com/apps, click Create New App, choose From scratch, and name it something obvious like Ad Launch Alerts.
  3. In the app sidebar, open Incoming Webhooks and flip the toggle on.
  4. Click Add New Webhook to Workspace and pick #competitor-launches.
  5. Copy the webhook URL. It looks like https://hooks.slack.com/services/T000/B000/XXXX.

Test it immediately:

bash
curl -X POST "$SLACK_WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -d '{"text":"Webhook live. Competitor ad launch alerts will land here."}'

Treat the URL like a password. Anyone holding it can post into your workspace, so it goes into a secrets manager or repository secret, never into the script itself.

The Poll Script: Node with first_seen Filtering (15 Minutes)

The script does four things per competitor. Search for their newest ads, drop everything already seen, post what remains to Slack, then record the new ads as seen. The AdLibrary search endpoint makes the first part one call: sortField: "-first_seen" returns the most recently indexed creatives first, and daysBack: 3 keeps the window tight.

js
// alert.js — Node 18+, zero dependencies
import fs from "node:fs";

const API_KEY = process.env.ADLIBRARY_API_KEY; // adl_...
const WEBHOOK = process.env.SLACK_WEBHOOK_URL;
const STATE_FILE = "seen.json";
const COMPETITORS = ["gymshark", "alo yoga", "vuori"];

const seen = fs.existsSync(STATE_FILE)
  ? new Set(JSON.parse(fs.readFileSync(STATE_FILE, "utf8")))
  : new Set();

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function searchAds(keyword) {
  const res = await fetch("https://adlibrary.com/api/search", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      keyword,
      appType: "3",             // forced to e-commerce for API keys
      sortField: "-first_seen", // newest launches first
      daysBack: 3,              // window wider than the daily cadence
      pageSize: 60,
    }),
  });
  if (!res.ok) throw new Error(`search ${keyword}: HTTP ${res.status}`);
  return res.json();
}

async function postToSlack(payload) {
  await fetch(WEBHOOK, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
}

for (const brand of COMPETITORS) {
  const data = await searchAds(brand);
  const fresh = (data.results ?? []).filter((ad) => !seen.has(ad.ad_key));
  for (const ad of fresh) {
    await postToSlack(formatAd(ad));
    seen.add(ad.ad_key);
  }
  console.log(
    `${brand}: ${fresh.length} new, ${data._credits?.remaining} credits left`
  );
  await sleep(7000); // stay under the 10 requests/min rate limit
}

fs.writeFileSync(STATE_FILE, JSON.stringify([...seen]));

A few deliberate choices worth explaining. The daysBack: 3 window is wider than the daily run cadence on purpose. If a run fails or an ad gets indexed near the boundary, the next run still catches it, and the dedup store absorbs the overlap. The 7-second sleep keeps a long competitor list under the API's 10-requests-per-minute limit. Each search costs 1 credit per page, and a failed search refunds its credit automatically, so an outage never burns budget. The formatAd function is defined two sections down.

Prefer Python? The shape is identical: requests.post for both calls, a set loaded from seen.json, time.sleep(7) between brands. Nothing about the design is language-specific.

One upgrade matters for brands with generic names. A keyword search for "ridge" will pull noise alongside Ridge Wallet. For those cases, resolve the brand once with the free GET /api/advertisers/search lookup, save it via POST /api/advertisers with its platform IDs, and poll POST /api/advertisers/{id}/curate instead. Curate returns every recent ad from the saved accounts across Meta, Google, and LinkedIn, deduped, for 1 credit per 30-minute session. It pairs naturally with saved ads for the creatives you want to keep.

The Dedup Store: Never Alert the Same Ad Twice

Nothing kills an alert channel faster than repeats. The pod mutes the channel the third time it sees the same creative, and the whole system is dead from that moment.

The fix is boring and reliable. Every ad in the API response carries an ad_key, a stable, platform-prefixed identifier like meta_123456789. Store every key you have alerted on, and filter against that store before posting. The script above uses a flat seen.json file, which is the right call at this scale. Ten competitors producing a few hundred new ads a month yields a file measured in kilobytes after a year.

Where the store lives depends on your scheduler. On a server with cron, the file just sits next to the script. On GitHub Actions, the runner is wiped after every job, so the workflow commits seen.json back to the repository as its final step. The exact YAML is in the next section. In n8n, the built-in static data storage serves the same role without any file at all.

Upgrade to SQLite only when you have a concrete reason, like multiple scripts sharing state or a roster past fifty brands. Premature databases are how 30-minute builds become two-week projects.

Split-screen diff view of new versus known competitor ad creatives for automate competitor ad monitoring workflows

Message Formatting: Ad Copy, Platform, Landing Page

A good launch alert answers five questions at a glance. Who launched, what the copy says, which platform it runs on, where it sends traffic, and when it was first seen. Slack's Block Kit renders that cleanly:

js
function formatAd(ad) {
  const copy = (ad.body || ad.message || ad.title || "(no ad text)").slice(0, 280);
  const seenAt = new Date(ad.first_seen * 1000).toISOString().slice(0, 10);
  return {
    blocks: [
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: `*${ad.advertiser_name}* launched a new ${ad.platform} ad\n>${copy}`,
        },
      },
      {
        type: "context",
        elements: [
          {
            type: "mrkdwn",
            text: `First seen ${seenAt} · <${ad.landing_page_url}|landing page> · heat ${ad.heat ?? "n/a"} · <${ad.preview_img_url}|creative preview>`,
          },
        ],
      },
      { type: "divider" },
    ],
  };
}

Three formatting rules learned the hard way. First, truncate the ad copy hard. The opening 280 characters contain the hook, and the hook is what your team needs to judge. Walls of text train people to scroll past. Second, always include the landing page URL. The destination reveals the offer, and a new landing page alongside a new creative usually signals a real campaign rather than a variant test. Third, surface one performance number, no more. Heat or the impression figure works, with the caveat that impressions arrive as bucketed ranges and spend is always an estimate, never an exact figure from the advertiser.

Link to the creative preview instead of unfurling the image. The channel stays scannable, and anyone triaging can click through when a hook earns the attention.

Scheduling: GitHub Actions, n8n, or Plain Cron (10 Minutes)

The script is useless until something runs it on a clock. Three solid options, in rough order of popularity.

GitHub Actions costs nothing and needs no server. Scheduled workflows are documented in GitHub's events reference, and two details matter: the cron expression is evaluated in UTC, and runs can start a few minutes late during peak load. Pick an odd minute to dodge the congestion at the top of the hour.

yaml
name: competitor-ad-alerts
on:
  schedule:
    - cron: "17 6 * * *" # 06:17 UTC daily
  workflow_dispatch: {}

jobs:
  poll:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: node alert.js
        env:
          ADLIBRARY_API_KEY: ${{ secrets.ADLIBRARY_API_KEY }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      - name: persist dedup store
        run: |
          git config user.name "ad-alert-bot"
          git config user.email "[email protected]"
          git add seen.json
          git commit -m "chore: update seen ads" || true
          git push

n8n suits teams that want the pipeline visible and editable without touching code. The Schedule Trigger node starts the flow, an HTTP Request node POSTs to /api/search, a Code node filters against workflow static data, and the Slack node posts the blocks. We published eight adjacent flows in n8n Meta ads automation recipes, and the same pattern translates to Zapier or Make.com if those already run your ops.

Plain cron wins when you already own a box that never sleeps:

bash
17 6 * * * cd /opt/ad-alerts && /usr/bin/node alert.js >> alert.log 2>&1

On cadence: daily is the right default for Slack competitor ad alerts across a ten-brand roster. Hourly polling of ten brands burns roughly 7,200 credits a month, which is real money for marginal freshness, since ad libraries index new creatives with a lag of hours anyway. The sane hybrid is daily for the roster and hourly for the single rival you are actively at war with.

Escalation Patterns: Emoji Triage and Thread Enrichment

Alert fatigue is the failure mode that kills these channels, and the cure is a cheap protocol for what happens after the ping. Two patterns cover most teams.

Emoji triage. Agree on a tiny vocabulary and pin it to the channel. One person reacting with 👀 claims an ad for review. 🔥 means the angle deserves a brief. 🧊 marks noise. ✅ means the team has already counter-tested it. The cost is zero code and one team agreement, and the payoff is a channel that documents its own decisions. A Friday sweep of every 🔥 from the week becomes the agenda for the creative sync, feeding directly into your creative testing queue.

Thread enrichment. When an ad earns 🔥, run a deeper teardown and post it in the thread so context lives next to the alert. The AI ad enrichment endpoint takes the ad object and returns a structured analysis, a summary, and a scene-by-scene transcript for video creatives:

bash
curl -X POST "https://adlibrary.com/api/enrichment" \
  -H "Authorization: Bearer $ADLIBRARY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ad":{"ad_key":"meta_123456789","platform":"facebook","video_url":"https://..."}}'

One credit for text or short video, refunded on failure. Paste the summary into the thread and the brief practically writes itself, the same ladder we walk in from ad library research to creative brief in 60 minutes. Start manual, with whoever reacted running the curl. Automate the reaction trigger later only if the habit sticks.

Past that, the channel becomes an input for agents. Teams already pipe it into Claude Code workflows against the adlibrary API or wrap the whole thing in their own MCP server. And before your own launches, the channel is the standing input for a pre-launch competitor scan, because the freshest intel is already sitting in the scrollback.

What Slack Competitor Ad Alerts Cost to Run

The infrastructure is free. Slack incoming webhooks cost nothing, and GitHub Actions' scheduled runs sit comfortably inside the free tier. The script has zero dependencies, so the only meter running is API credits.

The math for a typical setup:

ItemVolumeCredits/month
Daily poll, 10 competitors~300 searches~300
Thread enrichment on flagged ads~30 teardowns~30
Slack webhook + scheduler0
Total~330

A search costs 1 credit per page, an enrichment costs 1 credit for text and short video, and failed calls refund automatically. At roughly 330 credits a month, the Business plan (€329/mo, 1000+ credits, full API access) covers the alert system three times over, leaving most of the allowance for the manual research your team does anyway. Business also includes free integration help, which in practice means you can send this exact post to the team and ask them to help wire it up.

The spend estimates arriving with each ad earn their keep beyond alerting. Feed a competitor's estimated figures into the ad spend estimator to gauge their budget commitment, then sanity-check your counter-budget in the ad budget planner. When a rival's new launch shows scaled spend within a week, that is a signal worth a budget conversation, and now it arrives in-channel instead of in next quarter's deck.

If you are weighing the build, start with API access and the pricing page. The Business tier is the one with API keys.

Frequently Asked Questions

How fast will Slack competitor ad alerts catch a new launch?

Within one polling interval of the ad being indexed. Ad libraries themselves index new creatives with a lag of several hours, so a daily poll typically surfaces a launch within a day of it going live, and an hourly poll within hours. For most teams, daily is the right cost-to-freshness tradeoff.

Do I need Meta's Ad Library API for this build?

No. Meta's free API is the originator of ad transparency data and worth knowing, but it covers political and social-issue ads outside the EU/UK, requires app review, and only returns Meta. This build uses the AdLibrary API because one adl_ key covers eleven platforms with performance signals on each ad.

How many credits does competitor ad monitoring use per month?

One credit per search. Ten competitors polled daily is roughly 300 credits a month, plus about one credit per AI teardown you trigger from the channel. A Business plan's 1000+ monthly credits absorbs that with headroom.

Can I run the alert script without a server?

Yes. A GitHub Actions scheduled workflow runs the script on a cron expression, stores both secrets as repository secrets, and commits the dedup file back to the repo. The free tier covers a daily run indefinitely.

How do I avoid duplicate alerts for the same ad?

Dedupe on the ad_key field, the stable platform-prefixed identifier on every result. Persist the set of seen keys between runs, in a JSON file or your scheduler's data store, and filter before posting. Keep the search window wider than the polling cadence so boundary ads are never missed.

Ship the First Alert Today

The whole stack is one short script behind a webhook and a schedule. Thirty minutes of setup buys a channel where competitor launches surface within hours and triage takes one emoji, with the best angles torn down in-thread before your rivals' creatives even fatigue. Slack competitor ad alerts work precisely because they remove the step every dashboard depends on: a human remembering to look.

Start small. Wire up one competitor tonight and let the first daily run land tomorrow at 06:17 UTC. Add the rest of the roster once the pod trusts the signal. When you are ready for the data side, API access on the Business plan (€329/mo, 1000+ credits) is the only prerequisite this build has.

Related Articles

Meta Marketing API integration software: build vs buy decision framework — split diagram showing raw SDK and code on one side versus managed SaaS dashboard on the other
Guides & Tutorials,  Platforms & Tools

Zapier Meta Ads Automation Recipes 2026

8 ready-to-use Zapier Meta ads automation recipes: lead sync, budget alerts, creative notifications, Slack reporting, CRM handoffs, and API escalation paths.