成果を最適化に戻す ― Conversions APIとオフラインCVの活性化運用
媒体の最適化を「媒体CV」ではなく実成果(成約・LTV)で動かすために、Conversions APIやオフラインコンバージョンを送り返す運用を扱います。計測連載で設計した送信を、実際に回す側の勘所(重複排除・遅延・品質監視)を整理します。
by Shin
媒体(Meta・Googleなどの広告プラットフォーム)に「実成果で最適化してください」と頼む運用は、何に気をつければ事故らないのか。結論から言うと、何を送るか(成果の定義・識別子・同意とハッシュ化)の設計は計測設計の連載で済んでいて、活性化側の仕事は別にあります。重複排除・遅延・品質監視という、定期ジョブとして回し続けるための運用です。この記事では、計測設計の連載で設計した成果返しを「実際に毎日回す側」の勘所を、過去の受託案件をもとに設計した架空企業SOLNA(D2Cスキンケアの定期購入)のデータで具体化します。
扱うのは新しい送信仕様の話ではありません。同じ送信を、止まらず・二重に数えず・壊れたら気づける状態で運用する、という地味な話です。活性化は「分析を施策に戻す最後のひと区間」で、その最終出口が媒体最適化への成果返しにあたります。
媒体CV最適化から実成果最適化へ
媒体の最適化アルゴリズムは、こちらが渡した信号の質を超えられません。SOLNAで媒体に「purchase(初回購入)」だけを返すと、アルゴリズムは「初回は買うが1サイクルで解約する人」を安く大量に連れてきます。媒体管理画面のCV単価は綺麗に見えるのに、定期継続率とLTVは痩せていく——これは記述・予測編で扱った「成約しないリードを量産する」事故の、定期購入版です。
直し方は、返す信号を実成果に寄せることです。SOLNAなら、初回購入そのものではなく「nサイクル継続」や「90日LTV」を価値(value)として返す。すると媒体は「初回を踏むだけの人」ではなく「続く人」を探しにいきます。最適化対象を媒体CVから実成果へ差し替える——これが活性化としての成果返しの本質で、信号を磨くことが施策を磨くことに直結します。
送信は計測設計の連載、運用はここ(棲み分け)
ここは境界線を引いておかないと、計測設計の連載と内容が重複します。
計測設計の連載のConversions APIとオフラインCV連携 ― 実成果を最適化に返す前段が決めたのは「何を・どの識別子で・どう送るか」の設計です。実成果の定義、Conversions APIで返すイベント、オフラインCV連携のスキーマ、そして同意とハッシュ化の基盤。送信の作法はそこで完結しています。
この第4回が引き受けるのは、その送信を定期ジョブとして回し続ける運用です。warehouseから実成果を抜き、重複を排除し、遅れて確定する成果を拾い、品質を監視する。設計図は計測設計の連載、現場のオペレーションはここ、という棲み分けです。
| 観点 | 計測設計の連載(設計) | 活性化連載・本回(運用) |
|---|---|---|
| 主題 | 何を実成果として返すか/識別子・同意・ハッシュ化 | その送信を定期ジョブで回し、壊れを監視する |
| 成果物 | イベント定義・送信スキーマ・同意基盤 | 抽出SQL・送信ジョブ・監視ダッシュボード |
| 前提 | ― | 計測設計の連載の同意・ハッシュ化基盤と接合率 |
前提として、活性化は計測設計の連載の上にしか乗りません。識別子が一本に繋がっていなければ(接合率が低ければ)、オフラインの成果を媒体側のクリックに照合できず、そもそも返せる実弾が足りません。計測設計の連載→本連載の順番が自然なのはこのためです。
運用の勘所(重複排除・遅延・品質監視)
成果返しを定期ジョブにした瞬間、設計段階では見えなかった3つの問題が出ます。順に見ます。
重複排除(冪等性)
定期ジョブは必ず再実行されます。失敗リトライ、窓の重なり、手動の再送。そのたびに同じ成約を二重に送れば、媒体は実態の倍を最適化材料にします。対策は、各成果に安定した event_id を持たせ、媒体側で同一IDを重複と判定させること(CAPIのdedup・オフラインCVの注文ID単位の重複排除)です。SOLNAでは order_id をそのまま event_id に使えば、同じ注文は何度送っても1件です。ジョブ設計の原則は「同じ入力を何度流しても結果が同じ(冪等)」に保つことです。
遅延(late-arriving conversions)
実成果は遅れて確定します。SOLNAの「継続」は数サイクル後にしか分からず、B2Bの成約なら数週〜数ヶ月。一方で媒体側のアトリビューション窓は有限です。ここにトレードオフがあります。弱いが速い信号(初回購入)をすぐ返すか、強いが遅い信号(継続・LTV)を待って返すか。
実務解は段階送信です。初回購入は速報として返し、継続やLTVが確定したら value を更新する(値の上書き、または追加イベント)。そして毎回のジョブで直近 N 日を遡って再処理する「バックフィル窓」を持ち、後から確定した成果を取りこぼさない。窓を固定しすぎず、自社の成果確定までの分布に合わせて決めます。
品質監視(マッチ率・配信エラー・遅延分布)
送信は「送れた」では終わりません。媒体が照合できた割合(マッチ率/MetaのEvent Match Quality)、配信の成功率とエラー、送信遅延の分布、そしてdedup率。これらを定期チェックにして、低下したら気づける状態にします。
特にマッチ率の急落は、多くの場合この回より上流が壊れたサインです。同意基盤の不具合でハッシュ対象が欠けた、識別子の紐付けが切れた——つまり計測設計の連載の品質保証が崩れている。活性化の監視は、上流の壊れを下流で検知する役割も兼ねます。
ここから運用の最小構成です。まずwarehouseから返す実成果を抜くSQL。同意フラグでゲートし、安定IDを付け、バックフィル窓で遅延を拾います(BigQuery想定)。
-- SOLNA: 媒体へ返す実成果の抽出(冪等・同意ゲート・バックフィル付き)
-- 識別子と同意フラグは計測設計の連載の基盤に乗っている前提。
with consented as (
select
c.customer_id,
c.email_norm, -- 計測設計の連載で正規化済み(小文字・trim)。ハッシュ前
c.consent_ads -- 広告活用への同意(計測設計の連載の基盤)
from `solna.customers` c
where c.consent_ads = true -- 同意のない顧客は最初から除外
),
outcomes as (
select
o.order_id, -- これを event_id に使う(冪等の鍵)
o.customer_id,
o.order_at,
-- 実成果の value: 初回購入額ではなく「続く価値」に寄せる
-- 90日LTVが確定していればそれを、未確定なら初回額を速報値として返す
coalesce(l.ltv_90d, o.amount) as value,
(l.ltv_90d is not null) as is_final -- 確定フラグ(段階送信の判定に使う)
from `solna.orders` o
left join `solna.ltv_90d_by_order` l using (order_id)
where o.order_type in ('first', 'subscription')
-- バックフィル窓: 毎回ジョブで直近35日を再処理し、後から確定した成果を拾う
and o.order_at >= date_sub(current_date(), interval 35 day)
)
select
ot.order_id as event_id,
ot.order_at as event_time,
ot.value,
ot.is_final,
cs.email_norm -- 送信直前にハッシュ化(次のPythonコード)
from outcomes ot
join consented cs using (customer_id);次に送信ジョブの最小構成です。識別子の種類で道が分かれるのが要点です。メール等のPIIはSHA-256でハッシュして返しますが、クリックID(gclid)はクリックに直接紐づくのでハッシュ不要——オフラインCVをgclid経由で返せるなら、PIIを扱わずに済みます。
# SOLNA: 成果返しジョブの最小構成(冪等・段階送信・PII最小化)
import hashlib
def sha256_norm(value: str) -> str:
# 計測設計の連載の正規化(小文字・trim)後にハッシュ。媒体の照合仕様に合わせる
return hashlib.sha256(value.strip().lower().encode("utf-8")).hexdigest()
def build_event(row) -> dict:
payload = {
"event_id": row["event_id"], # = order_id。媒体側で重複排除させる鍵
"event_time": row["event_time"],
"value": row["value"],
"currency": "JPY",
}
if row.get("gclid"):
# クリックID経由: ハッシュ不要。PIIを送らずに照合できる
payload["gclid"] = row["gclid"]
else:
# PII経由: 同意済み顧客のみ。送信直前にハッシュ
payload["em_hashed"] = sha256_norm(row["email_norm"])
return payload
def run(rows, sink):
sent = 0
for row in rows:
ev = build_event(row)
# sink.send は同一 event_id を no-op にできる実装にする(冪等)
# 段階送信: is_final=False は速報、True 確定後に value を更新
ok = sink.send(ev, update=row["is_final"])
sent += int(ok)
# 監視メトリクスを必ず出す(送れた数だけでは品質は分からない)
return {"attempted": len(rows), "sent": sent}最後に、送信した後の品質を見る監視クエリの最小構成です。マッチ率・遅延・dedup率を前週比で追います。
-- SOLNA: 成果返しの品質監視(前週比で急落を検知)
select
date_trunc(sent_at, week) as wk,
count(*) as attempted,
countif(matched) as matched, -- 媒体が照合できた件数
safe_divide(countif(matched), count(*)) as match_rate, -- マッチ率
countif(is_duplicate) as duplicates, -- 媒体側で重複判定された件数
approx_quantiles(timestamp_diff(sent_at, event_time, hour), 100)[offset(50)]
as median_lag_h -- 成果確定→送信の中央遅延(時間)
from `solna.activation_send_log`
group by wk
order by wk;最適化が効いたかは別途検証
実成果を返すと媒体最適化の向き先は変わります。けれど「変わった」ことと「成果が増えた」ことは別です。前述のとおり、媒体管理画面のROAS改善は、数える対象とアトリビューションを変えた効果と区別がつきません。
活性化が本当に増分を生んだかは、媒体の自己申告では測れない因果の問いです。判定にはholdout(非配信群)や地域実験が要り、それは実験の連載(ユーザー単位で分けられないとき ― 地域実験と switchback)の主題になります。観測データから因果に迫る道具は記述・予測編の対になる因果編へ。成果返しの運用を整えることと、その効果を検証することは、必ず分けて持ちます。やりっぱなしにしないための分離です。
章末チェックリスト
- 返す信号を媒体CVではなく実成果(継続・LTV)に寄せられているか
- 計測設計の連載(送信の設計)と活性化(運用)の境界を説明できるか
-
event_idで冪等性を担保し、重複送信を媒体側で排除できているか - 段階送信とバックフィル窓で、遅れて確定する成果を拾えているか
- マッチ率・配信エラー・遅延を前週比で監視し、急落を検知できるか
- 「効いたか」は媒体の数字ではなく別途検証する前提を持っているか
成果を媒体に返す運用が整ったら、次は同じ予測スコア・セグメントを自社チャネルに出し分ける番です。次回はメール・LINE・オンサイトのパーソナライズで、効果検証のためのholdout(非配信群)を最初から設計に組み込む作法を扱います。判断材料の全体像(何を実成果として測り、何を動かすか)は対になる記述・予測編へ。