{"id":17810,"date":"2026-10-19T18:40:00","date_gmt":"2026-10-19T16:40:00","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/?p=17810"},"modified":"2026-10-09T22:39:12","modified_gmt":"2026-10-09T20:39:12","slug":"ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa","status":"publish","type":"post","link":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/","title":{"rendered":"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz"},"content":{"rendered":"<p>Kasa sklepowa wie o sprzeda\u017cy trzy rzeczy: co zosta\u0142o kupione, ile to kosztowa\u0142o i kt\u00f3ra karta lojalno\u015bciowa przesz\u0142a przez czytnik przy ladzie. GA4 nie wie o tym nic. GA4 zna przegl\u0105darki i rozpoznaje je po warto\u015bci cookie, kt\u00f3ra w sklepie nigdy nie istnia\u0142a.<\/p>\n\n<p>Measurement Protocol to jedyna droga, kt\u00f3r\u0105 wiedza kasy trafia do tej samej us\u0142ugi co dane witryny. To zwyk\u0142y POST po HTTPS, nie wymaga \u017cadnej biblioteki i przyjmuje niemal wszystko \u2014 w\u0142a\u015bnie dlatego t\u0119 drog\u0119 trzeba zbudowa\u0107 starannie, a nie szybko.<\/p>\n\n<figure class=\"lw-diagram\">\n<img src=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/diagrams\/kartenschluessel-pl.png\" width=\"1120\" height=\"580\" decoding=\"async\" loading=\"lazy\"\n     alt=\"\u0141a\u0144cuch od numeru karty lojalno\u015bciowej przez skr\u00f3t i tabel\u0119 powi\u0105za\u0144 do \u0142adunku Measurement Protocol, a pod nim o\u015b czasu z przyjmowanym oknem, jego zmierzonymi granicami i udzia\u0142em paragon\u00f3w traconych przy trzech rytmach wysy\u0142ki\">\n<figcaption>\u0141a\u0144cuch od numeru karty do \u0142adunku oraz okno, w kt\u00f3re musi trafi\u0107 znacznik czasu.<\/figcaption>\n<\/figure>\n\n<h2>Co trzeba po\u0142\u0105czy\u0107<\/h2>\n\n<p>Dwa pola \u0142adunku Measurement Protocol nios\u0105 to\u017csamo\u015b\u0107 i nie s\u0105 wymienne.<\/p>\n\n<p><code>client_id<\/code> to to\u017csamo\u015b\u0107 urz\u0105dzenia. T\u0119 warto\u015b\u0107 tag GA4 zapisuje w przegl\u0105darce w cookie <code>_ga<\/code> i jest ona <strong>obowi\u0105zkowa<\/strong> \u2014 \u0142adunek bez niej serwer walidacyjny odrzuci\u0142 komunikatem <code>VALUE_REQUIRED<\/code>, nawet gdy obecne by\u0142o <code>user_id<\/code>. <code>user_id<\/code> to to\u017csamo\u015b\u0107 osoby, pole opcjonalne, kt\u00f3re przetrwa zmian\u0119 urz\u0105dzenia.<\/p>\n\n<p>Numer karty lojalno\u015bciowej nie jest ani jednym, ani drugim. To trzeci identyfikator, istniej\u0105cy wy\u0142\u0105cznie w sklepie, a ca\u0142a praca polega na przekszta\u0142ceniu go w tamte dwa.<\/p>\n\n<h2>Numer karty zostaje zahaszowany, zanim gdziekolwiek trafi<\/h2>\n\n<p>Numer karty wskazuje osob\u0119 i nale\u017cy do relacji z klientem. Wys\u0142anie go otwartym tekstem do us\u0142ugi analitycznej zamienia identyfikator sklepowy w trwa\u0142y wpis na cudzej infrastrukturze, a GA4 wprost zabrania przesy\u0142ania danych umo\u017cliwiaj\u0105cych identyfikacj\u0119.<\/p>\n\n<p>Skr\u00f3t z kluczem rozwi\u0105zuje oba problemy naraz. Pieprz to sekret pozostaj\u0105cy na w\u0142asnym serwerze; bez niego skr\u00f3tu nie da si\u0119 odtworzy\u0107 ze skradzionej listy numer\u00f3w kart.<\/p>\n\n\n\n<figure style=\"--cbb-digits:1\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"php\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">PHP<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-php\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-keyword\">function<\/span> <span class=\"cbb-function\">card_key<\/span>(string <span class=\"cbb-variable\">$card<\/span>, string <span class=\"cbb-variable\">$pepper<\/span>): string {\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-comment\">\/\/ Najpierw ujednolicenie: kasa zapisuje \"LW 4471 9930\",<\/span>\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-comment\">\/\/ formularz na stronie \"lw-4471-9930\". Bez tego kroku obie<\/span>\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-comment\">\/\/ strony haszuja inaczej i nic nigdy sie nie polaczy.<\/span>\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-variable\">$clean<\/span> = <span class=\"cbb-function\">strtoupper<\/span>(<span class=\"cbb-function\">preg_replace<\/span>(<span class=\"cbb-string\">'\/&#91;^A-Za-z0-9&#93;\/'<\/span>, <span class=\"cbb-string\">''<\/span>, <span class=\"cbb-variable\">$card<\/span>));\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-keyword\">return<\/span> <span class=\"cbb-function\">hash_hmac<\/span>(<span class=\"cbb-string\">'sha256'<\/span>, <span class=\"cbb-variable\">$clean<\/span>, <span class=\"cbb-variable\">$pepper<\/span>);\n<\/span><span class=\"cbb-line\">}<\/span><\/code><\/pre><\/figure>\n\n\n\n<p>To ujednolicenie jest miejscem, w kt\u00f3rym integracje psuj\u0105 si\u0119 po cichu. Dwa systemy zapisuj\u0105ce t\u0119 sam\u0105 kart\u0119 inaczej daj\u0105 dwa r\u00f3\u017cne skr\u00f3ty, a po\u0142\u0105czenie danych zawodzi potem bez komunikatu w jakimkolwiek miejscu.<\/p>\n\n<h2>Zapis powi\u0105zania, dop\u00f3ki klient jest online<\/h2>\n\n<p>Most powstaje raz, w przegl\u0105darce, w jedynym momencie, gdy oba identyfikatory znajduj\u0105 si\u0119 w tym samym miejscu: gdy numer karty zostaje wpisany na koncie klienta albo powi\u0105zany przy zakupie online.<\/p>\n\n<p>Warto\u015b\u0107 <code>client_id<\/code> jest cz\u0119\u015bci\u0105 warto\u015bci cookie <code>_ga<\/code>. Poni\u017cszy kod odczytuje j\u0105, \u0142\u0105cz\u0105c dwa ostatnie segmenty kropk\u0105.<\/p>\n\n\n\n<figure style=\"--cbb-digits:1\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"php\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">PHP<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-php\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-comment\">\/\/ _ga wyglada jak GA1.1.1899482740.1757000000<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/\/ client_id to dwa ostatnie odcinki polaczone kropka.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">function<\/span> <span class=\"cbb-function\">ga_client_id<\/span>(): ?string {\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-keyword\">if<\/span> (<span class=\"cbb-keyword\">empty<\/span>(<span class=\"cbb-variable\">$_COOKIE<\/span>&#91;<span class=\"cbb-string\">'_ga'<\/span>&#93;)) { <span class=\"cbb-keyword\">return<\/span> <span class=\"cbb-constant\">null<\/span>; }\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-variable\">$parts<\/span> = <span class=\"cbb-function\">explode<\/span>(<span class=\"cbb-string\">'.'<\/span>, <span class=\"cbb-variable\">$_COOKIE<\/span>&#91;<span class=\"cbb-string\">'_ga'<\/span>&#93;);\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-keyword\">if<\/span> (<span class=\"cbb-function\">count<\/span>(<span class=\"cbb-variable\">$parts<\/span>) &lt; <span class=\"cbb-number\">4<\/span>) { <span class=\"cbb-keyword\">return<\/span> <span class=\"cbb-constant\">null<\/span>; }\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-keyword\">return<\/span> <span class=\"cbb-variable\">$parts<\/span>&#91;<span class=\"cbb-function\">count<\/span>(<span class=\"cbb-variable\">$parts<\/span>) - <span class=\"cbb-number\">2<\/span>&#93; . <span class=\"cbb-string\">'.'<\/span> . <span class=\"cbb-variable\">$parts<\/span>&#91;<span class=\"cbb-function\">count<\/span>(<span class=\"cbb-variable\">$parts<\/span>) - <span class=\"cbb-number\">1<\/span>&#93;;\n<\/span><span class=\"cbb-line\">}<\/span><\/code><\/pre><\/figure>\n\n\n\n<p>W tym samym momencie witryna ustawia <strong>ten sam skr\u00f3t<\/strong> jako <code>user_id<\/code> w strumieniu internetowym. Je\u017celi witryna i program wysy\u0142aj\u0105cy wylicz\u0105 tu r\u00f3\u017cne warto\u015bci, GA4 otrzyma dwie obce sobie osoby, a \u017cadna ze stron nie zg\u0142osi b\u0142\u0119du.<\/p>\n\n<h2>Tabela powi\u0105za\u0144<\/h2>\n\n<p>Jedna karta nale\u017cy do jednej osoby, ale ta osoba mo\u017ce korzysta\u0107 z kilku przegl\u0105darek. Tabela musi wi\u0119c przechowywa\u0107 wi\u0119cej ni\u017c jedno powi\u0105zanie dla danej karty, a program wysy\u0142aj\u0105cy potrzebuje regu\u0142y wyboru wiersza.<\/p>\n\n\n\n<figure style=\"--cbb-digits:1\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"sql\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">SQL<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-sql\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-keyword\">CREATE<\/span> <span class=\"cbb-keyword\">TABLE<\/span> <span class=\"cbb-function\">card_pairing<\/span> (\n<\/span><span class=\"cbb-line\">  card_hash  <span class=\"cbb-type\">CHAR<\/span>(<span class=\"cbb-number\">64<\/span>)    <span class=\"cbb-keyword\">NOT<\/span> <span class=\"cbb-constant\">NULL<\/span>,\n<\/span><span class=\"cbb-line\">  client_id  <span class=\"cbb-type\">VARCHAR<\/span>(<span class=\"cbb-number\">64<\/span>) <span class=\"cbb-keyword\">NOT<\/span> <span class=\"cbb-constant\">NULL<\/span>,\n<\/span><span class=\"cbb-line\">  first_seen <span class=\"cbb-type\">DATETIME<\/span>    <span class=\"cbb-keyword\">NOT<\/span> <span class=\"cbb-constant\">NULL<\/span>,\n<\/span><span class=\"cbb-line\">  last_seen  <span class=\"cbb-type\">DATETIME<\/span>    <span class=\"cbb-keyword\">NOT<\/span> <span class=\"cbb-constant\">NULL<\/span>,\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">PRIMARY<\/span> <span class=\"cbb-keyword\">KEY<\/span> (card_hash, client_id),\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">KEY<\/span> <span class=\"cbb-function\">by_time<\/span> (card_hash, last_seen)\n<\/span><span class=\"cbb-line\">) <span class=\"cbb-keyword\">ENGINE<\/span>=InnoDB;<\/span><\/code><\/pre><\/figure>\n\n\n\n<p>Najlepiej sprawdza si\u0119 powi\u0105zanie widziane ostatnio: <code>ORDER BY last_seen DESC LIMIT 1<\/code>. Telefon z zesz\u0142ego tygodnia to lepsze przypuszczenie ni\u017c komputer u\u017cywany raz w roku; najstarszy wiersz przypina zakup do urz\u0105dzenia, kt\u00f3rego dawno ju\u017c mo\u017ce nie by\u0107.<\/p>\n\n<h2>client_id dla os\u00f3b, kt\u00f3re nigdy nie by\u0142y online<\/h2>\n\n<p>W sklepie wi\u0119kszo\u015b\u0107 kart nie ma \u017cadnego powi\u0105zania. Te paragony te\u017c musz\u0105 gdzie\u015b trafi\u0107, a wymy\u015blenie <code>client_id<\/code> jest dozwolone \u2014 lecz nie w dowolnej postaci.<\/p>\n\n<p>Serwer walidacyjny wymusza format. Pomiar z 9 wrze\u015bnia 2026 roku, z ustawionym <code>ENFORCE_RECOMMENDATIONS<\/code>: <code>card-LW-4471-9930<\/code>, dwudziestoznakowy przedrostek skr\u00f3tu oraz pojedyncza cyfra <code>7<\/code> zosta\u0142y odrzucone komunikatem <code>client_id [...] has invalid format. It should be in &lt;number&gt;.&lt;number&gt; format.<\/code> Przecinek zamiast kropki zosta\u0142 odrzucony, trzeci odcinek r\u00f3wnie\u017c. Przyj\u0119te zosta\u0142y: <code>1.1<\/code>, <code>0.0<\/code>, dwudziestocyfrowe liczby po obu stronach oraz \u2014 nieoczekiwanie \u2014 <code>1899482740.<\/code> i <code>.1757000000<\/code>, ka\u017cde z brakuj\u0105c\u0105 po\u0142ow\u0105.<\/p>\n\n<p>Sta\u0142e wyliczenie ze skr\u00f3tu karty spe\u0142nia format i pozostaje niezmienne mi\u0119dzy uruchomieniami.<\/p>\n\n\n\n<figure style=\"--cbb-digits:1\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"php\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">PHP<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-php\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-comment\">\/\/ Dwie liczby 32-bitowe z pierwszych 16 znakow szesnastkowych.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-variable\">$h<\/span>   = <span class=\"cbb-function\">card_key<\/span>(<span class=\"cbb-variable\">$card<\/span>, <span class=\"cbb-variable\">$pepper<\/span>);\n<\/span><span class=\"cbb-line\"><span class=\"cbb-variable\">$cid<\/span> = <span class=\"cbb-function\">hexdec<\/span>(<span class=\"cbb-function\">substr<\/span>(<span class=\"cbb-variable\">$h<\/span>, <span class=\"cbb-number\">0<\/span>, <span class=\"cbb-number\">8<\/span>)) . <span class=\"cbb-string\">'.'<\/span> . <span class=\"cbb-function\">hexdec<\/span>(<span class=\"cbb-function\">substr<\/span>(<span class=\"cbb-variable\">$h<\/span>, <span class=\"cbb-number\">8<\/span>, <span class=\"cbb-number\">8<\/span>));\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/\/ LW-4471-9930 -&gt; 2531008582.2372831494, przyjete przez serwer walidacyjny<\/span><\/span><\/code><\/pre><\/figure>\n\n\n\n<p>Co to daje, a czego nie: paragony jednej karty trafiaj\u0105 teraz na jedn\u0105 sta\u0142\u0105 to\u017csamo\u015b\u0107, zamiast si\u0119 rozprasza\u0107. Z sesjami internetowymi tej samej osoby nie zrosn\u0105 si\u0119, bo wyliczona warto\u015b\u0107 nie pasuje do \u017cadnego cookie <code>_ga<\/code> na \u015bwiecie. To po\u0142\u0105czenie niesie wy\u0142\u0105cznie <code>user_id<\/code>, i tylko w us\u0142udze, kt\u00f3rej to\u017csamo\u015b\u0107 raportowania mo\u017ce z niego korzysta\u0107.<\/p>\n\n<h2>\u0141adunek, pole po polu<\/h2>\n\n<p>Dwa dane dost\u0119powe w ci\u0105gu zapytania, ca\u0142a reszta w tre\u015bci JSON. <code>measurement_id<\/code> i <code>api_secret<\/code> nigdy nie nale\u017c\u0105 do tre\u015bci \u2014 tam staj\u0105 si\u0119 nieznanymi polami, a nieznane pole nie zostaje pomini\u0119te, tylko uniewa\u017cnia ca\u0142y \u0142adunek.<\/p>\n\n\n\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"http\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">HTTP<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-http\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-keyword\">POST<\/span> \/mp\/collect?measurement_id=G-XXXXXXXXXX&amp;api_secret=SECRET <span class=\"cbb-meta\">HTTP\/1.1<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-attr\">Host<\/span>: www.google-analytics.com\n<\/span><span class=\"cbb-line\"><span class=\"cbb-attr\">Content-Type<\/span>: application\/json\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\">{\n<\/span><span class=\"cbb-line\">  \"client_id\": \"2531008582.2372831494\",\n<\/span><span class=\"cbb-line\">  \"user_id\": \"9f2c4b1e8a7d3c5f0b6e2a9d4c8f1b3e7a0d5c2f9b4e8a1d6c3f0b7e2a5d9c4f\",\n<\/span><span class=\"cbb-line\">  \"consent\": { \"ad_user_data\": \"GRANTED\", \"ad_personalization\": \"DENIED\" },\n<\/span><span class=\"cbb-line\">  \"events\": &#91;\n<\/span><span class=\"cbb-line\">    {\n<\/span><span class=\"cbb-line\">      \"name\": \"purchase\",\n<\/span><span class=\"cbb-line\">      \"timestamp_micros\": \"1792301400000000\",\n<\/span><span class=\"cbb-line\">      \"params\": {\n<\/span><span class=\"cbb-line\">        \"transaction_id\": \"HH-ALT-2026-1016-0007\",\n<\/span><span class=\"cbb-line\">        \"value\": 48.90,\n<\/span><span class=\"cbb-line\">        \"currency\": \"EUR\",\n<\/span><span class=\"cbb-line\">        \"affiliation\": \"Sklep Hamburg-Altona\",\n<\/span><span class=\"cbb-line\">        \"session_id\": \"1792301400\",\n<\/span><span class=\"cbb-line\">        \"engagement_time_msec\": \"1\",\n<\/span><span class=\"cbb-line\">        \"items\": &#91;\n<\/span><span class=\"cbb-line\">          { \"item_id\": \"SKU-3391\", \"item_name\": \"Mlynek do kawy reczny\",\n<\/span><span class=\"cbb-line\">            \"price\": 48.90, \"quantity\": 1 }\n<\/span><span class=\"cbb-line\">        &#93;\n<\/span><span class=\"cbb-line\">      }\n<\/span><span class=\"cbb-line\">    }\n<\/span><span class=\"cbb-line\">  &#93;\n<\/span><span class=\"cbb-line\">}<\/span><\/code><\/pre><\/figure>\n\n\n\n<p><code>transaction_id<\/code> to numer paragonu i jedyna rzecz, kt\u00f3ra powstrzymuje powt\u00f3rzon\u0105 wysy\u0142k\u0119 przed policzeniem sprzeda\u017cy dwa razy. <code>affiliation<\/code> niesie fili\u0119 \u2014 pole, kt\u00f3re dopiero czyni te dane warto\u015bciowymi. <code>session_id<\/code> oraz <code>engagement_time_msec<\/code> przypinaj\u0105 zdarzenie do sesji w raportach; protok\u00f3\u0142 ich nie wymaga, a ich brak nie jest nigdzie zg\u0142aszany.<\/p>\n\n<p>Dwie regu\u0142y typ\u00f3w, obie zmierzone. Warto\u015b\u0107 <code>price<\/code> pozycji podana jako tekst <code>\"48.90\"<\/code> zostaje przyj\u0119ta, <code>\"48,90\"<\/code> odrzucona komunikatem <code>Known item param [price] should have type [int64] or [double]<\/code>, podobnie <code>\"abc\"<\/code> i pusty ci\u0105g. Pole <code>value<\/code> na poziomie zdarzenia <strong>nie<\/strong> jest sprawdzane tak samo: tam <code>\"abc\"<\/code> przesz\u0142o bez s\u0142owa. Kwota sformatowana lokalnie przez kas\u0119 zawodzi wi\u0119c w jednym miejscu, a w drugim przechodzi po cichu.<\/p>\n\n<h2>Znacznik czasu i okno otwarte tylko z jednej strony<\/h2>\n\n<p>To pole rozstrzyga, czy wysy\u0142ka danych offline w og\u00f3le zadzia\u0142a, a obie jego kraw\u0119dzie zosta\u0142y zmierzone, a nie przepisane z dokumentacji.<\/p>\n\n<table>\n<tr><th>Odst\u0119p od teraz<\/th><th>Werdykt serwera walidacyjnego<\/th><\/tr>\n<tr><td>71 h 59 min 55 s wstecz<\/td><td>przyj\u0119ty<\/td><\/tr>\n<tr><td>72 h 00 min 00 s wstecz<\/td><td>odrzucony, <code>VALUE_INVALID<\/code><\/td><\/tr>\n<tr><td>0 s \u2014 bie\u017c\u0105ca sekunda<\/td><td>przyj\u0119ty<\/td><\/tr>\n<tr><td>5 s w przysz\u0142o\u015b\u0107<\/td><td>odrzucony, <code>VALUE_INVALID<\/code><\/td><\/tr>\n<tr><td>15 min w przysz\u0142o\u015b\u0107<\/td><td>odrzucony<\/td><\/tr>\n<\/table>\n\n<p>Kraw\u0119d\u017a wsteczna jest dok\u0142adna i wykluczaj\u0105ca: 72 godziny co do sekundy s\u0105 ju\u017c za stare. Kraw\u0119d\u017a przednia to niespodzianka. Tre\u015b\u0107 komunikatu brzmi <code>Measurement protocol only supports timestamps [15m] into the future<\/code>, odrzucona zosta\u0142a jednak ka\u017cda sprawdzona warto\u015b\u0107 w prz\u00f3d, a\u017c po pi\u0119\u0107 sekund. Zegar maszyny wysy\u0142aj\u0105cej mie\u015bci\u0142 si\u0119 w tym samym przebiegu w granicy 0,8 sekundy od nag\u0142\u00f3wka <code>Date<\/code> zwracanego przez Google \u2014 po stronie pomiaru przyczyny wi\u0119c nie ma.<\/p>\n\n<p>Dla sklepu oznacza to, \u017ce zegar kasy jest bie\u017c\u0105cym ryzykiem. Terminal spiesz\u0105cy si\u0119 o minut\u0119 tworzy paragony, kt\u00f3re protok\u00f3\u0142 traktuje jako zdarzenia z przysz\u0142o\u015bci, a produkcyjny endpoint i tak przyjmuje je z kodem 204. Program wysy\u0142aj\u0105cy powinien zestawia\u0107 czas paragonu z w\u0142asnym, wiarygodnym zegarem i zatrzymywa\u0107 wszystko datowane w prz\u00f3d, zamiast to wysy\u0142a\u0107.<\/p>\n\n<p>Warto\u015b\u0107 musi by\u0107 podana w mikrosekundach. Znacznik w sekundach zostaje odczytany jako data z roku 1970 i powoduje komunikat <em>too far in the past<\/em>. Mo\u017ce on sugerowa\u0107 problem z antydatowaniem, cho\u0107 faktyczn\u0105 przyczyn\u0105 jest b\u0142\u0119dna jednostka czasu.<\/p>\n\n<h2>Jedno \u017c\u0105danie na klienta i dlaczego \u0142\u0105czenie w paczki dzia\u0142a tu inaczej<\/h2>\n\n<p><code>client_id<\/code> oraz <code>user_id<\/code> stoj\u0105 na najwy\u017cszym poziomie \u0142adunku, obok <code>events<\/code>, a nie wewn\u0105trz zdarzenia. Przeniesienie <code>client_id<\/code> do zdarzenia zosta\u0142o odrzucone wprost: \u0142adunek przesta\u0142 by\u0107 czytelny, z komunikatem <code>invalid JSON in google.analytics.measurement.Measurement @ events[1]<\/code>. Przy <code>user_id<\/code> wewn\u0105trz zdarzenia sta\u0142o si\u0119 to samo.<\/p>\n\n<p>Jedno \u017c\u0105danie nie mo\u017ce wi\u0119c miesza\u0107 klient\u00f3w. Spakowanie dwudziestu pi\u0119ciu paragon\u00f3w od dwudziestu pi\u0119ciu posiadaczy kart w jedno wywo\u0142anie jest niemo\u017cliwe, a jednostk\u0105 pracy programu wysy\u0142aj\u0105cego jest klient, nie paczka.<\/p>\n\n<p>Dzia\u0142a natomiast znacznik czasu na poziomie zdarzenia: <code>timestamp_micros<\/code> wewn\u0105trz zdarzenia zosta\u0142o przyj\u0119te, tak\u017ce w \u0142adunku ze znacznikiem g\u00f3rnym sprzed godziny i drugim zdarzeniem sprzed dw\u00f3ch godzin. Jedno \u017c\u0105danie mo\u017ce zatem nie\u015b\u0107 wszystkie paragony jednej karty z ca\u0142ego okresu, ka\u017cdy z w\u0142asnym czasem.<\/p>\n\n<p>Udokumentowany pu\u0142ap to dwadzie\u015bcia pi\u0119\u0107 zdarze\u0144 i 130 kB na \u017c\u0105danie. Serwer walidacyjny nie wymusza \u017cadnego z nich \u2014 dwadzie\u015bcia sze\u015b\u0107 zdarze\u0144 przesz\u0142o, a sto paragon\u00f3w po dwie pozycje da\u0142o 28,8 kB i r\u00f3wnie\u017c przesz\u0142o \u2014 ale limit udokumentowany i niewymuszany pozostaje limitem, a skutki wida\u0107 w raportach.<\/p>\n\n<h2>Sprawdzenie przed wysy\u0142k\u0105, bo produkcja nigdy nie odpowiada<\/h2>\n\n<p>Produkcyjny endpoint zwraca na wszystko <strong>204 No Content<\/strong>. Zmierzone \u0142adunkami z tego artyku\u0142u: poprawny paragon, paragon sprzed trzydziestu dni, znacznik czasu w sekundach zamiast mikrosekund, czteroliterowy kod waluty oraz \u0142adunek zupe\u0142nie bez <code>client_id<\/code> \u2014 wszystkie 204, wszystkie poni\u017cej 220 milisekund, wszystkie z pust\u0105 tre\u015bci\u0105. Przypadek bez <code>client_id<\/code> jest tym wymownym, bo dok\u0142adnie ten \u0142adunek serwer walidacyjny odrzuca.<\/p>\n\n<p>Endpoint walidacyjny to ten sam adres z <code>\/debug<\/code> przed \u015bcie\u017ck\u0105. Nie sprawdza ani <code>measurement_id<\/code>, ani <code>api_secret<\/code>, \u0142adunek da si\u0119 wi\u0119c sprawdzi\u0107 bez wynoszenia jakiegokolwiek sekretu poza firm\u0119.<\/p>\n\n\n\n<figure style=\"--cbb-digits:1\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"bash\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">Shell<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-bash\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-function\">curl<\/span> <span class=\"cbb-attr\">-s<\/span> <span class=\"cbb-attr\">-X<\/span> POST \\\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-string\">'https:\/\/www.google-analytics.com\/debug\/mp\/collect?measurement_id=G-XXXXXXXXXX&amp;api_secret=zastepnik'<\/span> \\\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-attr\">-H<\/span> <span class=\"cbb-string\">'Content-Type: application\/json'<\/span> \\\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-attr\">-d<\/span> <span class=\"cbb-string\">'{\"client_id\":\"2531008582.2372831494\",<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-string\">       \"validation_behavior\":\"ENFORCE_RECOMMENDATIONS\",<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-string\">       \"events\":&#91;{\"name\":\"purchase\",\"params\":{\"currency\":\"EUR\",\"value\":48.90}}&#93;}'<\/span><\/span><\/code><\/pre><\/figure>\n\n\n\n<p><code>validation_behavior<\/code> nale\u017cy do sprawdzenia i do niczego innego. Wys\u0142ane na produkcyjny endpoint jest nieznanym polem, a nieznane pole uniewa\u017cnia dok\u0142adnie ten \u0142adunek, kt\u00f3ry mia\u0142o zabezpieczy\u0107.<\/p>\n\n<p>Jedna cecha serwera walidacyjnego kszta\u0142tuje spos\u00f3b korzystania z niego: zg\u0142asza on <strong>tylko pierwsze znalezisko<\/strong>. Trzy problemy w jednym zdarzeniu daj\u0105 jeden komunikat, a drugi pojawia si\u0119 dopiero po usuni\u0119ciu pierwszego. \u015awie\u017co zbudowany \u0142adunek potrzebuje wi\u0119c kilku rund \u2014 w\u0142a\u015bnie po to istnieje walidator wypisuj\u0105cy wszystkie b\u0142\u0119dy naraz.<\/p>\n\n<h2>Czego sprawdzenie nie wychwytuje<\/h2>\n\n<p>Z ustawionym <code>ENFORCE_RECOMMENDATIONS<\/code> wszystko poni\u017csze przesz\u0142o bez jednego komunikatu: <code>purchase<\/code> bez <code>transaction_id<\/code>, bez <code>value<\/code>, bez <code>currency<\/code>, bez <code>items<\/code>, z pust\u0105 tablic\u0105 <code>items<\/code> i zupe\u0142nie bez parametr\u00f3w. Dwa zdarzenia z tym samym <code>transaction_id<\/code> w jednym \u017c\u0105daniu przesz\u0142y. <code>user_id<\/code> o d\u0142ugo\u015bci 257 znak\u00f3w przesz\u0142o, o jeden znak ponad udokumentowane maksimum. Kod waluty <code>EURO<\/code> przeszed\u0142, ma\u0142ymi literami <code>eur<\/code> r\u00f3wnie\u017c.<\/p>\n\n<p>Nic z tego nie jest b\u0142\u0119dem walidatora; to granica tego, co kontrola protoko\u0142u mo\u017ce wiedzie\u0107. Czy paragon ma numer, czy kwota jest prawdziw\u0105 kwot\u0105 i czy ta sama sprzeda\u017c zosta\u0142a ju\u017c wys\u0142ana \u2014 to pytania o dane sklepu i odpowiedzi musz\u0105 istnie\u0107, zanim powstanie \u0142adunek.<\/p>\n\n<h2>Rytm wysy\u0142ki i cena przebiegu tygodniowego<\/h2>\n\n<p>Okno 72 godzin zamienia harmonogram w decyzj\u0119 projektow\u0105. Dla programu uruchamianego o 03:00 i obejmuj\u0105cego ca\u0142e dni:<\/p>\n\n<table>\n<tr><th>Rytm<\/th><th>Najstarszy paragon przy wysy\u0142ce<\/th><th>Udzia\u0142 poza oknem<\/th><\/tr>\n<tr><td>co noc<\/td><td>27 h<\/td><td>0 %<\/td><\/tr>\n<tr><td>co trzy dni<\/td><td>75 h<\/td><td>4,2 %<\/td><\/tr>\n<tr><td>co tydzie\u0144<\/td><td>171 h<\/td><td>58,9 %<\/td><\/tr>\n<\/table>\n\n<p>Przebieg tygodniowy traci ponad po\u0142ow\u0119 tego, co niesie, i traci to bezg\u0142o\u015bnie: endpoint odpowiada odrzuconym paragonom dok\u0142adnie tym samym kodem 204 co przyj\u0119tym. Przebieg nocny ma 45 godzin zapasu i dopiero ten zapas czyni nieudan\u0105 noc powtarzaln\u0105 \u2014 pomini\u0119ty przebieg da si\u0119 nadrobi\u0107 nast\u0119pnego dnia i wszystko mimo to dotrze.<\/p>\n\n<p>Ten zapas czasu przemawia te\u017c za przechowywaniem nieudanych wysy\u0142ek w kolejce. Ka\u017cdy niewys\u0142any paragon zachowuje w\u0142asny termin. Zapisanie jego czasu w kolejce pozwala ustali\u0107, kt\u00f3re paragony nadal mieszcz\u0105 si\u0119 w oknie wysy\u0142ki, a kt\u00f3re s\u0105 ju\u017c zbyt stare.<\/p>\n\n<h2>Czego to nie rozwi\u0105zuje<\/h2>\n\n<p>Trzy granice warto nazwa\u0107 wprost.<\/p>\n\n<p>Powy\u017csze pomiary opisuj\u0105 to, co przyjmuje <em>protok\u00f3\u0142<\/em>. Czy po\u0142\u0105czony zakup pojawi si\u0119 potem w konkretnym raporcie, zale\u017cy od to\u017csamo\u015bci raportowania us\u0142ugi i od tego, czy witryna w og\u00f3le zbiera <code>user_id<\/code> \u2014 to kwestia ustawie\u0144 GA4, nie protoko\u0142u, i nic z tego nie by\u0142o tu mierzone.<\/p>\n\n<p>Zwroty i anulowania nie maj\u0105 na tej drodze wygodnego miejsca. Zdarzenie <code>refund<\/code> istnieje, ale zwrot przychodz\u0105cy cztery dni po sprzeda\u017cy wypada z okna dok\u0142adnie tak samo jak sp\u00f3\u017aniony paragon.<\/p>\n\n<p>Czego okno nie dopuszcza, na to pozwala inna droga. Paragony starsze ni\u017c trzy dni, zwroty przychodz\u0105ce tydzie\u0144 p\u00f3\u017aniej i ka\u017cde uzupe\u0142nianie przesz\u0142o\u015bci nie nale\u017c\u0105 wcale do tego protoko\u0142u: przy us\u0142udze z w\u0142\u0105czonym eksportem do BigQuery nale\u017c\u0105 do z\u0142\u0105czenia z surowymi tabelami <code>events_*<\/code>, po tych samych identyfikatorach, kt\u00f3re buduje ten artyku\u0142. Ta droga nie zna limitu czasu. Jest te\u017c powodem, dla kt\u00f3rego kraw\u0119d\u017a 72 godzin pozostaje kwesti\u0105 harmonogramu, a nie trwa\u0142\u0105 strat\u0105 \u2014 dop\u00f3ki same paragony s\u0105 przechowywane.<\/p>\n\n<p>Sam protok\u00f3\u0142 jest za\u015b zamkni\u0119ty. Google przeni\u00f3s\u0142 Measurement Protocol w czerwcu 2026 roku do stanu ostatecznego \u2014 bez wy\u0142\u0105czenia i bez nowych funkcji \u2014 i kieruje nowe integracje serwer-serwer do Data Manager API. Dla sklepu, kt\u00f3rego kasa ju\u017c eksportuje paragony, nie jest to nag\u0142y wypadek: zmierzone tu granice s\u0105 granicami, kt\u00f3re pozostan\u0105.<\/p>\n\n<div class=\"lw-faq\">\n<h2>Pytania i odpowiedzi<\/h2>\n\n<h3>Co si\u0119 dzieje, gdy trzeba zmieni\u0107 pieprz, na przyk\u0142ad po wycieku?<\/h3>\n<p>Zmienia si\u0119 ka\u017cdy skr\u00f3t, a wraz z nim tego dnia urywa si\u0119 historia os\u00f3b w GA4. Dotyczy to trzech miejsc:<\/p>\n<ol>\n<li>user_id. Witryna i program wysy\u0142aj\u0105cy licz\u0105 je tym samym pieprzem. Je\u015bli prze\u0142\u0105czy si\u0119 tylko jedna z nich, powstaj\u0105 dok\u0142adnie te dwie obce sobie osoby, przed kt\u00f3rymi ostrzega artyku\u0142. Obie strony trzeba wi\u0119c przestawi\u0107 w tej samej chwili.<\/li>\n<li>Tabela powi\u0105za\u0144. Jej klucze to stare skr\u00f3ty, a HMAC nie da si\u0119 odwr\u00f3ci\u0107. Tabel\u0119 da si\u0119 odbudowa\u0107 tylko w ten spos\u00f3b, \u017ce ka\u017cdy numer karty z bazy klient\u00f3w zostaje zahaszowany starym i nowym pieprzem, a stary klucz zostaje zast\u0105piony nowym.<\/li>\n<li>Wyliczone client_id. One r\u00f3wnie\u017c pochodz\u0105 ze skr\u00f3tu. Ka\u017cda karta bez powi\u0105zania pojawia si\u0119 od chwili zmiany jako nowa to\u017csamo\u015b\u0107, a paragony sprzed zmiany i po niej si\u0119 nie \u0142\u0105cz\u0105.<\/li>\n<\/ol>\n<p>Zmiana pieprzu przypomina wi\u0119c bardziej migracj\u0119 danych ni\u017c rutynow\u0105 konserwacj\u0119. Pieprz znajduje si\u0119 wsz\u0119dzie tam, gdzie liczony jest skr\u00f3t, czyli w witrynie i w programie wysy\u0142aj\u0105cym. W obu miejscach musi by\u0107 r\u00f3wnie dobrze chroniony.<\/p>\n\n<h3>Dlaczego skr\u00f3t z kluczem, a nie po prostu SHA-256 z numeru karty?<\/h3>\n<p>Bo numer\u00f3w kart jest za ma\u0142o. Przy za\u0142o\u017conym formacie z\u0142o\u017conym ze sta\u0142ego przedrostka i o\u015bmiu cyfr, jak w przyk\u0142adzie LW 4471 9930, mo\u017cliwych jest tylko 100 milion\u00f3w numer\u00f3w; zahaszowanie ich wszystkich bez klucza i przypisanie ka\u017cdego skr\u00f3tu do jego numeru nie trwa na zwyk\u0142ym sprz\u0119cie d\u0142ugo. Pieprz uniemo\u017cliwia takie wyliczenie, dop\u00f3ki pozostaje tajny.<\/p>\n\n<h3>Po czym program wysy\u0142aj\u0105cy rozpoznaje nieudan\u0105 wysy\u0142k\u0119, skoro produkcyjny endpoint zawsze odpowiada 204?<\/h3>\n<p>Tylko po samym po\u0142\u0105czeniu: je\u015bli nie przychodzi odpowied\u017a, po\u0142\u0105czenie si\u0119 zrywa albo pojawia si\u0119 inny status ni\u017c 204, paragon trafia do kolejki. Wszystko, co dotyczy tre\u015bci, musi by\u0107 rozstrzygni\u0119te przed wysy\u0142k\u0105, bo endpoint niczego potem nie zg\u0142osi. Do samego programu wysy\u0142aj\u0105cego nale\u017c\u0105 wi\u0119c cztery kontrole:<\/p>\n<ul>\n<li>Czas paragonu w por\u00f3wnaniu z w\u0142asnym zegarem: to, co jest starsze ni\u017c 72 godziny, nie nale\u017cy ju\u017c do tego protoko\u0142u, tylko do drogi przez BigQuery, a to, co datowane jest w prz\u00f3d, zostaje wstrzymane.<\/li>\n<li>Formaty: ceny jako liczba z kropk\u0105 dziesi\u0119tn\u0105, client_id w postaci &lt;liczba&gt;.&lt;liczba&gt;, znacznik czasu w mikrosekundach.<\/li>\n<li>Pola, kt\u00f3rych brak serwer walidacyjny przepuszcza bez komunikatu: transaction_id, value, currency i items.<\/li>\n<li>Ka\u017cda nowa posta\u0107 \u0142adunku raz przez endpoint walidacyjny, w tylu rundach, a\u017c nie pojawi si\u0119 \u017caden komunikat, bo zg\u0142asza on tylko pierwsze znalezisko.<\/li>\n<\/ul>\n<p>Czy paragony naprawd\u0119 dotar\u0142y, da si\u0119 sprawdzi\u0107 dopiero w danych samej us\u0142ugi, przy w\u0142\u0105czonym eksporcie do BigQuery na przyk\u0142ad po numerach paragon\u00f3w.<\/p>\n<\/div>\n\n<div class=\"lw-quellen\">\n<h2>\u0179r\u00f3d\u0142a<\/h2>\n<ul>\n<li><a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/protocol\/ga4\/reference\" target=\"_blank\" rel=\"noopener noreferrer\">Measurement Protocol (Google Analytics 4) \u2014 Reference<\/a><\/li>\n<li><a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/protocol\/ga4\/validating-events\" target=\"_blank\" rel=\"noopener noreferrer\">Validate events for Measurement Protocol<\/a><\/li>\n<li><a href=\"https:\/\/support.google.com\/analytics\/answer\/9213390\" target=\"_blank\" rel=\"noopener noreferrer\">Reporting identity in Google Analytics<\/a><\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>Kasa wydaje paragon, numer karty i znacznik czasu. GA4 zna przegl\u0105darki. Ten poradnik buduje most mi\u0119dzy jednym a drugim za pomoc\u0105 Measurement Protocol: haszowanie numeru karty, zapis powi\u0105zania w koncie online, wyliczenie client_id dla pozosta\u0142ych i wysy\u0142ka zdarzenia purchase, kt\u00f3re dociera. Ka\u017cdy limit zosta\u0142 zmierzony 9 wrze\u015bnia 2026 roku &#8211; tak\u017ce ten, kt\u00f3ry serwer podaje b\u0142\u0119dnie we w\u0142asnym komunikacie.<\/p>\n","protected":false},"author":1,"featured_media":19917,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[117],"tags":[92035,92100,92238],"class_list":["post-17810","post","type-post","status-publish","format-standard","hentry","category-tutorials-pl","tag-conversion-tracking-pl","tag-google-analytics-4-pl","tag-tutorial-pl"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz | Lukas Wojcik<\/title>\n<meta name=\"description\" content=\"Zakupy sklepowe do GA4 przez Measurement Protocol: karta lojalno\u015bciowa jako klucz, zmierzone okno 72 godzin i \u0142adunek sprawdzony przed wysy\u0142k\u0105.\" \/>\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\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz | Lukas Wojcik\" \/>\n<meta property=\"og:description\" content=\"Zakupy sklepowe do GA4 przez Measurement Protocol: karta lojalno\u015bciowa jako klucz, zmierzone okno 72 godzin i \u0142adunek sprawdzony przed wysy\u0142k\u0105.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-19T16:40:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-17810-ga4-measurement-protocol-zakupy-skle-gk.png\" \/>\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\/png\" \/>\n<meta name=\"author\" content=\"Lukas Wojcik\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Lukas Wojcik\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/\"},\"author\":{\"name\":\"Lukas Wojcik\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"headline\":\"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz\",\"datePublished\":\"2026-10-19T16:40:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/\"},\"wordCount\":2424,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-17810-ga4-measurement-protocol-zakupy-skle-gk.png\",\"keywords\":[\"Conversion Tracking\",\"Google Analytics 4\",\"Tutorial\"],\"articleSection\":[\"Tutorials\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/\",\"name\":\"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz | Lukas Wojcik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-17810-ga4-measurement-protocol-zakupy-skle-gk.png\",\"datePublished\":\"2026-10-19T16:40:00+00:00\",\"description\":\"Zakupy sklepowe do GA4 przez Measurement Protocol: karta lojalno\u015bciowa jako klucz, zmierzone okno 72 godzin i \u0142adunek sprawdzony przed wysy\u0142k\u0105.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-17810-ga4-measurement-protocol-zakupy-skle-gk.png\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-17810-ga4-measurement-protocol-zakupy-skle-gk.png\",\"width\":1200,\"height\":630,\"caption\":\"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/digital-analytics-pl\\\/tutorials-pl\\\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz\"}]},{\"@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\":\"Lukas Wojcik\",\"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\":\"Lukas Wojcik\"},\"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":"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz | Lukas Wojcik","description":"Zakupy sklepowe do GA4 przez Measurement Protocol: karta lojalno\u015bciowa jako klucz, zmierzone okno 72 godzin i \u0142adunek sprawdzony przed wysy\u0142k\u0105.","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\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/","og_locale":"en_US","og_type":"article","og_title":"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz | Lukas Wojcik","og_description":"Zakupy sklepowe do GA4 przez Measurement Protocol: karta lojalno\u015bciowa jako klucz, zmierzone okno 72 godzin i \u0142adunek sprawdzony przed wysy\u0142k\u0105.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/","og_site_name":"Lukas Wojcik - Blog","article_published_time":"2026-10-19T16:40:00+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-17810-ga4-measurement-protocol-zakupy-skle-gk.png","type":"image\/png"}],"author":"Lukas Wojcik","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Lukas Wojcik","Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/#article","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/"},"author":{"name":"Lukas Wojcik","@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"headline":"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz","datePublished":"2026-10-19T16:40:00+00:00","mainEntityOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/"},"wordCount":2424,"commentCount":0,"publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-17810-ga4-measurement-protocol-zakupy-skle-gk.png","keywords":["Conversion Tracking","Google Analytics 4","Tutorial"],"articleSection":["Tutorials"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/","url":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/","name":"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz | Lukas Wojcik","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/#primaryimage"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-17810-ga4-measurement-protocol-zakupy-skle-gk.png","datePublished":"2026-10-19T16:40:00+00:00","description":"Zakupy sklepowe do GA4 przez Measurement Protocol: karta lojalno\u015bciowa jako klucz, zmierzone okno 72 godzin i \u0142adunek sprawdzony przed wysy\u0142k\u0105.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/#primaryimage","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-17810-ga4-measurement-protocol-zakupy-skle-gk.png","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-17810-ga4-measurement-protocol-zakupy-skle-gk.png","width":1200,"height":630,"caption":"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz"},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/digital-analytics-pl\/tutorials-pl\/ga4-measurement-protocol-zakupy-sklepowe-karta-lojalnosciowa\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Przesy\u0142anie zakup\u00f3w sklepowych do GA4 przez Measurement Protocol: numer karty lojalno\u015bciowej jako klucz"}]},{"@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":"Lukas Wojcik","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":"Lukas Wojcik"},"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\/posts\/17810","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"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=17810"}],"version-history":[{"count":3,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/17810\/revisions"}],"predecessor-version":[{"id":22730,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/17810\/revisions\/22730"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media\/19917"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=17810"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/categories?post=17810"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=17810"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}