{"id":16776,"date":"2026-09-09T12:40:49","date_gmt":"2026-09-09T10:40:49","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/werkzeuge\/ga4-measurement-protocol-json-validator\/"},"modified":"2026-09-09T13:09:28","modified_gmt":"2026-09-09T11:09:28","slug":"ga4-measurement-protocol-json-validator","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/ga4-measurement-protocol-json-validator\/","title":{"rendered":"GA4 Measurement Protocol JSON-Validator: alle Fehler auf einen Blick"},"content":{"rendered":"<div class=\"gtm-analyser-container\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<p style=\"color: var(--text-secondary, #a0a0b0); margin-bottom: 20px;\">Das Measurement Protocol antwortet auf fast alles mit 2xx. Ein falsch gebautes Ereignis, ein reservierter Parametername, ein Wert ein Zeichen \u00fcber der Grenze \u2014 die Anfrage wird angenommen, die Daten fallen weg, und der Bericht bleibt einfach leer. Der Pr\u00fcfserver von Google hilft, meldet aber nur den ersten Fund, und in der lockeren Betriebsart, die im Betrieb tats\u00e4chlich gilt, deutlich weniger als das. Diese Pr\u00fcfung liest die Nutzlast zuerst hier und z\u00e4hlt alles auf einmal auf, danach steht daneben, was der Pr\u00fcfserver zu demselben Text in beiden Betriebsarten sagt.<\/p>\n<div style=\"margin-bottom: 6px; display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline; justify-content: space-between;\">\n        <label for=\"mpv-json\" style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.85rem;\">Measurement-Protocol-Nutzlast (der JSON-POST-Rumpf)<\/label><br \/>\n        <span style=\"display: flex; gap: 8px;\"><br \/>\n            <button type=\"button\" id=\"mpv-bsp1\" class=\"button\" style=\"background: transparent; color: var(--text-secondary, #a0a0b0); border: 1px solid var(--border, #2a2a35); padding: 5px 12px; border-radius: 6px; font-size: 0.78rem; cursor: pointer;\">Fehlerhaftes Beispiel<\/button><br \/>\n            <button type=\"button\" id=\"mpv-bsp2\" class=\"button\" style=\"background: transparent; color: var(--text-secondary, #a0a0b0); border: 1px solid var(--border, #2a2a35); padding: 5px 12px; border-radius: 6px; font-size: 0.78rem; cursor: pointer;\">Sauberes Beispiel<\/button><br \/>\n        <\/span>\n    <\/div>\n<p>    <textarea id=\"mpv-json\" rows=\"16\" spellcheck=\"false\" style=\"width: 100%; padding: 12px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace; font-size: 0.85rem; line-height: 1.5; resize: vertical;\"><\/textarea><\/p>\n<div style=\"display: flex; flex-wrap: wrap; gap: 18px; align-items: center; margin: 14px 0 6px;\">\n        <span><br \/>\n            <label for=\"mpv-strom\" style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.85rem; margin-right: 8px;\">Datenstrom<\/label><br \/>\n            <select id=\"mpv-strom\" style=\"padding: 7px 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px;\"><option value=\"web\">Web (client_id)<\/option><option value=\"app\">App (app_instance_id)<\/option><\/select><br \/>\n        <\/span><br \/>\n        <label style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.85rem; cursor: pointer;\"><br \/>\n            <input type=\"checkbox\" id=\"mpv-360\" style=\"vertical-align: middle; margin-right: 6px;\">Analytics-360-Property (500 Zeichen je Wert)<br \/>\n        <\/label><br \/>\n        <label style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.85rem; cursor: pointer;\"><br \/>\n            <input type=\"checkbox\" id=\"mpv-google\" checked style=\"vertical-align: middle; margin-right: 6px;\">Zus\u00e4tzlich den Pr\u00fcfserver von Google fragen<br \/>\n        <\/label>\n    <\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.82rem; margin: 0 0 16px;\">Es werden keine Zugangsdaten abgefragt, und es braucht auch keine: Der Pr\u00fcfserver kontrolliert weder measurement_id noch api_secret, dieses Werkzeug sendet deshalb Platzhalter. Die Nutzlast selbst wird nicht gespeichert \u2014 im Z\u00e4hler landet nur ihre Bytezahl. Ereignisse an den Pr\u00fcfserver erscheinen in keinem Bericht.<\/p>\n<p>    <button id=\"mpv-btn\" class=\"button\" style=\"background: var(--accent, #7ee787); color: var(--on-accent, #0b1114); border: none; padding: 12px 24px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Nutzlast pr\u00fcfen<\/button><\/p>\n<div id=\"mpv-ausgabe\" style=\"margin-top: 22px;\"><\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.8rem; margin: 24px 0 0;\">Grenzen, die zu kennen sich lohnt: Reservierte Namen, Gr\u00f6\u00dfengrenzen und empfohlene Parameter folgen der ver\u00f6ffentlichten Referenz und k\u00f6nnen sich ohne Ank\u00fcndigung \u00e4ndern. Die Listen empfohlener Parameter sind f\u00fcr die gel\u00e4ufigen Ereignisse kuratiert; ein unbekannter Ereignisname bekommt deshalb gar keine Empfehlung statt einer falschen. Ob eine Property einen Parameter am Ende wirklich erfasst, h\u00e4ngt an ihren benutzerdefinierten Dimensionen, und die zeigt keine Nutzlast.<\/p>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    const T = {\"einleitung\":\"Das Measurement Protocol antwortet auf fast alles mit 2xx. Ein falsch gebautes Ereignis, ein reservierter Parametername, ein Wert ein Zeichen \u00fcber der Grenze \u2014 die Anfrage wird angenommen, die Daten fallen weg, und der Bericht bleibt einfach leer. Der Pr\u00fcfserver von Google hilft, meldet aber nur den ersten Fund, und in der lockeren Betriebsart, die im Betrieb tats\u00e4chlich gilt, deutlich weniger als das. Diese Pr\u00fcfung liest die Nutzlast zuerst hier und z\u00e4hlt alles auf einmal auf, danach steht daneben, was der Pr\u00fcfserver zu demselben Text in beiden Betriebsarten sagt.\",\"label_json\":\"Measurement-Protocol-Nutzlast (der JSON-POST-Rumpf)\",\"label_strom\":\"Datenstrom\",\"strom_web\":\"Web (client_id)\",\"strom_app\":\"App (app_instance_id)\",\"label_360\":\"Analytics-360-Property (500 Zeichen je Wert)\",\"label_google\":\"Zus\u00e4tzlich den Pr\u00fcfserver von Google fragen\",\"bsp_fehler\":\"Fehlerhaftes Beispiel\",\"bsp_sauber\":\"Sauberes Beispiel\",\"knopf\":\"Nutzlast pr\u00fcfen\",\"laeuft\":\"Nutzlast wird gelesen...\",\"datenschutz\":\"Es werden keine Zugangsdaten abgefragt, und es braucht auch keine: Der Pr\u00fcfserver kontrolliert weder measurement_id noch api_secret, dieses Werkzeug sendet deshalb Platzhalter. Die Nutzlast selbst wird nicht gespeichert \u2014 im Z\u00e4hler landet nur ihre Bytezahl. Ereignisse an den Pr\u00fcfserver erscheinen in keinem Bericht.\",\"grenze\":\"Grenzen, die zu kennen sich lohnt: Reservierte Namen, Gr\u00f6\u00dfengrenzen und empfohlene Parameter folgen der ver\u00f6ffentlichten Referenz und k\u00f6nnen sich ohne Ank\u00fcndigung \u00e4ndern. Die Listen empfohlener Parameter sind f\u00fcr die gel\u00e4ufigen Ereignisse kuratiert; ein unbekannter Ereignisname bekommt deshalb gar keine Empfehlung statt einer falschen. Ob eine Property einen Parameter am Ende wirklich erfasst, h\u00e4ngt an ihren benutzerdefinierten Dimensionen, und die zeigt keine Nutzlast.\",\"h_ereignisse\":\"Ereignisse\",\"h_befunde\":\"Befunde\",\"h_google\":\"Der Pr\u00fcfserver von Google\",\"sp_nr\":\"Nr.\",\"sp_name\":\"Ereignisname\",\"sp_parameter\":\"Parameter\",\"sp_items\":\"Items\",\"sp_urteil\":\"Urteil\",\"zeile_gut\":\"in Ordnung\",\"zeile_warnung\":\"Warnung\",\"zeile_kritisch\":\"fehlerhaft\",\"wert_fehlt\":\"ohne Namen\",\"zeile_groesse\":\"Gr\u00f6\u00dfe der Nutzlast\",\"zeile_ereignisse\":\"Ereignisse\",\"zeile_parameter\":\"Parameter\",\"zeile_items\":\"Items\",\"zeile_up\":\"Nutzereigenschaften\",\"zeile_dauer\":\"Dauer\",\"zeile_limit\":\"Pr\u00fcfungen diese Stunde \u00fcbrig\",\"einheit_byte\":\"Byte\",\"tausendertrenner\":\".\",\"stufe_hoch\":\"Hoch\",\"stufe_mittel\":\"Mittel\",\"stufe_niedrig\":\"Niedrig\",\"stufe_info\":\"Hinweis\",\"stufe_gut\":\"In Ordnung\",\"keine_befunde\":\"Nichts zu melden.\",\"urteil_gut\":\"Die Nutzlast entspricht den dokumentierten Regeln.\",\"urteil_warnung\":\"Die Nutzlast wird angenommen, aber Teile davon kommen nicht so an wie gedacht.\",\"urteil_kritisch\":\"Etwas darin wird abgelehnt oder stillschweigend verworfen.\",\"google_streng\":\"Mit validation_behavior: ENFORCE_RECOMMENDATIONS\",\"google_locker\":\"Ohne validation_behavior \u2014 so behandelt die Produktion sie\",\"google_keine\":\"Keine Meldung. Diese Pr\u00fcfung hat im selben Text {eigene} Punkt(e) gefunden, die eine Meldung wert sind.\",\"google_anzahl\":\"{n} Meldung(en). Diese Pr\u00fcfung hat im selben Text {eigene} Punkt(e) gefunden, die eine Meldung wert sind.\",\"google_uebersprungen\":\"Nicht gefragt \u2014 die Nutzlast l\u00e4sst sich nicht als JSON lesen, es gibt also nichts zu senden.\",\"google_aus\":\"Der Pr\u00fcfserver wurde f\u00fcr diesen Lauf nicht gefragt.\",\"google_fehler\":\"Der Pr\u00fcfserver war nicht erreichbar.\",\"google_fehler_netz\":\"Der Pr\u00fcfserver war nicht erreichbar.\",\"google_fehler_status\":\"Der Pr\u00fcfserver hat mit einem unerwarteten Status geantwortet.\",\"google_fehler_antwort\":\"Die Antwort des Pr\u00fcfservers lie\u00df sich nicht lesen.\",\"google_fehler_kein_json\":\"Es wurde nichts gesendet \u2014 die Nutzlast l\u00e4sst sich nicht als JSON lesen.\",\"google_hinweis\":\"Der Pr\u00fcfserver h\u00e4lt beim ersten Fund an, und die beiden L\u00e4ufe halten nicht immer beim selben an. Beide gingen mit Platzhalter-Zugangsdaten hinaus, weil der Server weder measurement_id noch api_secret pr\u00fcft.\",\"hinweis_komma\":\"Vor einer schlie\u00dfenden Klammer steht ein Komma \u2014 ein nachgestelltes Komma erlaubt JSON nicht.\",\"hinweis_anfuehrung\":\"Es stehen einfache Anf\u00fchrungszeichen dort, wo JSON doppelte verlangt.\",\"hinweis_schluessel_ohne_anfuehrung\":\"Ein Schl\u00fcssel steht ohne Anf\u00fchrungszeichen. JSON ist nicht JavaScript: Jeder Schl\u00fcssel braucht doppelte Anf\u00fchrungszeichen.\",\"hinweis_klammer\":\"Die Zahl der \u00f6ffnenden und schlie\u00dfenden geschweiften Klammern stimmt nicht \u00fcberein.\",\"hinweis_unbekannt\":\"Keine offensichtliche Ursache \u2014 die Stelle aus der Meldung ist der Anfang der Suche.\",\"fehler_kein_token\":\"Die Pr\u00fcfung lie\u00df sich nicht starten, weil reCAPTCHA nicht geladen hat.\",\"fehler_captcha\":\"reCAPTCHA hat die Anfrage als automatisiert eingestuft. Ein Neuladen der Seite hilft normalerweise.\",\"fehler_zu_viele\":\"Das Limit von {limit} Pr\u00fcfungen je Stunde f\u00fcr diese Adresse ist erreicht.\",\"fehler_zaehler\":\"Der Z\u00e4hler ist nicht erreichbar, es wurde deshalb nichts gepr\u00fcft.\",\"fehler_pruefdienst\":\"Der reCAPTCHA-Dienst war nicht erreichbar.\",\"fehler_aufbau\":\"Der Dienst ist nicht richtig eingerichtet. Der Fehler liegt auf dieser Seite.\",\"fehler_eingabe\":\"Die Anfrage lie\u00df sich nicht lesen.\",\"fehler_methode\":\"Falsche Anfragemethode.\",\"fehler_netz\":\"Der Dienst war nicht erreichbar.\",\"fehler_antwort\":\"Die Antwort lie\u00df sich nicht lesen.\",\"fehler_zu_gross\":\"Die Nutzlast ist l\u00e4nger als {grenze} Byte und wurde nicht gelesen.\",\"fehler_nichts_eingegeben\":\"Es wurde nichts eingegeben.\",\"fehler_unbekannt\":\"Es ist etwas schiefgegangen, wof\u00fcr es keine eigene Meldung gibt.\",\"befund_leer\":\"Das Feld ist leer || Es gibt nichts zu pr\u00fcfen.\",\"befund_json_kaputt\":\"Die Nutzlast ist kein g\u00fcltiges JSON || {meldung}. {rat} Weiter wurde nichts gepr\u00fcft: Was sich nicht lesen l\u00e4sst, erreicht das Measurement Protocol gar nicht erst.\",\"befund_kein_objekt\":\"Die Nutzlast ist kein Objekt || Ganz oben steht ein Wert vom Typ {typ}. Das Measurement Protocol erwartet ein JSON-Objekt mit client_id und events.\",\"befund_bom\":\"Der Text beginnt mit einer Bytereihenfolge-Marke || Manche Editoren setzen sie unsichtbar davor. Strenge JSON-Leser verwerfen deswegen das ganze Dokument.\",\"befund_groesse_ueber_grenze\":\"Die Nutzlast ist {bytes} Byte gro\u00df || Dokumentiert sind {grenze}. Dar\u00fcber wird die Anfrage als Ganzes abgelehnt, nicht gek\u00fcrzt.\",\"befund_groesse\":\"Gr\u00f6\u00dfe der Nutzlast: {bytes} Byte || Das sind {anteil}% der Grenze von {grenze} Byte.\",\"befund_datalayer_statt_mp\":\"Das sieht nach einem dataLayer-Objekt aus, nicht nach einer Measurement-Protocol-Nutzlast || Ganz oben steht \\u0022event\\u0022: \\u0022{wert}\\u0022. Im Measurement Protocol steht der Ereignisname in events[].name, und oben steht die client_id.\",\"befund_gtag_statt_mp\":\"Das sieht nach einem gtag-Aufruf aus, nicht nach einer Measurement-Protocol-Nutzlast || Ganz oben steht ein Schl\u00fcssel event_name. Das Measurement Protocol erwartet stattdessen ein events-Array.\",\"befund_zugangsdaten_im_rumpf\":\"Zugangsdaten stehen im Rumpf || measurement_id und api_secret geh\u00f6ren in die Abfragezeichenfolge der Anfrage, nicht ins JSON. Im Rumpf sind sie unbekannte Felder, und ein unbekanntes Feld l\u00e4sst Google die ganze Nutzlast verwerfen.\",\"befund_unbekannter_schluessel\":\"Unbekannter Schl\u00fcssel ganz oben: {schluessel} || Das Measurement Protocol verwirft wegen eines unbekannten Feldes die gesamte Nutzlast, nicht nur dieses Feld. Am Pr\u00fcfserver nachgemessen: Die Antwort ist ein Lesefehler ohne Feldangabe.\",\"befund_client_id_fehlt\":\"client_id fehlt || Ohne sie l\u00e4sst sich kein Ereignis einem Nutzer zuordnen, und der Pr\u00fcfserver antwortet mit VALUE_REQUIRED.\",\"befund_client_id_typ\":\"client_id ist vom Typ {typ}, kein Text || Auch eine rein numerische client_id geh\u00f6rt in Anf\u00fchrungszeichen. Ohne sie scheitert Google schon am Lesen der ganzen Nutzlast.\",\"befund_client_id_leer\":\"client_id ist leer || Eine leere Zeichenkette z\u00e4hlt wie eine fehlende.\",\"befund_client_id_gut\":\"client_id hat die empfohlene Form || {wert} \u2014 zwei Zahlen, durch einen Punkt verbunden, genau das, was gtag herausgibt.\",\"befund_client_id_cookie\":\"client_id ist der volle Cookiewert || {wert}. Das Measurement Protocol nimmt ihn an, und diese Form ist ausdr\u00fccklich vorgesehen. Die k\u00fcrzere ohne das GA1-Pr\u00e4fix ist das, was gtag selbst meldet.\",\"befund_client_id_form\":\"client_id hat eine ungew\u00f6hnliche Form || {wert}. Angenommen wird sie, aber ein Wert, der nicht aus dem Browser stammt, erzeugt bei jeder Anfrage einen neuen Nutzer \u2014 so f\u00fcllt sich ein Bericht mit Sitzungen aus je einem Ereignis.\",\"befund_client_id_im_app\":\"client_id in einer App-Nutzlast || App-Datenstr\u00f6me erkennen die Installation an app_instance_id. client_id wird hier \u00fcbergangen.\",\"befund_app_id_im_web\":\"app_instance_id in einer Web-Nutzlast || Web-Datenstr\u00f6me erkennen den Browser an client_id. app_instance_id wird hier \u00fcbergangen.\",\"befund_app_id_fehlt\":\"app_instance_id fehlt || App-Datenstr\u00f6me brauchen sie; sie stammt aus dem Firebase-SDK und ist keine client_id.\",\"befund_app_id_typ\":\"app_instance_id ist vom Typ {typ}, kein Text || Der Wert geh\u00f6rt in Anf\u00fchrungszeichen.\",\"befund_app_id_gut\":\"app_instance_id hat die erwartete Form || 32 hexadezimale Zeichen.\",\"befund_app_id_form\":\"app_instance_id hat eine ungew\u00f6hnliche Form || {wert}, {n} Zeichen. Das SDK gibt 32 hexadezimale Zeichen zur\u00fcck; alles andere hei\u00dft meist, dass eine client_id eingesetzt wurde.\",\"befund_user_id_typ\":\"user_id ist vom Typ {typ}, kein Text || Der Wert geh\u00f6rt in Anf\u00fchrungszeichen.\",\"befund_user_id_lang\":\"user_id ist {n} Zeichen lang || Dokumentiert sind h\u00f6chstens {grenze}.\",\"befund_user_id_pii\":\"user_id sieht nach personenbezogenen Daten aus ({art}) || Google Analytics verbietet das Senden personenbezogener Kennungen. Ein pseudonymer Schl\u00fcssel aus dem CRM leistet dasselbe, ohne die Property zu gef\u00e4hrden.\",\"befund_zeitstempel_typ\":\"timestamp_micros ist keine Zahl || {wert} vom Typ {typ}. Erwartet wird ein Unix-Zeitstempel in Mikrosekunden.\",\"befund_zeitstempel_sekunden\":\"timestamp_micros steht in Sekunden statt in Mikrosekunden || {wert}. Der Wert ist rund eine Million Mal zu klein und legt das Ereignis ins Jahr 1970. Der Pr\u00fcfserver meldet das zwar, aber als \u201ezu weit in der Vergangenheit\u201c \u2014 eine Meldung, die zur Suche nach einem R\u00fcckdatierungsproblem f\u00fchrt, das es nicht gibt.\",\"befund_zeitstempel_millisekunden\":\"timestamp_micros steht in Millisekunden statt in Mikrosekunden || {wert}. Date.now() liefert Millisekunden; das Measurement Protocol will diesen Wert mal 1000.\",\"befund_zeitstempel_zu_alt\":\"timestamp_micros liegt {stunden} Stunden zur\u00fcck || Ereignisse lassen sich h\u00f6chstens {grenze} Stunden zur\u00fcckdatieren. \u00c4ltere werden verworfen.\",\"befund_zeitstempel_zukunft\":\"timestamp_micros liegt {minuten} Minuten in der Zukunft || Meist eine Uhr, die nachgeht, oder eine doppelt aufgeschlagene Zeitzone.\",\"befund_zeitstempel_gut\":\"timestamp_micros liegt im erlaubten Fenster || Rund {stunden} Stunde(n) zur\u00fcck.\",\"befund_ereignis_zeit_typ\":\"Ereignis {nr}: timestamp_micros ist keine Zahl || {wert} vom Typ {typ}.\",\"befund_ereignis_zeit_sekunden\":\"Ereignis {nr}: timestamp_micros steht in Sekunden || {wert}. Auch hier werden Mikrosekunden erwartet.\",\"befund_ereignis_zeit_millisekunden\":\"Ereignis {nr}: timestamp_micros steht in Millisekunden || {wert}. Der Wert muss mal 1000 genommen werden.\",\"befund_ereignis_zeit_zu_alt\":\"Ereignis {nr}: timestamp_micros liegt {stunden} Stunden zur\u00fcck || Jenseits von {grenze} Stunden wird das Ereignis verworfen.\",\"befund_ereignis_zeit_zukunft\":\"Ereignis {nr}: timestamp_micros liegt {minuten} Minuten in der Zukunft || Meist eine Uhr, die nachgeht.\",\"befund_ereignis_zeit_gut\":\"Ereignis {nr}: timestamp_micros liegt im erlaubten Fenster || Rund {stunden} Stunde(n) zur\u00fcck.\",\"befund_consent_kein_objekt\":\"consent ist vom Typ {typ}, kein Objekt || Erwartet wird ein Objekt mit ad_user_data und ad_personalization.\",\"befund_consent_unbekannt\":\"Unbekannter Schl\u00fcssel in consent: {schluessel} || Hier gibt es nur ad_user_data und ad_personalization. analytics_storage und ad_storage geh\u00f6ren zum Consent Mode im Browser, nicht zum Measurement Protocol \u2014 und ein unbekannter Schl\u00fcssel l\u00e4sst Google die ganze Nutzlast verwerfen.\",\"befund_consent_wert\":\"consent.{schluessel} tr\u00e4gt {wert} || G\u00fcltig sind nur GRANTED und DENIED. Alles andere ist ein unbekannter Aufz\u00e4hlungswert, und die Nutzlast scheitert schon am Lesen.\",\"befund_consent_gut\":\"consent ist mit {n} g\u00fcltigen Wert(en) gesetzt || Ohne consent greift Google auf den Zustand aus den Browserinteraktionen desselben Clients zur\u00fcck.\",\"befund_npa_veraltet\":\"non_personalized_ads gilt als veraltet || Das Feld wirkt weiterhin, aber ad_personalization innerhalb von consent l\u00f6st es ab und ist das, was k\u00fcnftig gelesen wird.\",\"befund_verhalten_streng\":\"validation_behavior steht auf ENFORCE_RECOMMENDATIONS || F\u00fcr einen Test richtig. Im Betrieb bleibt das Feld besser weg, weil die strenge Pr\u00fcfung Ereignisse ablehnt, die die lockere noch erfassen w\u00fcrde.\",\"befund_verhalten_locker\":\"validation_behavior steht auf RELAXED || Das ist ohnehin die Vorgabe. Die lockere Pr\u00fcfung \u00fcbergeht Parameter, die \u00fcber den Grenzen liegen, statt sie zu melden.\",\"befund_verhalten_unbekannt\":\"validation_behavior tr\u00e4gt {wert} || Es gibt nur RELAXED und ENFORCE_RECOMMENDATIONS. Alles andere ist ein unbekannter Aufz\u00e4hlungswert, und die ganze Nutzlast scheitert am Lesen.\",\"befund_up_kein_objekt\":\"user_properties ist vom Typ {typ}, kein Objekt || Erwartet wird ein Objekt aus Namen, unter denen jeweils ein Objekt mit value steht.\",\"befund_up_zu_viele\":\"{n} Nutzereigenschaften || H\u00f6chstens {grenze} werden je Anfrage angenommen.\",\"befund_up_reserviert\":\"user_properties tr\u00e4gt den reservierten Namen {name} || Reservierte Namen werden abgelehnt. Eine Nutzerkennung geh\u00f6rt ganz oben als user_id hin, nicht in user_properties.\",\"befund_up_praefix\":\"Der Name {name} in user_properties beginnt mit {praefix} || Dieses Pr\u00e4fix ist reserviert.\",\"befund_up_name_lang\":\"Der Name {name} in user_properties ist {n} Zeichen lang || Hier liegt die Grenze bei {grenze} und damit enger als bei Ereignisparametern.\",\"befund_up_ohne_value\":\"user_properties {name} hat keine value-H\u00fclle || Erwartet wird {\\u0022value\\u0022: ...}. Eine blanke Zeichenkette l\u00e4sst die ganze Nutzlast am Lesen scheitern.\",\"befund_up_wert_typ\":\"user_properties {name} tr\u00e4gt einen Wert vom Typ {typ} || Werte von Nutzereigenschaften sind Einzelwerte, keine Strukturen.\",\"befund_up_wert_lang\":\"Der Wert von {name} ist {n} Zeichen lang || Hier liegt die Grenze bei {grenze} und damit enger als bei Ereignisparametern.\",\"befund_up_pii\":\"Der Wert von {name} sieht nach personenbezogenen Daten aus ({art}) || Google Analytics verbietet das Senden personenbezogener Kennungen.\",\"befund_up_fremder_schluessel\":\"user_properties {name} tr\u00e4gt einen zus\u00e4tzlichen Schl\u00fcssel: {schluessel} || Dort hinein geh\u00f6rt nur value.\",\"befund_up_gut\":\"Alle {n} Nutzereigenschaften liegen in den Grenzen || Namen bis 24 Zeichen, Werte bis 36.\",\"befund_events_fehlt\":\"events fehlt || Das Array ist Pflicht. Der Pr\u00fcfserver nimmt eine Nutzlast ohne es kommentarlos an \u2014 gemessen, nicht vermutet \u2014 und erfasst wird nichts.\",\"befund_events_kein_array\":\"events ist vom Typ {typ}, kein Array || Auch ein einzelnes Ereignis geh\u00f6rt in ein Array.\",\"befund_events_leer\":\"events ist leer || Ein leeres Array tr\u00e4gt nichts. Der Pr\u00fcfserver meldet kein Problem, und angekommen ist trotzdem nichts.\",\"befund_events_zu_viele\":\"{n} Ereignisse in einer Anfrage || Dokumentiert sind h\u00f6chstens {grenze}. In einer Messung gegen den Pr\u00fcfserver gingen 30 Ereignisse kommentarlos durch \u2014 der \u00dcberschuss f\u00e4llt also still weg, statt gemeldet zu werden.\",\"befund_ereignis_kein_objekt\":\"Ereignis {nr} ist vom Typ {typ}, kein Objekt || Jeder Eintrag braucht name und darf params tragen.\",\"befund_ereignis_fremder_schluessel\":\"Ereignis {nr} tr\u00e4gt einen unbekannten Schl\u00fcssel: {schluessel} || In ein Ereignis geh\u00f6ren nur name und params. Ein Zeitstempel geh\u00f6rt als timestamp_micros in die params.\",\"befund_ereignis_ohne_name\":\"Ereignis {nr} hat keinen Namen || Ohne Namen gibt es nichts zu erfassen.\",\"befund_ereignis_name_typ\":\"Der Name von Ereignis {nr} ist vom Typ {typ} || Erwartet wird eine Zeichenkette.\",\"befund_ereignis_name_lang\":\"Der Name von Ereignis {nr} ist {n} Zeichen lang || {name}. Die Grenze liegt bei {grenze}.\",\"befund_ereignis_name_form\":\"Der Name von Ereignis {nr} hat eine unzul\u00e4ssige Form || {name}. Namen m\u00fcssen mit einem Buchstaben beginnen und d\u00fcrfen nur Buchstaben, Ziffern und Unterstriche enthalten. Bindestriche, Punkte und Leerzeichen werden abgelehnt.\",\"befund_ereignis_name_gross\":\"Der Name von Ereignis {nr} enth\u00e4lt Gro\u00dfbuchstaben || {name}. Ereignisnamen unterscheiden Gro\u00df- und Kleinschreibung: Purchase und purchase sind zwei verschiedene Ereignisse, und nur das kleingeschriebene f\u00fcllt die E-Commerce-Berichte.\",\"befund_ereignis_name_reserviert\":\"Der Name von Ereignis {nr} ist reserviert || {name}. Google verwendet diesen Namen selbst; das Ereignis wird abgelehnt.\",\"befund_ereignis_name_praefix\":\"Der Name von Ereignis {nr} beginnt mit {praefix} || {name}. Dieses Pr\u00e4fix ist reserviert.\",\"befund_ereignis_nur_app\":\"Ereignis {nr} ist nur auf App-Datenstr\u00f6men erlaubt || {name}. Auf einem Web-Datenstrom wird es nicht erfasst. Der Pr\u00fcfserver sagt dazu nichts \u2014 gemessen, nicht vermutet.\",\"befund_ereignis_doppelt\":\"Der Ereignisname {name} kommt {n} Mal vor || Zwei gleichnamige Ereignisse in einer Anfrage werden angenommen und beide gez\u00e4hlt. Ob das so gemeint ist, lohnt einen zweiten Blick, denn meist steckt eine Schleife dahinter, die einmal zu oft ausl\u00f6st.\",\"befund_params_kein_objekt\":\"params von Ereignis {nr} ist vom Typ {typ}, kein Objekt || Parameter sind Paare aus Name und Wert.\",\"befund_params_zu_viele\":\"Ereignis {nr} hat {n} Parameter || H\u00f6chstens {grenze} werden je Ereignis angenommen, dar\u00fcber lehnt die strenge Pr\u00fcfung die Anfrage ab.\",\"befund_param_reserviert\":\"Ereignis {nr} benutzt den reservierten Parameternamen {name} || Der Name wird abgelehnt.\",\"befund_param_praefix\":\"Ereignis {nr}: Parameter {name} beginnt mit {praefix} || Reserviert sind die Pr\u00e4fixe _, firebase_, ga_, google_ und gtag. Der Parameter wird abgelehnt.\",\"befund_param_name_lang\":\"Ereignis {nr}: Parametername {name} ist {n} Zeichen lang || Die Grenze liegt bei {grenze}.\",\"befund_param_verschachtelt\":\"Ereignis {nr}: Parameter {name} tr\u00e4gt einen Wert vom Typ {typ} || Nur items darf ein Array sein. Jeder andere Parameter nimmt einen Einzelwert; eine verschachtelte Struktur f\u00e4llt weg.\",\"befund_param_null\":\"Ereignis {nr}: Parameter {name} ist null || Ein Nullwert ist kein Wert. Den Parameter wegzulassen sagt dasselbe deutlicher.\",\"befund_param_leer\":\"Ereignis {nr}: Parameter {name} ist leer || Eine leere Zeichenkette sieht aus wie eine Antwort und ist keine. Im Bericht erscheint sie als leerer Dimensionswert und nicht als fehlend.\",\"befund_param_wert_lang\":\"Ereignis {nr}: Der Wert von {name} ist {n} Zeichen lang || Die Grenze liegt bei {grenze}. In der lockeren Pr\u00fcfung \u2014 der Vorgabe, und das ist die des Betriebs \u2014 wird der Wert kommentarlos gek\u00fcrzt oder verworfen; am Pr\u00fcfserver nachgemessen kam dieselbe Nutzlast sauber zur\u00fcck.\",\"befund_param_typ\":\"Ereignis {nr}: {name} sollte {soll} sein, ist aber {ist} || {wert}. Werte vom falschen Typ werden bei bekannten Parametern abgelehnt.\",\"befund_param_pii\":\"Ereignis {nr}: {name} sieht nach personenbezogenen Daten aus ({art}) || Google Analytics verbietet das Senden personenbezogener Kennungen, und eine Property, die es tut, kann gel\u00f6scht werden.\",\"befund_waehrung_form\":\"Ereignis {nr}: currency ist kein dreibuchstabiger Code || {wert}. Erwartet wird ISO 4217 in Gro\u00dfbuchstaben, etwa EUR oder USD.\",\"befund_wert_ohne_waehrung\":\"Ereignis {nr} hat value, aber kein currency || Umsatz ohne W\u00e4hrung wird \u00fcberhaupt nicht gez\u00e4hlt. Das ist der h\u00e4ufigste Grund f\u00fcr einen E-Commerce-Bericht, der auf null steht, w\u00e4hrend die Ereignisse ankommen.\",\"befund_waehrung_ohne_wert\":\"Ereignis {nr} hat currency, aber kein value || Die W\u00e4hrung allein tr\u00e4gt keinen Umsatz.\",\"befund_session_id_fehlt\":\"Kein Ereignis tr\u00e4gt session_id || Ohne sie geh\u00f6ren die Ereignisse zu keiner Sitzung. In manchen Berichten tauchen sie trotzdem auf, aber Sitzungen, engagierte Sitzungen und Echtzeit gehen nicht auf.\",\"befund_engagement_fehlt\":\"Kein Ereignis tr\u00e4gt engagement_time_msec || Ohne sie bleibt die durchschnittliche Interaktionsdauer bei null, und in der Echtzeit fehlen die Ereignisse m\u00f6glicherweise ganz.\",\"befund_items_kein_array\":\"Ereignis {nr}: items ist vom Typ {typ}, kein Array || Auch ein einzelnes Item geh\u00f6rt in ein Array.\",\"befund_items_leer\":\"Ereignis {nr}: items ist leer || Ein E-Commerce-Ereignis ohne Items f\u00fcllt keinen Artikelbericht.\",\"befund_items_zu_viele\":\"Ereignis {nr}: {n} Items || Dokumentiert sind h\u00f6chstens {grenze} je Ereignis.\",\"befund_item_kein_objekt\":\"Ereignis {nr}, Item {pos} ist vom Typ {typ} || Jedes Item ist ein Objekt mit mindestens item_id oder item_name.\",\"befund_item_ohne_kennung\":\"Ereignis {nr}, Item {pos} hat weder item_id noch item_name || Eines von beiden ist Pflicht; ohne beides l\u00e4sst sich das Item nicht anzeigen.\",\"befund_item_typ\":\"Ereignis {nr}, Item {pos}: {feld} ist vom Typ {ist} || {wert}. Erwartet wird eine Zahl. Item-Typen pr\u00fcft der Pr\u00fcfserver tats\u00e4chlich \u2014 ein Preis in Anf\u00fchrungszeichen kam als VALUE_INVALID zur\u00fcck.\",\"befund_item_eigene_felder\":\"Ereignis {nr}, Item {pos} tr\u00e4gt {n} eigene Felder || H\u00f6chstens {grenze} eigene Parameter auf Item-Ebene werden je Item gelesen.\",\"befund_summe_weicht_ab\":\"Ereignis {nr}: value ist {value}, die Items ergeben {summe} || F\u00fcr sich genommen kein Fehler \u2014 Versand, Steuern und Rabatte erkl\u00e4ren eine L\u00fccke zu Recht. Wo sie es nicht tun, ist eine der beiden Zahlen falsch, und der Bericht widerspricht dem Shop.\",\"befund_summe_passt\":\"Ereignis {nr}: value passt zu den Items || Preis mal Menge ergibt {summe}.\",\"befund_items_fehlen\":\"Ereignis {nr} ({ereignis}) hat keine items || Dieses Ereignis f\u00fcllt die Artikelberichte, und ohne Items gibt es dazu nichts.\",\"befund_empfohlene_fehlen\":\"Ereignis {nr} ({ereignis}) fehlen empfohlene Parameter || {felder}. Erfasst wird das Ereignis auch ohne sie, aber die darauf aufbauenden Berichte bleiben leer.\",\"befund_empfohlene_da\":\"Ereignis {nr} ({ereignis}) tr\u00e4gt die empfohlenen Parameter || F\u00fcr dieses Ereignis fehlt nichts.\",\"befund_zahlen\":\"{ereignisse} Ereignis(se), {parameter} Parameter, {items} Item(s), {user_properties} Nutzereigenschaft(en) || So viel tr\u00e4gt die Nutzlast.\"};<\/p>\n<p>    var ENDPUNKT = '\/lw-mpvalidate.php';\n    var SITEKEY = '6LcrPkEtAAAAAPo1QCOf-IIM2fCL0UfdJz4y2iSY';\n    var STUFEN = ['hoch', 'mittel', 'niedrig', 'info', 'gut'];\n    var NL = String.fromCharCode(10);<\/p>\n<p>    var BEISPIEL_FEHLER = [\n        '{',\n        '  \"client_id\": \"1234567890.1700000000\",',\n        '  \"timestamp_micros\": 1700000000,',\n        '  \"consent\": { \"ad_user_data\": \"yes\" },',\n        '  \"user_properties\": { \"user_id\": { \"value\": \"k-9910\" } },',\n        '  \"events\": [',\n        '    {',\n        '      \"name\": \"Purchase\",',\n        '      \"params\": {',\n        '        \"transaction_id\": \"T-8891\",',\n        '        \"value\": 25,',\n        '        \"ga_source\": \"newsletter\",',\n        '        \"customer_mail\": \"anna@example.com\",',\n        '        \"items\": [ { \"item_id\": \"A-1\", \"price\": 10, \"quantity\": 2 } ]',\n        '      }',\n        '    },',\n        '    { \"name\": \"screen_view\", \"params\": { \"screen_name\": \"Checkout\" } }',\n        '  ]',\n        '}'\n    ].join(NL);<\/p>\n<p>    var BEISPIEL_SAUBER = [\n        '{',\n        '  \"client_id\": \"1234567890.1700000000\",',\n        '  \"consent\": { \"ad_user_data\": \"GRANTED\", \"ad_personalization\": \"DENIED\" },',\n        '  \"user_properties\": { \"customer_tier\": { \"value\": \"gold\" } },',\n        '  \"events\": [',\n        '    {',\n        '      \"name\": \"purchase\",',\n        '      \"params\": {',\n        '        \"transaction_id\": \"T-8891\",',\n        '        \"currency\": \"EUR\",',\n        '        \"value\": 20,',\n        '        \"session_id\": \"1700000000\",',\n        '        \"engagement_time_msec\": 100,',\n        '        \"items\": [ { \"item_id\": \"A-1\", \"item_name\": \"Shirt\", \"price\": 10, \"quantity\": 2 } ]',\n        '      }',\n        '    }',\n        '  ]',\n        '}'\n    ].join(NL);<\/p>\n<p>    function liste(w) { return Array.isArray(w) ? w : []; }\n    function zahl(w) { return (typeof w === 'number' && isFinite(w)) ? w : null; }\n    function zeichen(w) { return (typeof w === 'string' && w !== '') ? w : null; }\n    function text(schluessel, daten) {\n        var t = T[schluessel];\n        if (typeof t !== 'string') { return null; }\n        return t.replace(\/\\{([a-z_0-9]+)\\}\/g, function (m, k) {\n            return (daten && daten[k] !== undefined && daten[k] !== null) ? String(daten[k]) : m;\n        });\n    }\n    \/** Zahl mit Tausenderpunkten in der Sprache der Seite. *\/\n    function tausend(n) {\n        return String(n).replace(\/\\B(?=(\\d{3})+(?!\\d))\/g, T.tausendertrenner || ' ');\n    }\n    \/** Eine Meldung des Pruefservers auf Tabellenbreite kuerzen. *\/\n    function kurz(s, max) {\n        var t = String(s || '');\n        var m = max || 150;\n        return t.length <= m ? t : t.slice(0, m - 1) + '\u2026';\n    }\n\n    function meldungen(w) {\n        return liste(w).map(function (m) {\n            return { feld: String((m &#038;&#038; m.feld) || ''), code: String((m &#038;&#038; m.code) || ''), meldung: String((m &#038;&#038; m.meldung) || '') };\n        });\n    }\n\n    function bewerten(antwort) {\n        var a = antwort || {};\n        if (a.fehler) { return { fehler: String(a.fehler), limit: zahl(a.limit), grenze: zahl(a.grenze) }; }\n        var befunde = liste(a.befunde).map(function (f) {\n            return { key: String(f.key || ''), stufe: STUFEN.indexOf(f.stufe) === -1 ? 'info' : f.stufe, daten: (f.daten &#038;&#038; typeof f.daten === 'object') ? f.daten : {} };\n        });\n        var gruppen = {};\n        STUFEN.forEach(function (s) { gruppen[s] = befunde.filter(function (f) { return f.stufe === s; }); });\n        var ereignisse = liste(a.ereignisse).map(function (z) {\n            return {\n                nr: zahl(z.nr), name: zeichen(z.name), parameter: zahl(z.parameter), items: zahl(z.items),\n                urteil: (z.urteil === 'gut' || z.urteil === 'warnung') ? z.urteil : 'kritisch'\n            };\n        });\n        var g = (a.google &#038;&#038; typeof a.google === 'object') ? a.google : null;\n        var zahlen = (a.zahlen &#038;&#038; typeof a.zahlen === 'object') ? a.zahlen : {};\n        return {\n            fehler: null,\n            urteil: (a.urteil === 'gut' || a.urteil === 'warnung') ? a.urteil : 'kritisch',\n            bytes: zahl(a.bytes), strom: a.strom === 'app' ? 'app' : 'web',\n            befunde: befunde, gruppen: gruppen, ereignisse: ereignisse,\n            zahlen: {\n                ereignisse: zahl(zahlen.ereignisse) || 0, parameter: zahl(zahlen.parameter) || 0,\n                items: zahl(zahlen.items) || 0, user_properties: zahl(zahlen.user_properties) || 0\n            },\n            google: g ? {\n                streng: g.streng ? { fehler: g.streng.fehler || null, meldungen: meldungen(g.streng.meldungen), dauer: zahl(g.streng.dauer_ms) } : null,\n                locker: g.locker ? { fehler: g.locker.fehler || null, meldungen: meldungen(g.locker.meldungen), dauer: zahl(g.locker.dauer_ms) } : null\n            } : null,\n            dauer: zahl(a.dauer_ms), limitRest: zahl(a.limit_rest)\n        };\n    }\n\n    function skriptLaden() {\n        return new Promise(function (auf) {\n            if (window.grecaptcha) { auf(true); return; }\n            var s = document.createElement('script');\n            s.src = 'https:\/\/www.google.com\/recaptcha\/api.js?render=' + SITEKEY;\n            s.onload = function () { auf(true); }; s.onerror = function () { auf(false); };\n            document.head.appendChild(s);\n            setTimeout(function () { auf(!!window.grecaptcha); }, 8000);\n        });\n    }\n    function tokenHolen() {\n        return skriptLaden().then(function (da) {\n            if (!da || !window.grecaptcha || !window.grecaptcha.ready) { return null; }\n            return new Promise(function (auf) {\n                var fertig = false;\n                function einmal(w) { if (!fertig) { fertig = true; auf(w); } }\n                setTimeout(function () { einmal(null); }, 12000);\n                try {\n                    window.grecaptcha.ready(function () {\n                        if (fertig) { return; }\n                        if (!window.grecaptcha.execute) { einmal(null); return; }\n                        window.grecaptcha.execute(SITEKEY, { action: 'mpvalidate' }).then(function (t) { einmal(t || null); }, function () { einmal(null); });\n                    });\n                } catch (e) { einmal(null); }\n            });\n        }, function () { return null; });\n    }\n    function abfragen(eingabe) {\n        return tokenHolen().then(function (token) {\n            var rumpf = {\n                nutzdaten: eingabe.nutzdaten, strom: eingabe.strom, tarif: eingabe.tarif,\n                google: eingabe.google, token: token || ''\n            };\n            return fetch(ENDPUNKT, { method: 'POST', headers: { 'Content-Type': 'application\/json' }, body: JSON.stringify(rumpf) })\n                .then(function (r) { return r.json().then(function (j) { return j; }, function () { return { fehler: 'antwort' }; }); }, function () { return { fehler: 'netz' }; });\n        });\n    }\n\n    window.LW_TEST = window.LW_TEST || {};\n    window.LW_TEST.MPV = {\n        bewerten: bewerten, darstellen: null, text: text, kurz: kurz, tausend: tausend,\n        ENDPUNKT: ENDPUNKT, STUFEN: STUFEN, T: T,\n        BEISPIEL_FEHLER: BEISPIEL_FEHLER, BEISPIEL_SAUBER: BEISPIEL_SAUBER\n    };\n\n    var btn = document.getElementById('mpv-btn');\n    var out = document.getElementById('mpv-ausgabe');\n    var feld = document.getElementById('mpv-json');\n    if (!btn || !out || !feld) { return; }\n\n    function el(tag, stil, txt) { var e = document.createElement(tag); if (stil) { e.setAttribute('style', stil); } if (txt !== undefined &#038;&#038; txt !== null) { e.textContent = txt; } return e; }\n    var UEBERSCHRIFT = 'font-family: \"Nunito Sans\", sans-serif; font-weight: 700; color: var(--text-primary, #e8e8ee); font-size: 0.95rem; margin: 22px 0 8px;';\n    var ZELLE = 'padding: 5px 12px 5px 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem;';\n    var ZELLE_WERT = 'padding: 5px 12px 5px 0; color: var(--text-primary, #e8e8ee); font-size: 0.87rem; font-family: monospace; word-break: break-all;';\n    var KASTEN = 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 8px; padding: 14px 16px; margin: 0 0 10px;';\n    var GUT = ' color: #7ee787;', WARN = ' color: #ffa94d;', ROT = ' color: #ff7b72;', GRAU = ' color: #8a8a99;';\n    var FARBEN = { hoch: '#ff7b72', mittel: '#ffa94d', niedrig: '#e3b341', info: '#8a8a99', gut: '#7ee787' };\n    var URTEIL = { gut: GUT, warnung: WARN, kritisch: ROT };\n    function gitter(sp) { return el('div', 'display: grid; grid-template-columns: ' + sp + '; gap: 0 18px; align-items: baseline;'); }\n    function paar(g, n, w, stil) { g.appendChild(el('div', ZELLE, n)); g.appendChild(el('div', ZELLE_WERT + (stil || ''), w)); }\n\n    function tabelle(b) {\n        var huelle = el('div', 'overflow-x: auto; margin: 0 0 6px;');\n        var t = el('table', 'border-collapse: collapse; width: 100%; min-width: 520px; font-size: 0.84rem;');\n        var kopf = el('tr', '');\n        [T.sp_nr, T.sp_name, T.sp_parameter, T.sp_items, T.sp_urteil].forEach(function (n) {\n            kopf.appendChild(el('th', 'text-align: left; padding: 6px 12px 6px 0; color: #8a8a99; font-weight: 700; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.4px; border-bottom: 1px solid var(--border, #2a2a35); white-space: nowrap;', n));\n        });\n        t.appendChild(kopf);\n        b.ereignisse.forEach(function (z) {\n            var r = el('tr', '');\n            function zelle(txt, stil) { r.appendChild(el('td', 'padding: 7px 12px 7px 0; border-bottom: 1px solid var(--border, #2a2a35); font-family: monospace; color: var(--text-primary, #e8e8ee);' + (stil || ''), txt)); }\n            zelle(String(z.nr !== null ? z.nr : '-'), GRAU);\n            zelle(z.name || T.wert_fehlt, z.name ? '' : ROT);\n            zelle(String(z.parameter !== null ? z.parameter : '-'), GRAU);\n            zelle(z.items === null ? '-' : String(z.items), GRAU);\n            zelle(T['zeile_' + z.urteil], URTEIL[z.urteil]);\n            t.appendChild(r);\n        });\n        huelle.appendChild(t);\n        return huelle;\n    }\n\n    function googleBlock(name, lauf, eigene) {\n        var kasten = el('div', KASTEN);\n        kasten.appendChild(el('div', 'color: var(--text-primary, #e8e8ee); font-size: 0.9rem; font-weight: 700;', name));\n        if (!lauf) { kasten.appendChild(el('div', ZELLE + ' padding: 6px 0 0;', T.google_uebersprungen)); return kasten; }\n        if (lauf.fehler) {\n            kasten.appendChild(el('div', ZELLE + WARN + ' padding: 6px 0 0;', T['google_fehler_' + lauf.fehler] || T.google_fehler));\n            return kasten;\n        }\n        if (!lauf.meldungen.length) {\n            kasten.appendChild(el('div', ZELLE + GUT + ' padding: 6px 0 0;', text('google_keine', { eigene: eigene })));\n            return kasten;\n        }\n        kasten.appendChild(el('div', ZELLE + ' padding: 6px 0 2px;', text('google_anzahl', { n: lauf.meldungen.length, eigene: eigene })));\n        lauf.meldungen.forEach(function (m) {\n            var z = el('div', 'margin: 8px 0 0; padding: 0 0 0 12px; border-left: 2px solid ' + FARBEN.mittel + ';');\n            z.appendChild(el('div', ZELLE_WERT + ' padding: 0;', m.code + (m.feld ? '  \u00b7  ' + m.feld : '')));\n            z.appendChild(el('div', ZELLE + ' padding: 2px 0 0; line-height: 1.5;', kurz(m.meldung, 320)));\n            kasten.appendChild(z);\n        });\n        return kasten;\n    }\n\n    function darstellen(b, ziel) {\n        ziel.innerHTML = '';\n        if (b.fehler) {\n            var txt = T['fehler_' + b.fehler] || T.fehler_unbekannt;\n            if (b.limit !== null) { txt = txt.replace('{limit}', String(b.limit)); }\n            if (b.grenze !== null) { txt = txt.replace('{grenze}', tausend(b.grenze)); }\n            ziel.appendChild(el('p', ZELLE + WARN + ' margin: 0;', txt));\n            return;\n        }\n        ziel.appendChild(el('div', 'font-size: 1.05rem; font-weight: 700; margin: 0 0 12px;' + URTEIL[b.urteil], T['urteil_' + b.urteil]));\n\n        var g = gitter('auto auto');\n        paar(g, T.zeile_groesse, tausend(b.bytes === null ? 0 : b.bytes) + ' ' + T.einheit_byte, GRAU);\n        paar(g, T.zeile_ereignisse, String(b.zahlen.ereignisse), GRAU);\n        paar(g, T.zeile_parameter, String(b.zahlen.parameter), GRAU);\n        if (b.zahlen.items) { paar(g, T.zeile_items, String(b.zahlen.items), GRAU); }\n        if (b.zahlen.user_properties) { paar(g, T.zeile_up, String(b.zahlen.user_properties), GRAU); }\n        if (b.dauer !== null) { paar(g, T.zeile_dauer, b.dauer + ' ms', GRAU); }\n        if (b.limitRest !== null) { paar(g, T.zeile_limit, String(b.limitRest), GRAU); }\n        ziel.appendChild(g);\n\n        if (b.ereignisse.length) {\n            ziel.appendChild(el('div', UEBERSCHRIFT, T.h_ereignisse));\n            ziel.appendChild(tabelle(b));\n        }\n\n        ziel.appendChild(el('div', UEBERSCHRIFT, T.h_befunde));\n        var irgendwas = false;\n        STUFEN.forEach(function (s) {\n            var gr = b.gruppen[s];\n            if (!gr.length) { return; }\n            irgendwas = true;\n            ziel.appendChild(el('div', 'font-weight: 700; font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.5px; margin: 14px 0 6px; color: ' + FARBEN[s] + ';', T['stufe_' + s] + ' (' + gr.length + ')'));\n            gr.forEach(function (f) {\n                var k = el('div', KASTEN + ' border-left: 3px solid ' + FARBEN[s] + ';');\n                var daten = {};\n                Object.keys(f.daten).forEach(function (n) {\n                    daten[n] = (n === 'bytes' || n === 'grenze') ? tausend(f.daten[n]) : f.daten[n];\n                });\n                if (f.key === 'json_kaputt') { daten.rat = T['hinweis_' + f.daten.hinweis] || T.hinweis_unbekannt; }\n                var t = text('befund_' + f.key, daten) || f.key;\n                var teile = t.split(' || ');\n                k.appendChild(el('div', 'color: var(--text-primary, #e8e8ee); font-size: 0.9rem; font-weight: 700;', teile[0]));\n                if (teile[1]) { k.appendChild(el('div', ZELLE + ' padding: 6px 0 0; line-height: 1.55;', teile[1])); }\n                ziel.appendChild(k);\n            });\n        });\n        if (!irgendwas) { ziel.appendChild(el('p', ZELLE + ' margin: 0;', T.keine_befunde)); }\n\n        ziel.appendChild(el('div', UEBERSCHRIFT, T.h_google));\n        if (!b.google) {\n            ziel.appendChild(el('p', ZELLE + ' margin: 0 0 10px;', T.google_aus));\n        } else {\n            var eigene = b.gruppen.hoch.length + b.gruppen.mittel.length;\n            ziel.appendChild(googleBlock(T.google_streng, b.google.streng, eigene));\n            ziel.appendChild(googleBlock(T.google_locker, b.google.locker, eigene));\n            ziel.appendChild(el('p', ZELLE + ' margin: 4px 0 0; line-height: 1.55;', T.google_hinweis));\n        }\n    }\n    window.LW_TEST.MPV.darstellen = darstellen;\n\n    function wert(id) { var e = document.getElementById(id); return e ? e.value : ''; }\n    function angehakt(id) { var e = document.getElementById(id); return !!(e &#038;&#038; e.checked); }\n\n    var b1 = document.getElementById('mpv-bsp1');\n    var b2 = document.getElementById('mpv-bsp2');\n    if (b1) { b1.addEventListener('click', function () { feld.value = BEISPIEL_FEHLER; }); }\n    if (b2) { b2.addEventListener('click', function () { feld.value = BEISPIEL_SAUBER; }); }\n    if (feld.value.trim() === '') { feld.value = BEISPIEL_FEHLER; }\n\n    var laeuft = false;\n    btn.addEventListener('click', function () {\n        if (laeuft) { return; }\n        laeuft = true; btn.disabled = true;\n        out.innerHTML = '';\n        out.appendChild(el('p', ZELLE + GRAU + ' margin: 0;', T.laeuft));\n        abfragen({\n            nutzdaten: feld.value, strom: wert('mpv-strom') === 'app' ? 'app' : 'web',\n            tarif: angehakt('mpv-360') ? '360' : 'standard', google: angehakt('mpv-google')\n        }).then(function (a) { darstellen(bewerten(a), out); laeuft = false; btn.disabled = false; },\n            function () { darstellen(bewerten({ fehler: 'netz' }), out); laeuft = false; btn.disabled = false; });\n    });\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Liest eine Measurement-Protocol-Nutzlast gegen die dokumentierten Regeln und meldet jeden Fehler in einem Durchgang: reservierte Namen, stillschweigende L\u00e4ngengrenzen, Zeitstempel in der falschen Einheit, personenbezogene Daten in Parametern, Item-Summen, die nicht zu value passen. Derselbe Text geht zweimal an den Pr\u00fcfserver von Google \u2014 streng und so, wie die Produktion ihn behandelt \u2014 damit der Abstand zwischen den drei Urteilen nebeneinander sichtbar wird.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":9283,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[92088,92091,92148],"class_list":["post-16776","page","type-page","status-publish","hentry","tag-data-quality-de","tag-google-analytics-4-de","tag-server-side-gtm-de"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>GA4 Measurement Protocol JSON-Validator: alle Fehler auf einen Blick - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/ga4-measurement-protocol-json-validator\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"GA4 Measurement Protocol JSON-Validator: alle Fehler auf einen Blick - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Liest eine Measurement-Protocol-Nutzlast gegen die dokumentierten Regeln und meldet jeden Fehler in einem Durchgang: reservierte Namen, stillschweigende L\u00e4ngengrenzen, Zeitstempel in der falschen Einheit, personenbezogene Daten in Parametern, Item-Summen, die nicht zu value passen. Derselbe Text geht zweimal an den Pr\u00fcfserver von Google \u2014 streng und so, wie die Produktion ihn behandelt \u2014 damit der Abstand zwischen den drei Urteilen nebeneinander sichtbar wird.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/ga4-measurement-protocol-json-validator\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-09T11:09:28+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/ga4-measurement-protocol-json-validator\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/ga4-measurement-protocol-json-validator\\\/\",\"name\":\"GA4 Measurement Protocol JSON-Validator: alle Fehler auf einen Blick - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-09-09T10:40:49+00:00\",\"dateModified\":\"2026-09-09T11:09:28+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/ga4-measurement-protocol-json-validator\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/ga4-measurement-protocol-json-validator\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/ga4-measurement-protocol-json-validator\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/toolbox-2\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"GA4 Measurement Protocol JSON-Validator: alle Fehler auf einen Blick\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"luky\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"luky\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"GA4 Measurement Protocol JSON-Validator: alle Fehler auf einen Blick - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/ga4-measurement-protocol-json-validator\/","og_locale":"en_US","og_type":"article","og_title":"GA4 Measurement Protocol JSON-Validator: alle Fehler auf einen Blick - Lukas Wojcik - Blog","og_description":"Liest eine Measurement-Protocol-Nutzlast gegen die dokumentierten Regeln und meldet jeden Fehler in einem Durchgang: reservierte Namen, stillschweigende L\u00e4ngengrenzen, Zeitstempel in der falschen Einheit, personenbezogene Daten in Parametern, Item-Summen, die nicht zu value passen. Derselbe Text geht zweimal an den Pr\u00fcfserver von Google \u2014 streng und so, wie die Produktion ihn behandelt \u2014 damit der Abstand zwischen den drei Urteilen nebeneinander sichtbar wird.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/ga4-measurement-protocol-json-validator\/","og_site_name":"Lukas Wojcik - Blog","article_modified_time":"2026-09-09T11:09:28+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/ga4-measurement-protocol-json-validator\/","url":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/ga4-measurement-protocol-json-validator\/","name":"GA4 Measurement Protocol JSON-Validator: alle Fehler auf einen Blick - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-09-09T10:40:49+00:00","dateModified":"2026-09-09T11:09:28+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/ga4-measurement-protocol-json-validator\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/ga4-measurement-protocol-json-validator\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/ga4-measurement-protocol-json-validator\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/toolbox-2\/"},{"@type":"ListItem","position":3,"name":"GA4 Measurement Protocol JSON-Validator: alle Fehler auf einen Blick"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"luky","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"luky"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/16776","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=16776"}],"version-history":[{"count":1,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/16776\/revisions"}],"predecessor-version":[{"id":17028,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/16776\/revisions\/17028"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/9283"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=16776"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=16776"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}