Skip to content

Instantly share code, notes, and snippets.

@kobitoDevelopment
Last active January 30, 2026 03:37
Show Gist options
  • Select an option

  • Save kobitoDevelopment/b30baad1638ee01f6eb8fddeef6e5096 to your computer and use it in GitHub Desktop.

Select an option

Save kobitoDevelopment/b30baad1638ee01f6eb8fddeef6e5096 to your computer and use it in GitHub Desktop.

クロスサイトコンバージョン計測(img ピクセル方式)

サイトA(送客元)からサイトB(送客先)への遷移を計測する汎用的な手法。 CMS や特定のサービスに依存しない、ピュア HTML + JavaScript で実現できるアプローチ。


前提

  • サイトA とサイトB は異なるドメインで運用されている
  • サイトB には複数の流入経路があり、サイトA 経由のユーザーのみを識別・計測したい
  • サイトA 側に計測データを受け取る API エンドポイントが存在する

手順概要

1. サイトA → サイトB へのパラメータ付き遷移

サイトA からサイトB への遷移リンクに、一意の識別子をパラメータとして付与する。

https://siteb.hoge?id=fuga123

この識別子(以下 click_id)により、サイトB 側で「サイトA 経由の流入である」と判定できる。

2. サイトB 側で URL パラメータを Cookie に保存

サイトB の LP(ランディングページ)で、URL パラメータから click_id を抽出し Cookie に保存する。 Cookie に保存する理由は、LP とサンクスページ(コンバージョン地点)が別ページである場合に click_id を引き継ぐため。

3. サイトB 側で img 要素を動的に生成

<img> タグの src 属性にサイトA の計測 API の URL を指定し、DOM に挿入する。 ブラウザが画像を読み込む際の GET リクエストが計測 API に到達する。

<img> タグは CORS 制約を受けないため、サイトB のドメインからサイトA のドメインへのリクエストに CORS 設定は不要。

4. サイトA 側でリクエストを受信・記録

手順 3 の時点でサイトA の計測 API にリクエストが発生する。 API 側で click_idevent の種別(LP 着地 / サンクスページ着地)をログまたは DB に記録することで、サイトA 経由でサイトB に着地した人数を計測できる。


計測ポイント

# 計測ポイント event 値 発火タイミング
1 サイトB LP に着地 visit LP のページ表示時
2 サイトB サンクスページ着地 conversion サンクスページのページ表示時

生成される URL の例

https://example.com?click_id=clk_1706500000000_a1b2c3d4e

click_id の形式は任意。タイムスタンプ + ランダム文字列の組み合わせにすると衝突を回避しやすい。


コード

LP 着地計測用(サイトB の LP に設置)

URL パラメータから click_id を取得し、Cookie に保存したうえで計測 API に通知する。 click_id が存在しない場合(= サイトA 経由でない場合)は何も実行しない。

<script>
  (function () {
    const clickId = new URLSearchParams(window.location.search).get("click_id");

    if (!clickId) return;

    // Cookie に保存(30日間有効)
    const expires = new Date();
    expires.setDate(expires.getDate() + 30);
    document.cookie =
      "_mx_cid=" + clickId + "; expires=" + expires.toUTCString() + "; path=/";

    // 計測 API にアクセス通知(img ピクセル)
    const img = document.createElement("img");
    img.src =
      "サイトAのURL/api/track?click_id=" +
      encodeURIComponent(clickId) +
      "&event=visit";
    img.width = 1;
    img.height = 1;
    img.style.position = "absolute";
    img.style.left = "-9999px";
    img.alt = "";
    document.body.appendChild(img);
  })();
</script>

サンクスページ着地計測用(サイトB のサンクスページに設置)

Cookie から click_id を取得し、計測 API にコンバージョン通知を送信する。 送信後に Cookie を削除して重複計測を防止する。

<script>
  (function () {
    // Cookie から click_id を取得
    const match = document.cookie.match(/(^|;\s*)_mx_cid=([^;]*)/);
    const clickId = match ? match[2] : null;

    if (!clickId) return;

    // 計測 API にコンバージョン通知(img ピクセル)
    const img = document.createElement("img");
    img.src =
      "サイトAのURL/api/track?click_id=" +
      encodeURIComponent(clickId) +
      "&event=conversion";
    img.width = 1;
    img.height = 1;
    img.style.position = "absolute";
    img.style.left = "-9999px";
    img.alt = "";
    document.body.appendChild(img);

    // 重複計測防止(過去の日時をexpiresに指定して有効期限切れにする)
    document.cookie =
      "_mx_cid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
  })();
</script>

計測 API の仕様(サイトA 側に実装)

項目
URL /api/track
メソッド GET
パラメータ click_id (string), event ("visit" | "conversion")
レスポンス 1x1 透明 GIF 画像

レスポンスとして 1x1 透明 GIF を返すことで、ブラウザのコンソールに画像読み込みエラーが出ることを防ぐ。 キャッシュヘッダーは Cache-Control: no-store, no-cache, must-revalidate を設定し、毎回リクエストが発生するようにする。

テーブル定義(MySQL)

CREATE TABLE tracking_events (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    click_id VARCHAR(255) NOT NULL,
    event ENUM('visit', 'conversion') NOT NULL,
    ip VARCHAR(45) DEFAULT NULL,
    user_agent TEXT DEFAULT NULL,
    referer TEXT DEFAULT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_click_id (click_id),
    INDEX idx_event (event)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

計測 API の実装例(PHP)

/api/track.php として設置する。 click_idevent をバリデーションし、tracking_events テーブルに INSERT したうえで 1x1 透明 GIF を返す。

<?php

declare(strict_types=1);

// DB 接続情報
$dsn = 'mysql:host=localhost;dbname=your_database;charset=utf8mb4';
$dbUser = 'your_user';
$dbPass = 'your_password';

// パラメータ取得
$clickId = $_GET['click_id'] ?? '';
$event = $_GET['event'] ?? '';

// バリデーション
$validEvents = ['visit', 'conversion'];

if ($clickId !== '' && in_array($event, $validEvents, true)) {
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? null;
    $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;
    $referer = $_SERVER['HTTP_REFERER'] ?? null;

    try {
        $pdo = new PDO($dsn, $dbUser, $dbPass, [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
        ]);

        $stmt = $pdo->prepare(
            'INSERT INTO tracking_events (click_id, event, ip, user_agent, referer)
             VALUES (:click_id, :event, :ip, :user_agent, :referer)'
        );
        $stmt->execute([
            ':click_id' => $clickId,
            ':event' => $event,
            ':ip' => $ip,
            ':user_agent' => $userAgent,
            ':referer' => $referer,
        ]);
    } catch (PDOException $e) {
        error_log('[Track] DB save failed: ' . $e->getMessage());
    }
}

// 1x1 透明 GIF を返す
header('Content-Type: image/gif');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');

// GIF89a 1x1 transparent pixel
echo base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');

集計クエリの例

-- LP 着地数
SELECT COUNT(*) AS visit_count
FROM tracking_events
WHERE event = 'visit';

-- サンクスページ着地数
SELECT COUNT(*) AS conversion_count
FROM tracking_events
WHERE event = 'conversion';

-- click_id ごとの到達状況
SELECT
    click_id,
    MAX(event = 'visit') AS reached_lp,
    MAX(event = 'conversion') AS reached_thanks,
    MIN(created_at) AS first_seen
FROM tracking_events
GROUP BY click_id
ORDER BY first_seen DESC;

-- 日別の集計
SELECT
    DATE(created_at) AS date,
    SUM(event = 'visit') AS visit_count,
    SUM(event = 'conversion') AS conversion_count
FROM tracking_events
GROUP BY DATE(created_at)
ORDER BY date DESC;

制約事項

Cookie の有効期間

  • Safari ITP により、サードパーティ Cookie は最大 7 日間で削除される
  • LP 着地からサンクスページ到達までの期間が 7 日を超えると、Cookie が消失し conversion を計測できない
  • ブラウザの Cookie ブロック設定が有効な場合、Cookie に click_id を保存できないため計測不可

広告ブロッカー

  • 1x1 ピクセル画像のパターンは広告ブロッカーの検出対象になる場合がある
  • ブロックされた場合、visit / conversion のいずれも計測 API に到達しない

重複計測

  • サンクスページ側のスクリプトで Cookie を削除することで、同一ユーザーの重複コンバージョン計測を防止している
  • LP 側の visit は同一 click_id での再訪問時にも発火する(Cookie の上書きのみで重複制御はしていない)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment