Conversions APIとオフラインCV連携 ― 実成果を最適化に返す前段
媒体の最適化を「媒体CV」ではなく実成果で動かすために、Conversions APIやオフラインコンバージョン連携で成果を送り返す設計を扱います。本回は送信の設計まで。実際の活性化運用は活性化連載へ、効果検証は因果・実験連載へ接続します。
by Shin
媒体に「実成果」で最適化させるには、何を送り返せばいいのか。答えは、Conversions API やオフラインCV連携を使い、媒体CV(フォーム送信や初回CV)ではなく実成果(成約・購入・LTV)を送り返すことです。この回で扱うのは、その「送信」の設計まで——返すべき成果の定義、識別子、value、同意とハッシュ化の前提、そして二重計上を防ぐ重複排除——です。実際の同期・配信運用は活性化連載へ、効いたかどうかの検証は因果・実験連載へ送り出します。
計測の5層(収集→同意→識別→保管→送信)でいえば、本回は最後の「送信」を扱います。手前で接合率を作っても、媒体に戻すイベントの設計を誤ると、最適化が実成果から外れる——という、土台の最後の関門です。
媒体CVで最適化する危うさ
広告プラットフォームの自動入札は、「最適化目標として教えたイベント」へ向かって学習します。教えるイベントがフォーム送信や初回購入だと、媒体は「送信しやすい人」「初回は買うが続かない人」を集めにきます。最適化対象が成果の手前にある限り、最適化はその手前を増やす方向に働く——この構造は判断レイヤーの記事(本当の成果で測る(記述・予測編))で詳しく扱ったので、ここでは再掲にとどめます。
計測設計の観点で言い直すと、これは送信層の設計問題です。第5回までで広告接触から成果までの背骨(接合率)を作っても、媒体に返すイベントが媒体CVのままなら、せっかくの背骨は媒体の学習には流れ込みません。だから「何を成果として返すか」の定義が、送信層の最初の意思決定になります。返すものが count(件数)か、初回金額か、LTVを加味した金額かで、媒体が向かう先が変わります。
Conversions APIが返すもの
ブラウザ側のピクセル/タグ計測は、トラッキング防止機能・広告ブロック・JSエラー・回遊離脱などで静かに取りこぼします。サーバー側から直接イベントを送る経路(Conversions API、広義のサーバーサイド計測)は、この取りこぼしを補い、ブラウザ環境に依存しにくいという利点があります。これは第3回のサーバーサイド計測の延長線上にあります。
ただし、CAPIを「導入したか」より重要なのは「何を・いつ・いくらで返すか」です。送信イベントに最低限必要な設計項目は、おおむね次に整理できます。
- イベント種別(購入/サブスク開始/成約など、返す成果の定義そのもの)
- イベント時刻(媒体の学習窓に間に合う鮮度で送る)
- value と currency(媒体CVの件数ではなく、実成果の金額)
- マッチに使う識別子(クリックID
gclid/fbclid、次にハッシュ化したメール・電話) - 重複排除キー(
event_id。ブラウザ側計測との二重計上を防ぐ) - action_source(どこで発生した成果か)
ピクセルとCAPIを併用するときは、同一の event_id を両経路に持たせて媒体側で名寄せさせます。これを怠ると、同じ1件の成果が二度数えられ、媒体CVが実態より膨らみます。二重計上は「媒体CV≠実成果」の乖離を自分で広げる行為なので、送信設計の中で最初に潰すべき点です。
オフラインCV連携(成約・LTVを返す)
CAPIがブラウザの取りこぼしを補う送信経路だとすれば、オフラインCV連携は「ブラウザの外で、後から確定する成果」を返すための仕組みです。CRM上の成約、店舗・催事での購入、サブスクの継続や解約後に確定する真のLTV——これらをクリックIDやハッシュ化IDで広告クリックに突合し、媒体に遅延アップロードします。即時に確定しない成果を最適化に戻すには、この経路が必要です。
突合の精度は識別子で決まります。最も確実なのはクリックID(gclid / fbclid)で、これが残っていれば高い確率で元のクリックに紐づきます。クリックIDが無い成果は、ハッシュ化したメールや電話でのマッチに頼ることになり、マッチ率は下がります。ここで効いてくるのが第5回の接合率です。返したい成果のうち、媒体が使える識別子が紐づいている割合が、そのままオフラインCVのマッチ率の上限になります。 接合率を測らずにオフラインCVを始めると、想定より少ない成果しか媒体に届かず、最適化への寄与が読めません。
参考までに、私が運用で見てきた感覚値をレンジで丸めて置いておきます(N=1・設定や同意状況で大きく振れるので、絶対値として持ち帰らず、必ず自社で測ってください)。新規購入で gclid が保持できているのはおおむね6〜8割、ハッシュ化メールのマッチはおおむね4〜7割、両者を併用すると母数の取りこぼしが減る、という程度の幅です。これらは前回のSQLで is_matchable を集計すれば、自社の数字として出せます。
送信前の前提として、第4回の同意管理とプライバシーが効きます。同意のない利用者のデータは送らない。PII(メール・電話)は送信前に正規化(小文字化・トリム、電話はE.164形式)したうえでSHA-256でハッシュ化し、生のPIIを媒体に渡さない。これらは法務確認を前提とする論点で、ここで断定はしません(具体の可否は専門家確認に委ねます)。設計上の含意として押さえるべきは、同意で送れる母集団が変わると、媒体に返せる成果の母集団も変わるということです。つまり同意バイアスは分析だけでなく媒体の最適化にも伝播します。
ここまでを、SOLNA(過去の受託案件をもとに設計した架空D2C・既存連載と共通スキーマ)で「媒体へ返す成果イベント」テーブルとして組むと、最小構成は次のようになります。各媒体のAPI仕様や設定手順はドキュメントに譲り、ここでは「何を返すか」の設計だけを示します。
-- SOLNA: 媒体へ返す「成果イベント」テーブルの最小構成(BigQuery)
-- 目的: 媒体CV(フォーム送信)ではなく実成果(購入・サブスク開始)を、
-- 識別子・value・時刻・重複排除キーつきで一本化する。
-- 同意フラグ・email_sha256 は第4回(同意管理)の同意基盤で生成済みの前提。
WITH consented_orders AS (
SELECT
o.order_id,
o.customer_id,
o.order_at AS event_time,
o.order_type,
o.amount AS conversion_value, -- まずは確定の早い初回金額を返す
s.gclid, -- クリックID(最優先のマッチキー)
s.fbclid,
c.email_sha256, -- 正規化→SHA-256済み(生PIIは送らない)
c.marketing_consent -- 同意フラグ(未同意は送らない)
FROM orders AS o
JOIN customers AS c USING (customer_id)
LEFT JOIN sessions AS s
ON s.customer_id = o.customer_id
AND s.session_start <= o.order_at -- 購入より前のクリックに限定
WHERE o.order_type IN ('first', 'subscription') -- 返す成果の定義を明示
AND c.marketing_consent = TRUE -- 同意ゲート(第4回)
QUALIFY ROW_NUMBER() OVER (
PARTITION BY o.order_id ORDER BY s.session_start DESC
) = 1 -- 購入に最も近いクリックを1件だけ採用
)
SELECT
-- 重複排除キー: 媒体側のevent_idに渡し二重計上を防ぐ(第3回sGTMと整合)
TO_HEX(SHA256(CONCAT(order_id, '|', CAST(event_time AS STRING)))) AS event_id,
'purchase' AS event_name,
event_time,
conversion_value,
'JPY' AS currency,
gclid, fbclid, email_sha256,
-- マッチに使える識別子があるか(=マッチ率/接合率の母数を自社で測る列)
(gclid IS NOT NULL OR fbclid IS NOT NULL OR email_sha256 IS NOT NULL) AS is_matchable
FROM consented_orders;このテーブルから媒体へ渡すペイロードは、プラットフォーム非依存の形で次のように組めます。ここでも各媒体固有の手順には踏み込まず、「何を返すか」の構造に絞ります。
# 媒体へ返すペイロードの最小構成(プラットフォーム非依存の形)
# 各媒体のAPI仕様・設定手順はdocsへ。ここは「何を・いつ・いくらで返すか」の設計。
import hashlib
def normalize_and_hash(email: str) -> str:
# PIIは送信前に正規化→SHA-256。ハッシュ化前のメールは媒体に渡さない(第4回の前提)。
normalized = email.strip().lower()
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
def build_event(row) -> dict:
return {
"event_name": "purchase",
"event_time": row["event_time"], # 媒体の学習窓に間に合う鮮度で送る
"event_id": row["event_id"], # 重複排除(ピクセルとの二重計上防止)
"value": row["conversion_value"], # 媒体CVのcountではなく実成果の金額
"currency": "JPY",
"user_data": { # マッチキー: クリックID優先、次にハッシュPII
"gclid": row.get("gclid"),
"fbclid": row.get("fbclid"),
"em": row.get("email_sha256"),
},
"action_source": "system",
}
# 確定が遅い成果(サブスク継続・真LTV)は、後から「値の調整」として
# 同一event_idで再送する設計にする。媒体の調整可能窓を超えると反映されない。
def build_value_adjustment(event_id: str, adjusted_value: float) -> dict:
return {"event_id": event_id, "adjusted_value": adjusted_value, "currency": "JPY"}要点は3つです。第一に、value は件数ではなく実成果の金額を入れること。第二に、event_id で重複排除し、ブラウザ計測との二重計上を防ぐこと。第三に、遅れて確定する成果は同一 event_id の値調整として返し、媒体の調整窓内に収めること。実行可能な完成形ではなく、回をまたいで一貫させるための設計の骨格です。
ここまでが計測、ここからが活性化
ここまでが計測連載の守備範囲——「何を成果として返すか、どの識別子で、どの鮮度で、どこまで同意の取れた母集団で返すか」という送信の設計です。ここから先、warehouse から各媒体・各ツールへ実際に同期し、オーディエンスを連携し、ループを閉じて運用する部分は、活性化の連載(なぜ「分析して終わり」になるのか ― ループの不在)の領域になります。設計(本連載)と運用(活性化の連載)は順序として設計→運用が自然で、活性化は本連載で作った同意・識別・送信の基盤の上に乗ります。
章末チェックリスト
- 媒体CV(件数)最適化の危うさを、自分の言葉で言えるか
- 媒体に返す「実成果」を、件数ではなく金額・LTVの観点で定義できたか
-
event_idによる重複排除を、ピクセルとCAPIの両経路に通したか - 返す成果のうち識別子が紐づく割合(マッチ率の上限=接合率)を測ったか
- 同意ゲートとPIIのハッシュ化を送信前の前提に組み込んだか(法務確認前提)
- 遅れて確定する成果を、値調整として調整窓内に返す設計にしたか
- 計測(送信の設計)と活性化(同期・配信の運用)の境界を引けたか
実行(同期・配信・オーディエンス連携)は活性化の連載へ、その活性化が本当に効いたかの検証は因果・実験の連載へ送り出します。次回(発展・任意)は、媒体の中(ウォールドガーデン)を測るためのデータクリーンルーム入門です。
なお「自社では何を実成果として返すべきか」という判断そのもの——媒体CVと本当の成果の乖離をどう見極め、何を最適化目標に据えるか——は、対になる判断レイヤーの記事本当の成果で測る(記述・予測編)で扱っています。実装はこの回、判断はそちらへ。