Skript 2 · nur lesend

Der Conversion-Wert-Wächter für dein Konto.

Findet Conversion-Aktionen, die weiter zählen, aber keinen Umsatz mehr melden — verglichen gegen die drei besten Monate, nicht gegen den Vormonat. Dieses Skript verändert nichts in deinem Konto: Es liest, es protokolliert, es schreibt nicht.

Der Defekt dahinter

Warum das im Konto nicht auffällt

Der Defekt, der am längsten überlebt, ist nicht der laute. Die Conversion feuert weiter, die Anzahl steht auf Rekord, nur der Wert dahinter ist weg. Jede zählende Prüfung bleibt grün, während der ausgewiesene ROAS zur Erfindung wird — und die wertbasierte Gebotsstrategie verteilt das Budget monatelang nach einer Zahl, die es nicht mehr gibt.

Was das Skript findet

Eingebrochener Wert bei gehaltener Anzahl
Der Wert je Conversion fällt unter 30 Prozent seines gesunden Niveaus, während die Anzahl mindestens die Hälfte hält. Genau diese Kombination unterscheidet einen Tracking-Bruch von einem echten Umsatzrückgang.
Exakt null Umsatz
Bei einer echten Geld-Aktion ist die Anzahl-Prüfung hier die falsche Prüfung: Kein Rückgang der Welt erzeugt 48 Käufe für 0,00. Null wird deshalb auch dann gemeldet, wenn die Anzahl wild schwankt.
Aktionen, die aufgehört haben zu feuern
Eine Aktion mit typisch 40 Conversions im Monat, die im letzten vollen Monat auf null steht. Liegt der letzte Datenmonat mehr als zwei Monate zurück, wird der Fund als Historie eingestuft und nicht als laufender Vorfall.
Fester Standardwert statt Umsatz
Viele Aktionen tragen einen festen Wert (1,00 oder ein Lead-Score von 150). Ändert der sich, ist das eine Einstellung, kein Umsatzverlust — das Skript liest dafür „always_use_default_value“ und stuft solche Funde herunter. Ohne dieses eine Feld besteht ein Wächter zur Hälfte aus Fehlalarmen.
ROAS, der gar kein ROAS ist
Läuft eine wertbasierte Gebotsstrategie, während alle gezählten Aktionen auf festen Standardwerten stehen, sagt das Skript es: Der „ROAS“ dieses Kontos ist eine Punktzahl. Das ist keine Panne, aber es darf niemand als verdiente Euros berichten.

Der komplette Code

Alles, was du einfügst — nichts dahinter

506 Zeilen, eine Datei, keine Abhängigkeiten. Die Einstellungen stehen im CONFIG-Block ganz oben; der E-Mail-Versand ist ab Werk aus. Getestet gegen Google Ads Scripts, Stand 08/2026.

conversion-wert-waechter.js

Version 1.0.0 · Stand 2026-08-30 · 506 Zeilen

Als Datei
/**
 * CONVERSION-WERT-WAECHTER — Google Ads Script
 * Version 1.0.0 · Stand 2026-08-30
 * https://visnakovs.de/google-ads-scripts/conversion-wert-waechter/
 *
 * Sucht den Defekt, den zaehlende Pruefungen nie finden: die Conversion feuert
 * weiter, aber der Umsatz, den sie meldet, ist weg. Die Anzahl bleibt gruen,
 * der ROAS im Konto wird zur Erfindung, und die Gebotsstrategie optimiert
 * monatelang auf eine Zahl, die es nicht mehr gibt.
 *
 * Der Vergleichswert ist bewusst NICHT der Vormonat. Ein Bruch, der alt genug
 * ist, hat sein eigenes Vergleichsfenster schon vergiftet und liest sich dann
 * als gerade Linie. Verglichen wird gegen den Median der drei besten
 * Wert-je-Conversion-Monate im gesamten Zeitraum.
 *
 * ⛔ GARANTIE — Dieses Skript verändert nichts in deinem Konto: Es liest, es
 * protokolliert, es schreibt nicht. Jede Abfrage darin ist ein SELECT. Es gibt
 * keinen Aufruf, der eine Conversion-Aktion, eine Gebotsstrategie oder eine
 * Kampagne anfassen koennte.
 *
 * Getestet gegen Google Ads Scripts, Stand 08/2026.
 *
 * Umlaute stehen in diesem Code bewusst als ae/oe/ue: der Script-Editor zeigt
 * kopierte Dateien je nach Browser in wechselnder Kodierung an, und ein
 * kaputtes Sonderzeichen im Protokoll oder in einer Bedingung faellt still aus.
 */

// ---------------------------------------------------------------------------
// CONFIG
// ---------------------------------------------------------------------------
var CONFIG = {
  // E-Mail-Zusammenfassung. Bewusst AUS - erst lesen, dann einschalten.
  EMAIL_SENDEN: false,
  EMPFAENGER: 'dein.name@beispiel.de',
  BETREFF: 'Google Ads: Conversion-Werte auffaellig',

  // Nur bei diesen Stufen mailen. HINWEIS-Funde sind Einordnung, kein Alarm.
  EMAIL_AB_STUFE: 'WARNUNG', // 'KRITISCH' | 'WARNUNG' | 'HINWEIS'

  // Rueckblick in Monaten. Muss eine gesunde Phase mit abdecken - sonst
  // versteckt sich ein alter Bruch hinter lauter kaputten Monaten.
  MONATE: 24,

  // Unter dieser Zahl schwankt der Wert je Conversion an einzelnen Verkaeufen.
  MIN_CONVERSIONS: 5,

  // Wert je Conversion muss unter diesen Anteil seines gesunden Niveaus fallen.
  WERT_EINBRUCH_ANTEIL: 0.30,

  // ... waehrend die Anzahl mindestens diesen Anteil haelt. Genau das
  // unterscheidet einen Tracking-Bruch von einem echten Umsatzrueckgang.
  ANZAHL_HAELT_ANTEIL: 0.50,

  // Aelter als so viele Monate: Historie, kein laufender Vorfall.
  VERALTET_AB_MONATEN: 2
};

// Gebotsstrategien, die auf den Conversion-WERT optimieren. Genau hier wird
// aus einem Messfehler eine falsche Budgetverteilung.
var WERT_GEBOTE = ['TARGET_ROAS', 'MAXIMIZE_CONVERSION_VALUE'];

var STUFEN_RANG = { 'KRITISCH': 3, 'WARNUNG': 2, 'HINWEIS': 1, 'OK': 0 };

// ---------------------------------------------------------------------------

function main() {
  var heute = new Date();
  var jetztIndex = heute.getFullYear() * 12 + (heute.getMonth() + 1);
  var bereich = zeitraum(CONFIG.MONATE);

  Logger.log('Conversion-Wert-Waechter 1.0.0 — Konto ' + AdsApp.currentAccount().getCustomerId());
  Logger.log('Zeitraum: ' + bereich.start + ' bis ' + bereich.ende + ' (' + CONFIG.MONATE + ' Monate)');
  Logger.log('Waehrung laut Konto: ' + AdsApp.currentAccount().getCurrencyCode());
  Logger.log('Dieses Skript liest nur. Es schreibt nichts ins Konto.');
  Logger.log('');

  var reihen = holeReihenJeAktion(bereich);
  var ebene = 'Conversion-Aktion';
  if (reihen === null) {
    Logger.log('HINWEIS: Die Auswertung je Conversion-Aktion war in diesem Konto nicht');
    Logger.log('abrufbar. Das Skript weicht auf die Kampagnen-Ebene aus. Damit sieht es');
    Logger.log('einen Wertbruch nur noch, wenn er eine ganze Kampagne betrifft - eine');
    Logger.log('einzelne kaputte Conversion-Aktion neben gesunden geht darin unter.');
    Logger.log('');
    reihen = holeReihenJeKampagne(bereich);
    ebene = 'Kampagne';
  }

  var stammdaten = holeStammdaten();
  var wertBietendeKampagnen = holeWertBietendeKampagnen();

  var funde = auswerten(reihen, stammdaten, wertBietendeKampagnen, jetztIndex, ebene);
  berichte(funde, reihen, ebene, stammdaten, bereich);
}

// --- Konto lesen -----------------------------------------------------------

/**
 * Wert und Anzahl je Conversion-Aktion und Monat.
 *
 * Die Abfrage laeuft FROM customer und segmentiert nach
 * segments.conversion_action_name. Der Umweg ist noetig, weil
 * metrics.conversions FROM conversion_action verboten ist.
 *
 * Gibt null zurueck, wenn das Konto die Abfrage nicht zulaesst - dann faellt
 * main() auf die Kampagnen-Ebene zurueck und sagt das im Protokoll.
 */
function holeReihenJeAktion(bereich) {
  var abfrage =
    'SELECT segments.month, segments.conversion_action_name, ' +
    'metrics.all_conversions, metrics.all_conversions_value ' +
    'FROM customer ' +
    "WHERE segments.date BETWEEN '" + bereich.start + "' AND '" + bereich.ende + "'";
  try {
    var reihen = {};
    var lauf = AdsApp.search(abfrage);
    while (lauf.hasNext()) {
      var reihe = lauf.next();
      var name = pfad(reihe, ['segments', 'conversionActionName']);
      if (!name) continue;
      sammle(reihen, name, monatVon(reihe), reihe);
    }
    return reihen;
  } catch (fehler) {
    Logger.log('Abfrage je Conversion-Aktion fehlgeschlagen: ' + fehler);
    return null;
  }
}

/** Rueckfallebene: Wert und Anzahl je Kampagne und Monat. */
function holeReihenJeKampagne(bereich) {
  var abfrage =
    'SELECT segments.month, campaign.name, ' +
    'metrics.all_conversions, metrics.all_conversions_value ' +
    'FROM campaign ' +
    "WHERE segments.date BETWEEN '" + bereich.start + "' AND '" + bereich.ende + "' " +
    "AND campaign.status != 'REMOVED'";
  var reihen = {};
  var lauf = AdsApp.search(abfrage);
  while (lauf.hasNext()) {
    var reihe = lauf.next();
    var name = pfad(reihe, ['campaign', 'name']);
    if (!name) continue;
    sammle(reihen, name, monatVon(reihe), reihe);
  }
  return reihen;
}

function sammle(reihen, name, monat, reihe) {
  if (!monat) return;
  if (!reihen[name]) reihen[name] = {};
  if (!reihen[name][monat]) reihen[name][monat] = { anzahl: 0, wert: 0 };
  reihen[name][monat].anzahl += zahl(pfad(reihe, ['metrics', 'allConversions']));
  reihen[name][monat].wert += zahl(pfad(reihe, ['metrics', 'allConversionsValue']));
}

/**
 * Einstellungen je Conversion-Aktion. Das entscheidende Feld ist
 * always_use_default_value: steht es, war der "Umsatz" nie Umsatz, sondern ein
 * fester Platzhalter - eine Aenderung daran ist eine Einstellung, kein Verlust.
 * Ohne dieses Feld meldet ein Waechter reihenweise Fehlalarme.
 */
function holeStammdaten() {
  var abfrage =
    'SELECT conversion_action.name, conversion_action.status, ' +
    'conversion_action.primary_for_goal, ' +
    'conversion_action.include_in_conversions_metric, ' +
    'conversion_action.value_settings.default_value, ' +
    'conversion_action.value_settings.always_use_default_value ' +
    'FROM conversion_action';
  var daten = { verfuegbar: false, nach_name: {} };
  try {
    var lauf = AdsApp.search(abfrage);
    while (lauf.hasNext()) {
      var reihe = lauf.next();
      var aktion = reihe.conversionAction || {};
      var werte = aktion.valueSettings || {};
      daten.nach_name[aktion.name] = {
        status: aktion.status || 'UNKNOWN',
        primaer: aktion.primaryForGoal === true,
        // Fehlt das Feld in der Antwort, ist es false - die API laesst
        // Standardwerte im JSON schlicht weg.
        gezaehlt: aktion.includeInConversionsMetric === true,
        standardwert: zahl(werte.defaultValue),
        immer_standardwert: werte.alwaysUseDefaultValue === true
      };
    }
    daten.verfuegbar = true;
  } catch (fehler) {
    Logger.log('Stammdaten der Conversion-Aktionen nicht abrufbar: ' + fehler);
    Logger.log('Ohne sie kann das Skript einen festen Platzhalterwert nicht von echtem');
    Logger.log('Umsatz unterscheiden. Es faellt auf die Faustregel zurueck: ein Wert je');
    Logger.log('Conversion, der ueber alle Monate exakt gleich war, gilt als Platzhalter.');
  }
  return daten;
}

/** Aktive Kampagnen, die auf den Conversion-Wert bieten. */
function holeWertBietendeKampagnen() {
  var namen = [];
  try {
    var lauf = AdsApp.search(
      'SELECT campaign.name, campaign.bidding_strategy_type FROM campaign ' +
      "WHERE campaign.status = 'ENABLED'"
    );
    while (lauf.hasNext()) {
      var reihe = lauf.next();
      var art = pfad(reihe, ['campaign', 'biddingStrategyType']);
      for (var i = 0; i < WERT_GEBOTE.length; i++) {
        if (art === WERT_GEBOTE[i]) { namen.push(pfad(reihe, ['campaign', 'name'])); break; }
      }
    }
  } catch (fehler) {
    Logger.log('Gebotsstrategien nicht abrufbar: ' + fehler);
  }
  return namen;
}

// --- Auswertung ------------------------------------------------------------

function auswerten(reihen, stammdaten, wertBietende, jetztIndex, ebene) {
  var funde = [];
  var bietetAufKaputtes = false;

  for (var name in reihen) {
    if (!reihen.hasOwnProperty(name)) continue;
    var info = stammdaten.nach_name[name] || {};
    if (info.status === 'REMOVED') continue; // Entferntes kann nichts brechen

    // Der laufende Kalendermonat ist immer unvollstaendig und fliegt raus -
    // und zwar der Kalendermonat, nicht einfach der letzte Monat mit Daten.
    // Bei einer duennen Aktion sind das zwei verschiedene Dinge, und der
    // letzte Monat mit Daten ist der aussagekraeftigste von allen.
    var monate = [];
    for (var monat in reihen[name]) {
      if (!reihen[name].hasOwnProperty(monat)) continue;
      if (monatIndex(monat) === jetztIndex) continue;
      monate.push(monat);
    }
    monate.sort();
    if (monate.length < 5) continue;

    var letzter = monate[monate.length - 1];
    var veraltet = (jetztIndex - monatIndex(letzter)) > CONFIG.VERALTET_AB_MONATEN;
    var letzteAnzahl = reihen[name][letzter].anzahl;
    var letzterWert = reihen[name][letzter].wert;

    var anzahlen = [];
    for (var a = 0; a < monate.length; a++) anzahlen.push(reihen[name][monate[a]].anzahl);
    var typischeAnzahl = median(anzahlen);
    if (typischeAnzahl < CONFIG.MIN_CONVERSIONS) continue;

    if (letzteAnzahl === 0) {
      funde.push({
        art: veraltet ? 'RUHT' : 'ANZAHL_GESTOPPT',
        stufe: veraltet ? 'HINWEIS' : 'WARNUNG',
        name: name,
        monat: letzter,
        gezaehlt: info.gezaehlt === true,
        detail: 'feuert nicht mehr (typisch ' + runde(typischeAnzahl, 0) + '/Monat)'
      });
      continue;
    }

    // Wert je Conversion, nur aus Monaten mit belastbarer Anzahl.
    var jeConversion = {};
    var werteListe = [];
    var alleGleich = true;
    var ersterWert = null;
    for (var b = 0; b < monate.length; b++) {
      var m = monate[b];
      if (reihen[name][m].anzahl < CONFIG.MIN_CONVERSIONS) continue;
      var v = reihen[name][m].wert / reihen[name][m].anzahl;
      jeConversion[m] = v;
      werteListe.push(v);
      if (ersterWert === null) ersterWert = v;
      else if (Math.abs(v - ersterWert) > 0.005) alleGleich = false;
    }
    if (werteListe.length < 4) continue;

    var sortiert = werteListe.slice(0).sort(function (x, y) { return y - x; });
    var gesund = median(sortiert.slice(0, 3));
    if (gesund <= 0) continue;
    var letztesJeConversion = letzterWert / letzteAnzahl;

    // Ohne Stammdaten: ein ueber alle Monate exakt konstanter Wert ist ein
    // Platzhalter, kein Umsatz. Grobe Regel, aber ehrlicher als Schweigen.
    var festerStandardwert = info.immer_standardwert === true ||
      (!stammdaten.verfuegbar && alleGleich);

    // Die Anzahl-haelt-Pruefung trennt Tracking-Bruch von Nachfragerueckgang.
    // Fuer exakt 0,00 Umsatz auf einer echten Geld-Aktion ist sie die falsche
    // Pruefung: kein Rueckgang der Welt erzeugt 48 Kaeufe fuer 0,00.
    var harteNull = (letzterWert === 0 && letzteAnzahl >= CONFIG.MIN_CONVERSIONS && !festerStandardwert);
    var eingebrochen = (letzteAnzahl >= typischeAnzahl * CONFIG.ANZAHL_HAELT_ANTEIL &&
      letztesJeConversion < gesund * CONFIG.WERT_EINBRUCH_ANTEIL);
    if (!harteNull && !eingebrochen) continue;

    var seit = ersterKaputterMonat(monate, jeConversion, gesund) || letzter;
    var detail = 'Wert je Conversion ' + runde(letztesJeConversion, 2) + ' statt gesund ' +
      runde(gesund, 2) + '; Anzahl haelt (' + runde(letzteAnzahl, 0) + ' statt typisch ' +
      runde(typischeAnzahl, 0) + '); aendert sich ab ' + seit;

    var art, stufe;
    if (festerStandardwert) {
      art = 'STANDARDWERT_GEAENDERT';
      stufe = 'HINWEIS';
    } else if (veraltet) {
      art = 'RUHT';
      stufe = 'HINWEIS';
    } else if (letzterWert === 0) {
      art = 'WERT_NULL';
      stufe = 'WARNUNG';
    } else {
      art = 'WERT_EINGEBROCHEN';
      stufe = 'WARNUNG';
    }
    if ((art === 'WERT_NULL' || art === 'WERT_EINGEBROCHEN') && info.gezaehlt === true) {
      bietetAufKaputtes = true;
    }

    funde.push({
      art: art, stufe: stufe, name: name, monat: letzter, seit: seit,
      gezaehlt: info.gezaehlt === true, primaer: info.primaer === true, detail: detail
    });
  }

  if (wertBietende.length > 0) {
    if (bietetAufKaputtes) {
      funde.push({
        art: 'GEBOT_IN_GEFAHR', stufe: 'KRITISCH', name: '-', monat: '-', gezaehlt: true,
        detail: 'Wertbasierte Gebote laufen auf: ' + wertBietende.slice(0, 4).join(', ')
      });
    } else if (stammdaten.verfuegbar) {
      var gezaehlteAktionen = [];
      var alleFest = true;
      for (var n in stammdaten.nach_name) {
        if (!stammdaten.nach_name.hasOwnProperty(n)) continue;
        var s = stammdaten.nach_name[n];
        if (s.gezaehlt !== true || s.status !== 'ENABLED') continue;
        gezaehlteAktionen.push(n);
        if (s.immer_standardwert !== true) alleFest = false;
      }
      if (gezaehlteAktionen.length > 0 && alleFest) {
        funde.push({
          art: 'PLATZHALTER_WERT', stufe: 'HINWEIS', name: '-', monat: '-', gezaehlt: true,
          detail: 'Wertbasierte Gebote auf lauter festen Standardwerten (' +
            gezaehlteAktionen.slice(0, 3).join(', ') +
            '). Der ROAS dieses Kontos ist eine Punktzahl, kein Euro-Betrag.'
        });
      }
    }
  }

  if (ebene === 'Kampagne') {
    for (var f = 0; f < funde.length; f++) funde[f].name = 'Kampagne: ' + funde[f].name;
  }
  return funde;
}

/** Erster Monat, ab dem der Wert dauerhaft unten blieb. */
function ersterKaputterMonat(monate, jeConversion, gesund) {
  var grenze = gesund * CONFIG.WERT_EINBRUCH_ANTEIL;
  for (var i = 0; i < monate.length; i++) {
    var m = monate[i];
    if (!(m in jeConversion) || jeConversion[m] >= grenze) continue;
    var bleibtUnten = true;
    for (var j = i; j < monate.length; j++) {
      var spaeter = monate[j];
      if ((spaeter in jeConversion) && jeConversion[spaeter] >= grenze) { bleibtUnten = false; break; }
    }
    if (bleibtUnten) return m;
  }
  return null;
}

function hoechsteStufe(funde) {
  var beste = 'OK';
  for (var i = 0; i < funde.length; i++) {
    if (STUFEN_RANG[funde[i].stufe] > STUFEN_RANG[beste]) beste = funde[i].stufe;
  }
  return beste;
}

// --- Ausgabe ---------------------------------------------------------------

function berichte(funde, reihen, ebene, stammdaten, bereich) {
  var geprueft = 0;
  for (var k in reihen) if (reihen.hasOwnProperty(k)) geprueft++;

  Logger.log('Ausgewertet auf Ebene: ' + ebene + ' (' + geprueft + ' Reihen).');
  if (!stammdaten.verfuegbar) {
    Logger.log('Ohne Stammdaten - Platzhalterwerte wurden ueber die Faustregel erkannt.');
  }

  if (funde.length === 0) {
    Logger.log('Ergebnis: keine Auffaelligkeit.');
    Logger.log('Das heisst: kein Wertbruch, den diese Regeln beweisen koennen. Es heisst');
    Logger.log('nicht, dass richtig gemessen wird - eine Aktion, die noch nie einen Wert');
    Logger.log('geliefert hat, hat auch kein gesundes Niveau, gegen das man sie haelt.');
  } else {
    Logger.log('Ergebnis: ' + funde.length + ' Auffaelligkeit(en), hoechste Stufe ' +
      hoechsteStufe(funde) + '.');
    Logger.log('');
    var rang = { 'KRITISCH': 0, 'WARNUNG': 1, 'HINWEIS': 2 };
    funde.sort(function (a, b) { return (rang[a.stufe] || 9) - (rang[b.stufe] || 9); });
    for (var i = 0; i < funde.length; i++) {
      var f = funde[i];
      Logger.log('[' + f.stufe + '] ' + f.art + ' — ' + f.name);
      Logger.log('    ' + f.detail);
      if (f.gezaehlt) Logger.log('    zaehlt in die Conversions-Spalte, steuert also Gebote mit');
    }
  }

  Logger.log('');
  Logger.log('Lauf beendet: Zeitraum ' + bereich.start + '..' + bereich.ende + ' · ' +
    funde.length + ' Fund(e).');
  vielleichtMailen(funde, ebene, bereich);
}

function vielleichtMailen(funde, ebene, bereich) {
  if (!CONFIG.EMAIL_SENDEN) return;
  var stufe = hoechsteStufe(funde);
  var schwelle = STUFEN_RANG[CONFIG.EMAIL_AB_STUFE] || 2;
  if (STUFEN_RANG[stufe] < schwelle) return;
  if (!CONFIG.EMPFAENGER || CONFIG.EMPFAENGER.indexOf('@') === -1) {
    Logger.log('E-Mail uebersprungen: EMPFAENGER ist keine Adresse.');
    return;
  }

  var zeilen = [];
  zeilen.push('Konto: ' + AdsApp.currentAccount().getCustomerId());
  zeilen.push('Zeitraum: ' + bereich.start + ' bis ' + bereich.ende);
  zeilen.push('Ebene: ' + ebene);
  zeilen.push('Hoechste Stufe: ' + stufe);
  zeilen.push('');
  for (var i = 0; i < funde.length; i++) {
    zeilen.push('[' + funde[i].stufe + '] ' + funde[i].art + ' — ' + funde[i].name);
    zeilen.push('  ' + funde[i].detail);
    zeilen.push('');
  }
  zeilen.push('Dieses Skript liest nur. Es hat nichts im Konto veraendert.');

  MailApp.sendEmail(CONFIG.EMPFAENGER, CONFIG.BETREFF + ' (' + stufe + ')', zeilen.join('\n'));
  Logger.log('E-Mail an ' + CONFIG.EMPFAENGER + ' verschickt.');
}

// --- Kleinkram -------------------------------------------------------------

/** Erster Tag des Monats vor n Monaten bis heute, als JJJJ-MM-TT. */
function zeitraum(monate) {
  var heute = new Date();
  var jahr = heute.getFullYear();
  var monat = heute.getMonth() + 1 - monate;
  while (monat <= 0) { monat += 12; jahr -= 1; }
  return {
    start: jahr + '-' + zweistellig(monat) + '-01',
    ende: heute.getFullYear() + '-' + zweistellig(heute.getMonth() + 1) + '-' + zweistellig(heute.getDate())
  };
}

function monatVon(reihe) {
  var roh = pfad(reihe, ['segments', 'month']);
  return roh ? String(roh).substring(0, 7) : null;
}

function monatIndex(monat) {
  var teile = String(monat).split('-');
  return parseInt(teile[0], 10) * 12 + parseInt(teile[1], 10);
}

/**
 * Feldzugriff, der ein fehlendes Zwischenobjekt aushaelt. Die API laesst
 * Felder mit Standardwert (0, false, "") in der Antwort einfach weg - ein
 * direkter reihe.metrics.allConversionsValue faellt dann auf die Nase.
 */
function pfad(objekt, teile) {
  var aktuell = objekt;
  for (var i = 0; i < teile.length; i++) {
    if (aktuell === null || aktuell === undefined) return null;
    aktuell = aktuell[teile[i]];
  }
  return (aktuell === undefined) ? null : aktuell;
}

function zahl(wert) {
  var n = Number(wert);
  return isNaN(n) ? 0 : n;
}

function median(liste) {
  if (!liste.length) return 0;
  var sortiert = liste.slice(0).sort(function (a, b) { return a - b; });
  var mitte = Math.floor(sortiert.length / 2);
  if (sortiert.length % 2 === 1) return sortiert[mitte];
  return (sortiert[mitte - 1] + sortiert[mitte]) / 2;
}

function runde(wert, stellen) {
  var faktor = Math.pow(10, stellen);
  return String(Math.round(wert * faktor) / faktor);
}

function zweistellig(zahlWert) {
  return zahlWert < 10 ? '0' + zahlWert : String(zahlWert);
}

Einbau

In fünf Schritten im Konto

Der letzte Schritt ist der wichtigste: erst die Vorschau, dann der echte Lauf.

  1. 1

    Code kopieren

    Der vollständige Code steht weiter unten auf dieser Seite. Kein Download, kein Formular, keine E-Mail-Adresse — der Knopf legt ihn in die Zwischenablage.

  2. 2

    In Google Ads: Tools → Skripte

    Linke Leiste „Tools“ → „Bulk-Aktionen“ → „Skripte“. Dann auf das Plus, um ein leeres Skript anzulegen. In manchen Konten heißt der Punkt nur „Skripte“ — die Suchleiste oben findet ihn zuverlässig.

  3. 3

    Einfügen und benennen

    Den Beispielcode im Editor komplett markieren, überschreiben, und dem Skript oben einen Namen geben. Die Einstellungen im CONFIG-Block ganz oben anpassen — mehr musst du im Code nicht anfassen.

  4. 4

    Autorisieren

    Google fragt einmal nach Berechtigungen. Das Skript braucht Lesezugriff auf das Konto, und wenn du die E-Mail einschaltest, das Recht, in deinem Namen Mails zu schicken. Das ist eine Google-Abfrage, keine Anmeldung bei mir.

  5. 5

    Erst „Vorschau“, dann „Ausführen“

    Die Vorschau lässt das Skript durchlaufen und zeigt das Protokoll, ohne etwas zu speichern. Lies das Protokoll einmal ganz. Erst danach „Ausführen“ und, wenn es passt, einen Zeitplan setzen.

Ehrliche Grenzen

Was dieses Skript nicht sieht

Ein Waldstück, das niemand abgesucht hat, ist nicht leer. Deshalb steht hier, wo dieses Skript gar nicht erst hinschaut — und im Protokoll steht derselbe Satz noch einmal, direkt neben dem Ergebnis.

  • Der Vergleich braucht Geschichte. Aktionen mit weniger als fünf auswertbaren Monaten oder typisch unter fünf Conversions im Monat werden übersprungen: Bei kleinen Zahlen schwankt der Wert je Conversion an einzelnen Verkäufen.
  • Der laufende Kalendermonat wird verworfen, weil er immer unvollständig ist. Ein Bruch, der heute passiert, fällt frühestens im nächsten Monat auf.
  • Was nie einen Wert hatte, hat kein gesundes Niveau — eine Aktion, bei der der Umsatzwert von Anfang an fehlt, findet dieses Skript nicht. Dafür ist der Health-Check die richtige Stelle.
  • Lässt das Konto die Auswertung je Conversion-Aktion nicht zu, weicht das Skript auf die Kampagnen-Ebene aus und sagt das im Protokoll. Auf dieser Ebene sieht es einen Bruch nur, wenn er eine ganze Kampagne betrifft: Eine einzelne kaputte Aktion neben gesunden geht im Kampagnen-Durchschnitt unter.
  • Es prüft, ob der gemeldete Wert plausibel ist — nicht, ob er stimmt. Ob 240 Euro auch 240 Euro Umsatz waren, weiß nur dein Warenwirtschaftssystem.

Was mit Schreibrechten besser wäre — und trotzdem fehlt

Diese Bibliothek ist in Version 1 per Konstruktion nur lesend. Nicht aus Bequemlichkeit: Der Code landet in fremden, laufenden Konten, und ein Skript, das dort selbstständig pausiert oder umschreibt, richtet im Zweifel einen Schaden an, den niemand kommen sieht. Prüfungen, die mit einem Schreibzugriff besser wären, stehen deshalb hier statt im Code.

  • v2 Bei einem kritischen Fund die betroffene Kampagne von wertbasiertem Gebot auf Conversion-Anzahl zurückstellen, bis der Wert wieder stimmt. Das ist der Eingriff, der den Schaden wirklich stoppt — und genau deshalb keiner, den ein fremdes Skript ungefragt machen darf.
  • v2 Die betroffenen Conversion-Aktionen automatisch aus den Kampagnen-Zielen nehmen. Gleiche Begründung.

FAQ

Conversion-Wert-Wächter: häufige Fragen

Ist dieses Google Ads Script sicher?

Ja. Jede Abfrage darin ist ein SELECT, der komplette Code steht auf dieser Seite, und er enthält keinen Aufruf, der eine Conversion-Aktion, eine Gebotsstrategie oder eine Kampagne ändern könnte. Lass ihn beim ersten Mal über „Vorschau“ laufen, dann siehst du genau, was er liest.

Ändert das Skript etwas in meinem Konto?

Nein. Es liest die Conversion-Zahlen der letzten 24 Monate, die Einstellungen der Conversion-Aktionen und die Gebotsstrategien der aktiven Kampagnen. Es ändert keine davon. Auch der kritische Fund „wertbasierte Gebote laufen auf kaputten Werten“ ist eine Meldung, kein Eingriff.

Wie oft sollte ich es laufen lassen?

Einmal im Monat, ein paar Tage nach dem Monatswechsel. Der laufende Monat wird bewusst verworfen, also gibt es zwischendurch nichts Neues zu sehen. Wer ein Konto mit wertbasierten Geboten führt, setzt sich den Termin fest in den Kalender: Das ist die Prüfung, die man genau dann vergisst, wenn die Zahlen gut aussehen.

Warum sehe ich nichts im Protokoll?

Meistens, weil das Konto zu jung oder zu klein für den Vergleich ist: Ohne fünf auswertbare Monate und ohne typisch fünf Conversions im Monat wird eine Aktion übersprungen. Das Skript nennt deshalb immer die Zahl der ausgewerteten Reihen und die Ebene, auf der es gerechnet hat. Steht am Ende keine Zeile „Lauf beendet“, ist der Lauf abgebrochen — ein frisches Protokoll ohne Fehlermeldung sieht sonst genauso aus wie ein sauberer Lauf.
Kundenstimmen und Case Studies

Ergebnisse, die ich gemessen habe.

14 Kundenstimmen · 22 Case Studies

Kundenstimmen
Das sagt die Kundin
„Mir ist besonders wichtig, dass es bei Google Ads nicht einfach darum geht, mehr Budget auszugeben und mehr Umsatz zu machen, sondern dass die Wirtschaftlichkeit für uns stimmt – und genau das berücksichtigt Daniils bei seiner Arbeit.“
Vollständige Bewertung lesenBewertung einklappen

„Wir arbeiten jetzt schon seit mehreren Jahren mit Danny zusammen und ich schätze vor allem den direkten und unkomplizierten Austausch mit ihm. Er schaut sich die Zahlen genau an, hinterfragt Dinge und kommt auch immer wieder selbst mit neuen Ideen und Ansätzen auf uns zu. Mir ist besonders wichtig, dass es bei Google Ads nicht einfach darum geht, mehr Budget auszugeben und mehr Umsatz zu machen, sondern dass die Wirtschaftlichkeit für uns stimmt – und genau das berücksichtigt Daniils bei seiner Arbeit. Ich schätze außerdem sehr, dass wir uns offen austauschen können.“

Natalja RichertGeschäftsführerin · MYTRAURINGstore Case Study lesen
Das sagt die Kundin
„Die Zusammenarbeit mit Daniils ist ein riesiger Gewinn für unsere Firma.“
Vollständige Bewertung lesenBewertung einklappen

„Die Zusammenarbeit mit Daniils ist ein riesiger Gewinn für unsere Firma. Bei Fragen oder auch Problemen, wir erreichen uns immer. Daniils hat sich in die Materie Sauna voll eingearbeitet und unterstützt uns wo er nur kann. Ich freue mich, dass ich Dich persönlich kennenlernen durfte und wir eine so tolle Zusammenarbeit pflegen. Wie schon angemerkt: Du bist ein Ausnahmetalent, Chapeau!“

Anke Della-BellaGeschäftsführerin · Finsterbusch – die Wellness Factory GmbH Case Study lesen
Google-Ads-Beratung
„Danach konnten wir unsere Conversion Rate auf Sales fast verdoppeln.“
Vollständige Bewertung lesenBewertung einklappen

„Ich betreue unsere Kampagnen selbst und habe Danny für die Google-Ads-Beratung dazugeholt. Wir standen unter Zeitdruck und brauchten eine klare Richtung.

Danny hat sich schnell in unser Geschäft hineingedacht und uns konkrete Empfehlungen zur Gebotsstrategie und zum passenden Conversion-Ziel gegeben. Danach konnten wir unsere Conversion Rate auf Sales fast verdoppeln.

Für mich hat sich vor allem das Gefühl verändert: Ich habe einen Ansprechpartner per Call oder WhatsApp und muss die schwierigsten Entscheidungen nicht mehr allein tragen. Der Austausch ist herzlich, cool und entspannt. Ich würde Danny jedem empfehlen, der seine Kampagnen selbst betreut und jemanden braucht, der wirklich mitdenkt.“

Merlin FaudeGeschäftsführer Fachzentrum für Wut & Aggression
Das sagt der Kunde
„Wir konnten unsere Marketingziele messbar verbessern.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils ist ein äußerst professioneller und zuverlässiger Partner. Die Zusammenarbeit verläuft effizient, lösungsorientiert und immer auf Augenhöhe. Besonders hervorzuheben sind die kurzen Kommunikationswege und seine schnelle Reaktionszeit — man fühlt sich jederzeit gut betreut. Wir konnten unsere Marketingziele messbar verbessern. Eine Zusammenarbeit, die wir uneingeschränkt weiterempfehlen können.“

Patrick ZitzerZitberg GmbH Case Study lesen
Google-Ads-Beratung
„Du hast uns nicht nur bei Google Ads geholfen, sondern unser gesamtes Online-Marketing auf ein neues Level gehoben.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils, danke für deine großartige Unterstützung! Du hast uns nicht nur bei Google Ads geholfen, sondern unser gesamtes Online-Marketing auf ein neues Level gehoben. Deine strukturierte Herangehensweise, dein Blick fürs Detail und dein ehrliches Feedback haben uns extrem weitergebracht. Besonders schätze ich, dass du dich wirklich in unser Business hineingedacht hast – es ging nie um schnelle Tricks, sondern um nachhaltige Performance. Man merkt einfach, dass du weißt, was du tust, und dass du Spaß daran hast. Ich kann dich von Herzen weiterempfehlen!“

Markus BrucknerBruckner Analysentechnik GmbH
Verständliche Beratung
„Daniils ist ein echtes Ausnahmetalent.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils ist ein echtes Ausnahmetalent. Er beherrscht nicht nur alles rund um Google, sondern bringt auch ein starkes unternehmerisches Gespür mit. Komplexe Themen erklärt er verständlich und hilft so, fundierte Geschäftsentscheidungen zu treffen. Eine klare Empfehlung!“

Chris CerraRemoteBase
Google-Ads-Beratung
„Besonders schätze ich, wie verständlich er komplexe Themen erklärt.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils ist ein großartiger Google-Ads-Experte! Er ist schnell, hands-on und bringt kreative Lösungen. Besonders schätze ich, wie verständlich er komplexe Themen erklärt. Wer Hilfe bei Google Ads braucht — Daniils ist die richtige Wahl!“

Samantha ScottAll Things Remote
Google-Ads-Beratung
„Daniils hat zwei meiner Kunden hervorragend bei ihren Google-Ads-Kampagnen unterstützt.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils hat zwei meiner Kunden hervorragend bei ihren Google-Ads-Kampagnen unterstützt. Mit seinem Fachwissen, seiner Professionalität und wertvollen Strategien konnten sie ihre Sichtbarkeit steigern und die Conversion Rates verbessern. Seine Expertise hat wirklich den Unterschied gemacht — absolut empfehlenswert!“

Tim RautenbergXM CROSSMEDIA
Ads & Tracking
„Daniils leistet super Arbeit und bringt absolute Kompetenz im Bereich Ads und Tracking mit!“
Pascal RähseFunnelforms-Gründer
Marketingstrategie
„Daniils hat uns mit seiner Marketingstrategie für die Events eines Kunden auf ein ganz neues Level gebracht.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils hat uns mit seiner Marketingstrategie für die Events eines Kunden auf ein ganz neues Level gebracht. Er war schnell, engagiert und persönlich dabei — und hat uns sogar mit weiteren Expert:innen vernetzt. Fachlich top und mit ganzem Herzen dabei — absolute Empfehlung!“

Alexandra MonitskaMar Sabi Studio
Technische Problemlösung
„Daniils hat mir in kürzester Zeit bei der Lösung eines Problems geholfen, bei dem ich selbst nicht weitergekommen bin.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils hat mir in kürzester Zeit bei der Lösung eines Problems geholfen, bei dem ich selbst nicht weitergekommen bin. Reaktionsschnell, freundlich und höchst kompetent hat er den Auftrag erledigt und sich danach sogar noch Zeit für ein gemeinsames Videomeeting genommen, um die Ergebnisse zu besprechen. Vielen Dank dafür!“

Daniel Fischerdigitalpositiv
Marketingstrategie
„Sehr gute Kommunikation.“
Vollständige Bewertung lesenBewertung einklappen

„Sehr gute Kommunikation. In unseren zwei Beratungsterminen hat sich Daniils intensiv mit meiner Situation beschäftigt und mir klares, gut begründetes Feedback zu Google Ads, Analytics und meiner gesamten digitalen Strategie gegeben.“

FjodoraFiverr-Kunde
Ads & Tracking
„Top Beratung und Kompetenz. Daniils nimmt sich wirklich die Zeit, das Problem gemeinsam zu lösen. Kann ich jedem weiterempfehlen.“
Leon PawlickiFiverr-Kunde
Ads & Tracking
„Daniils kommuniziert sehr gut und hat den Auftrag super schnell und zuverlässig umgesetzt. Ich werde künftig öfter mit ihm arbeiten :)“
Homepage HeroesWebdesign-Agentur

Lieber jemanden, der hinschaut?

Die Skripte finden, was sich beweisen lässt. Struktur, Gebote und die Frage, ob überhaupt das Richtige gemessen wird, gehen wir im Gespräch durch.

Kostenlos & unverbindlich Antwort innerhalb 24h Du sprichst direkt mit mir