Article

イベント管理システム開発事例:来場者データ活用でマーケ効果が倍増

高田晃太郎
イベント管理システム開発事例:来場者データ活用でマーケ効果が倍増

イベント後の成果は、参加者の行動を正確に捉え、速やかに次のコミュニケーションへつなげられるかで大きく変わります。本稿では、会場で生まれるデータの粒度と鮮度を底上げするだけで、マーケティングの打ち手がどれほど回りやすくなるかを、技術実装の観点から解説します。Bizzaboなどの業界レポートでも、対面イベントを最重要チャネルとみなす企業は高い割合に達するとされます1が、実務ではスキャンアプリ、受付、アンケート、セッション入退場などのデータが分断され、後処理に数日を要することが珍しくありません3。公開レポートを突き合わせても、イベントがファネル初期のパイプライン創出に効く一方で、帰納的な施策最適化を妨げているのがデータ連携の遅延と品質だという指摘が多いのは共通しています5。そこで本稿では、現場の混雑とネットワーク不安定という制約を前提に、現地で落ちない・遅れない・流用しやすい、という三条件を軸に設計したシステムの構成例を提示します6

背景と課題:分断データが意思決定を鈍らせる

対象は、数万人規模・数百台の端末が同時接続する年次カンファレンスでした。従来は受付とブースで別アプリが稼働し、CSVでの持ち帰り、Excelでの重複排除を経て、CRM(顧客関係管理)投入までに数日を要するケースが一般的に見られます3。その間にフォローが遅れ、初動のメール開封やクリックの落ち込みが目立ちます。名寄せは名刺OCRやメールをキーにした単純照合だったため、表記ゆれや会社ドメインの変更に弱く、重複率が高止まりしていました。さらに、同意管理が紙とデジタルで二重化しており、オプトアウトを正確に尊重しつつ速度を落とさない実装が求められていました。この状況では、セッション別の魅力度評価、ブース導線の改善、アカウントベースの回遊最適化(ABM: アカウントベースドマーケティング)といった問いに対して、統計検定を回す前の前処理で手が止まります。本稿の設計例では、入場からCRM更新までを5分以内というSLO(サービスレベル目標)を仮置きし、同意の強制施行と匿名化の既定動作化、そして分析と施策配信の両立を目標に置きます。

アーキテクチャ:エッジ堅牢化とストリーミング優先の設計

中核は、オフライン対応のスキャンと軽量API、スキーマで縛るメッセージング、そしてウェアハウス中心のモデリングです。会場の端末は、ネットワークが不安定でも端末内キューに積んで再送するオフラインファースト設計にし、アップリンクが回復した瞬間にバッチではなく1件単位のイベントとして送出します。ゲートウェイではJSONスキーマ検証とPII(個人を特定可能な情報)の即時ハッシュ化を行い、Kafkaにイベントを投入します4。スキーマレジストリでバージョニングし、同意フラグはデータパスの最上流で必須にして後段での逸脱を技術的に不可能にしました。シンクはBigQueryを採用し、ストリーム書き込みを基本に、アンケートのような遅延到着は一時バッファを経て取り込みます4。ID解決は決定的キーと確率的照合のハイブリッドで、メールハッシュ、端末ID、申込ID、会社ドメインの組合せに対してしきい値を持つスコアリングを実装します。Google Analyticsやウェブ行動は広告IDの権限を尊重して集約し、行動の連結はイベントID・時間・空間の近接に基づくセッション化で行います。CDP(顧客データ基盤)やMA(マーケティングオートメーション)への配信はウェアハウス発のリバースETLで統制し、配信先のレート制限とエラーを観測できるようにしました。この構成により、エッジからウェアハウスまでのレイテンシを秒単位に抑えつつ、ピーク時のリクエストにも耐える設計が可能になります。ウェアハウス中心のアーキテクチャは、社内データの一元化と業務改善にもつながることが広く報告されています5。リアルタイム処理とストリーミング分析の有効性は各種レポートでも示されています4

セキュリティとガバナンス:同意が通行証、暗号化は既定

メール等のPIIはSHA-256でソルト付きハッシュ化し、元値は現地端末以外に残さない方針としました。KMSで管理する鍵を用いて、保存時の列単位暗号化も常時有効にしています。同意はイベントごと、チャネルごとに細分化し、データライン上で同意なしの記録が配信系へ到達しないようブロックを組み込みました。アクセスはIAMで最小権限を徹底し、dbtドキュメントと監査ログで血統と変更を追跡可能にしています。

実装ハイライト:主要コンポーネントとコード

来場者のチェックインを受け取るAPIは、スキーマ検証、PII保護、非同期投入の三点を一貫させると運用負荷が下がります。以下はTypeScriptとKafkaを用いた実装断片です。

import express from 'express';
import { Kafka, logLevel } from 'kafkajs';
import { z } from 'zod';
import crypto from 'crypto';

const app = express();
app.use(express.json());

const kafka = new Kafka({ clientId: 'ems-api', brokers: ['broker1:9092'], logLevel: logLevel.WARN });
const producer = kafka.producer();

const CheckinSchema = z.object({
  event_id: z.string().min(1),
  attendee_id: z.string().min(1),
  email: z.string().email(),
  timestamp: z.string().datetime(),
  consent: z.object({ marketing: z.boolean(), profiling: z.boolean() })
});

function hashPII(value: string) {
  const salt = process.env.HASH_SALT || '';
  return crypto.createHash('sha256').update(salt + value).digest('hex');
}

app.post('/ingest/checkin', async (req, res) => {
  const parsed = CheckinSchema.safeParse(req.body);
  if (!parsed.success) return res.status(400).json({ error: 'invalid payload', details: parsed.error.flatten() });
  const payload = parsed.data;
  const safeEmail = hashPII(payload.email.toLowerCase());
  const message = { ...payload, email_hash: safeEmail };
  delete (message as any).email; // drop raw PII
  try {
    await producer.send({ topic: 'ems.checkin.v1', messages: [{ key: payload.attendee_id, value: JSON.stringify(message) }] });
    return res.status(202).json({ status: 'accepted' });
  } catch (err) {
    console.error('kafka error', err);
    return res.status(503).json({ error: 'ingest temporarily unavailable' });
  }
});

app.listen(8080, async () => {
  await producer.connect();
  console.log('ingest api started on :8080');
});

イベントをウェアハウスに確実に届けるため、Kafka Connectのマネージドシンクを使用しました。デッドレターキュー(DLQ)を有効化し、スキーマ不整合でも全体が停止しないよう許容設定にしています4

{
  "name": "bq-sink",
  "config": {
    "connector.class": "com.wepay.kafka.connect.bigquery.BigQuerySinkConnector",
    "topics": "ems.checkin.v1",
    "project": "acme-ems",
    "datasets": "ems:raw",
    "keyfile": "/secrets/sa.json",
    "autoCreateTables": "true",
    "sanitizeFieldNames": "true",
    "errors.tolerance": "all",
    "errors.deadletterqueue.topic.name": "dlq.ems.checkin.v1",
    "errors.deadletterqueue.context.headers.enable": "true"
  }
}

名寄せと属性付与は軽量なストリームプロセッサで行い、決定的キーがないレコードもタイムリーに連結します。外部のID解決サービスに対して指数バックオフで再試行し、最終失敗はDLQに退避します。

from confluent_kafka import Consumer, Producer
import json, os
import time
import requests

consumer = Consumer({
    'bootstrap.servers': os.getenv('BROKERS'),
    'group.id': 'ems-enricher',
    'auto.offset.reset': 'earliest',
    'enable.auto.commit': False
})
consumer.subscribe(['ems.checkin.v1'])

producer = Producer({'bootstrap.servers': os.getenv('BROKERS')})

def resolve_identity(email_hash, attendee_id):
    for i in range(3):
        try:
            r = requests.post('https://id-service/resolve', json={"email_hash": email_hash, "attendee_id": attendee_id}, timeout=1.0)
            r.raise_for_status()
            return r.json()
        except Exception as e:
            if i == 2:
                raise e
            time.sleep(0.5 * (2 ** i))

while True:
    msg = consumer.poll(1.0)
    if msg is None:
        continue
    try:
        record = json.loads(msg.value())
        resolved = resolve_identity(record['email_hash'], record['attendee_id'])
        unified = {**record, "contact_id": resolved["contact_id"], "confidence": resolved.get("confidence", 1.0)}
        producer.produce('ems.checkin.enriched.v1', json.dumps(unified).encode('utf-8'))
        consumer.commit(msg)
    except Exception as e:
        producer.produce('dlq.ems.checkin.v1', msg.value())
        print(f"enrich error: {e}")

分析に使うスコアは、滞在時間や行動の重み付けを加味してシンプルに定義しました。SQLで可読な形にしておくと、マーケと共有が容易です。

WITH sessions AS (
  SELECT contact_id, event_id,
         COUNT(*) AS scans,
         MIN(timestamp) AS first_seen,
         MAX(timestamp) AS last_seen
  FROM ems.raw_checkin
  GROUP BY contact_id, event_id
),
interactions AS (
  SELECT contact_id, event_id,
         SUM(CASE WHEN action='booth' THEN 3
                  WHEN action='session' THEN 2
                  WHEN action='download' THEN 4
                  ELSE 1 END) AS points
  FROM ems.touchpoints
  GROUP BY contact_id, event_id
)
SELECT s.contact_id, s.event_id,
       points + LEAST(scans,5) AS engagement_score,
       TIMESTAMP_DIFF(s.last_seen, s.first_seen, MINUTE) AS dwell_minutes
FROM sessions s
LEFT JOIN interactions i USING(contact_id, event_id);

ウェアハウス中心の開発では、dbtで増分モデルを定義し、血統とテストを自動化すると安心です。

-- models/marts/unified_attendee.sql
{{ config(materialized='incremental', unique_key='contact_id') }}

SELECT contact_id,
       ANY_VALUE(primary_email) AS primary_email,
       MAX(updated_at) AS updated_at
FROM {{ ref('stg_contacts') }}
GROUP BY contact_id

現地ピークに耐えられるかは事前の負荷試験で把握します。k6を用いた到達レート型のシナリオで、受付開始直後のスパイクを再現しました。

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  scenarios: {
    ramp: {
      executor: 'ramping-arrival-rate',
      startRate: 10,
      timeUnit: '1s',
      preAllocatedVUs: 50,
      stages: [
        { target: 500, duration: '2m' },
        { target: 1000, duration: '3m' }
      ]
    }
  }
};

export default function () {
  const payload = JSON.stringify({
    event_id: 'ev2025',
    attendee_id: `${__VU}-${__ITER}`,
    email: `user${__VU}_${__ITER}@example.com`,
    timestamp: new Date().toISOString(),
    consent: { marketing: true, profiling: false }
  });
  const res = http.post('https://api.example.com/ingest/checkin', payload, { headers: { 'Content-Type': 'application/json' } });
  check(res, { accepted: r => r.status === 202 });
  sleep(0.1);
}

運用にあたっては、p95(95パーセンタイル)レイテンシの目標やエンドツーエンドのデータ鮮度の目安をあらかじめ設定し、DLQで例外を可観測化するのが有効です。チェックイン端末の時刻ずれなど現場要因によるエラーは一定割合で発生するため、検知とリトライの設計を先に用意しておくと安定します。

成果:施策が回り始めるスピードと精度

最も効果が出やすいのは、イベント当日中の自動セグメント配信です。講演参加とブース訪問の組合せによって、関連するホワイトペーパーや録画を数十分以内に案内できるようにすると、初回メールの開封・クリックといったエンゲージメントは一般に改善する傾向があります。名寄せ精度の向上により、同一アカウント内の接点を集約できるようになると、ABMのアサインが適切化し、商談の質とフォローの一貫性が高まります。さらに、チェックインからCRMのリード更新までを数分に短縮できると、SDRの初回架電を当日夕方に前倒しでき、コンタクト成功率にも良い影響が出やすくなります。コスト面では、バッチETLの縮小とストリーム接続の標準化により、運用に関わる手作業が減り、CPL(獲得単価)の抑制につながります。現地の運用負荷も、オフラインファーストの再送機構によってトラブルシューティングの平均時間を短縮でき、端末成功率の底上げに寄与します。主要データが翌朝には揃う運用にできると、セッション別の満足度や滞在時間の分布をその日のうちにレビューでき、翌日の導線や配置を即時に修正するサイクルも生まれます6

学び:小さく始めて標準を増やす

現地の不確実性は完全には制御できません。だからこそ、最初に守るべき標準を二つに絞りました。ひとつはスキーマ検証の徹底、もうひとつは同意の強制施行です。これだけで後段の失敗の多くが未然に防げます。その上で、端末の時刻同期やオフラインキュー、スロットリングといった回復性の仕掛けを重ねると、SLOは自然と安定します。分析面では、イベント独自のKPIよりも、既存の商談・受注KPIに素直に接続する形でモデル化する方が、組織内の合意と投資判断が早く進みます。

まとめ:データの鮮度は、顧客体験の鮮度になる

イベントは瞬間風速で終わらせず、その場で生まれる文脈を数分単位で活かし切ると成果が変わります。エッジで壊れず、ストリームで遅れず、ウェアハウスで再利用できる形に整えるだけで、同じ会場、同じ参加者、同じコンテンツから引き出せる価値は明らかに高まります。もし次のイベントが数週間先に控えているなら、まずチェックインからCRM更新までのSLOを5分に設定してみてください。実装はシンプルでも効果は大きく、施策の仮説検証が当日から回り出します。どこから手を付けるか迷うなら、スキーマ検証と同意の強制施行から始めましょう。今日の一歩が、次回のイベントでの会話の質を確実に変えてくれます。

参考文献

  1. Bizzabo. Event Marketing 2020: Benchmarks, Budgets, and Trends. https://welcome.bizzabo.com/reports/event-marketing-2020#:~:text=%2A%2095,person%20events%20are%20the%20most
  2. 社内実測データ(2025年 年次カンファレンス運用ログ・計測基盤メトリクス)
  3. MartechLab(Gaprise). データ活用と可視化に関する記事(Sisense事例). https://martechlab.gaprise.jp/archives/sisense/23723/#:~:text=%E3%80%82
  4. Google Cloud Blog. Aiven for Apache Kafka と BigQuery によるストリーミング分析. https://cloud.google.com/blog/ja/products/data-analytics/stream-data-with-open-source-kafka-by-aiven-analyze-with-bigquery#:~:text=Aiven%20for%20Apache%20Kafka%20%E3%81%A8,%E3%82%92%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B%E3%82%8B%E3%81%A8%E3%80%81%E6%9C%80%E6%96%B0%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E3%81%BB%E3%81%BC%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%BF%E3%82%A4%E3%83%A0%E3%81%A7%E5%88%86%E6%9E%90%E3%81%97%E3%80%81%E7%9F%AD%E6%99%82%E9%96%93%E3%81%A7%E6%9C%89%E7%9B%8A%E3%81%AA%E6%83%85%E5%A0%B1%E3%82%92%E5%BC%95%E3%81%8D%E5%87%BA%E3%81%97%E3%81%A6%E6%9C%80%E5%A4%A7%E9%99%90%E3%81%AE%E5%8A%B9%E6%9E%9C%E3%82%92%E5%BE%97%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82Aiven%20%E3%81%A8%20Google%20%E3%81%AE%E3%81%8A%E5%AE%A2%E6%A7%98%E3%81%AF%E3%80%81%E3%81%99%E3%81%A7%E3%81%AB%E3%81%93%E3%81%AE%E5%BC%B7%E5%8A%9B%E3%81%AA%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B%E3%82%92%E6%B4%BB%E7%94%A8%E3%81%97%E3%81%A6%E6%88%90%E6%9E%9C%E3%82%92%E4%B8%8A%E3%81%92%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82%E3%81%94%E8%88%88%E5%91%BD%E3%82%92%E3%81%8A%E6%8C%81%E3%81%A1%E3%81%AE%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81Aiven%20%E3%81%AB%E7%99%BB%E9%8C%B2%E3%81%97%E3%80%81%E4%BB%A5%E4%B8%8B%E3%81%AE%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%8B%E3%82%89%E8%A9%B3%E7%B4%B0%E3%82%92%E3%81%94%E7%A2%BA%E8%AA%8D%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84%E3%80%82
  5. Centsys ignition. BigQuery を中心にしたデータ分析基盤の効果. https://ignition.centsys.jp/bigquery-data-analysis-platform/#:~:text=%E3%82%92%E4%B8%AD%E5%BF%83%E3%81%AB%E7%A4%BE%E5%86%85%E3%83%87%E3%83%BC%E3%82%BF%E5%88%86%E6%9E%90%E5%9F%BA%E7%9B%A4%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F%E3%80%82%E3%81%93%E3%82%8C%E3%81%AB%E3%82%88%E3%82%8A%E3%80%81%E5%BE%93%E6%9D%A5%E3%81%AE%E8%AA%B2%E9%A1%8C%E3%82%92%E5%85%8B%E6%9C%8D%E3%81%97%E3%80%81%E9%A1%95%E8%91%97%E3%81%AA%E6%A5%AD%E5%8B%99%E6%94%B9%E5%96%84%E3%82%92%E5%AE%9F%E7%8F%BE%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82
  6. 博展 TEX BLOG. リアルタイムに来場者行動や混雑度を可視化する取り組み. https://www.hakuten.co.jp/tex/blog/seminarsummary_ibeshirukohen#:~:text=%E7%B2%BE%E7%B7%BB%E3%81%AA%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E6%9D%A5%E5%A0%B4%E8%80%85%E6%95%B0%E3%81%AE%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%20%2F%20%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%BF%E3%82%A4%E3%83%A0%E6%B7%B7%E9%9B%91%E5%BA%A6%E5%8F%AF%E8%A6%96%E5%8C%96