メインコンテンツへスキップ
5分で読めます

サーバーサイドトラッキング

POST /api/v1/events エンドポイントを使うと、バックエンドから直接 Zenovay に分析イベントを送信できます。決済 Webhook で取得した購入、認証サービスから発生する identify イベント、その他クライアントブラウザを真実のソースにできない場面でご利用ください。

イベントはブラウザトラッカーのイベントと同じ Postgres テーブルに到着しますが、source: 'server' でタグ付けされるため、ブラウザストリームと並んでダッシュボードに表示されます。app.zenovay.com の整合性レポートはこのフラグを利用して 2 つのストリームを区別します。

ブラウザトラッカーとサーバーサイドエンドポイントは互いに補完するものであり、置き換え可能ではありません。両方を併用してください。ブラウザトラッカーはページビューとエンゲージメントを取得し、サーバーエンドポイントはブラウザに送信を任せられない正本のイベント(購入、識別、サイト外の活動など)を取得します。

エンドポイント

POST/api/v1/events

最大 1,000 件のサーバーサイドイベントをバッチで取り込みます

URL: https://api.zenovay.com/api/v1/events

ヘッダー:

  • Authorization: Bearer zv_... — 必須
  • Content-Type: application/json — 必須
  • Sec-GPC: 1 — 任意。指定された場合、バッチ全体が gpc_opted_out の理由で拒否されます

認証

app.zenovay.com → 設定 → API キー で発行した zv_* API キーで認証してください。このキーは呼び出し元を 1 つのチームに紐付け、さらに単一のウェブサイト(スコープ site_access)、またはそのチーム配下のすべてのウェブサイト(スコープ full_access)のいずれかを認可します。

キーはエッジで SHA-256 によりハッシュ化され、website_api_keys に対して検証されます。プランに応じた月次および分単位の上限が適用されます(詳しくは レート制限 を参照してください)。

リクエストボディ

{
  "trackingCode": "ZV_AbC123...",
  "events": [
    {
      "type": "pageview",
      "ts": 1735689600000,
      "visitorId": "11111111-1111-4111-9111-111111111111",
      "sessionId": "22222222-2222-4222-9222-222222222222",
      "props": { "url": "https://example.com/pricing", "referrer": "" },
      "idempotencyKey": "pv-2026-05-02-abc"
    }
  ],
  "serverContext": {
    "clientIp": "203.0.113.42",
    "userAgent": "Mozilla/5.0 ...",
    "acceptLanguage": "en-US"
  }
}
フィールド必須備考
trackingCodestringはいウェブサイトのトラッキングコード。app.zenovay.com → ウェブサイト設定で確認できます。
eventsServerEvent[]はい1〜1,000 件のイベント。
events[i].typepageview | event | identify | goal | purchaseはいイベント分類 — 後述を参照。
events[i].tsnumberはいUnix ミリ秒。サーバー時刻から ±24 時間以内である必要があります。範囲外の場合は invalid_ts で拒否されます。
events[i].sessionIdstring条件付きpageviewgoal で必須。pageview では UUID が必要です(列が UUID 型のため)。
events[i].visitorIdstring条件付きpageviewidentifygoal で必須。pageview では UUID が必要です。
events[i].propsobjectはい種類ごとのペイロード — 後述を参照。
events[i].idempotencyKeystring推奨trackingCode 単位で一意であること。24 時間にわたり重複書き込みを防ぎます。最大 128 文字。
events[i].consentopt-in | opt-out | unknown任意opt-out の場合、当該イベントのみが consent_opted_out の理由で拒否されます。
serverContext.clientIpstring任意永続化前に日次ソルト付き SHA-256 でハッシュ化されます。平文は一切保存しません。

イベントタイプ

type書き込み先必須の props
pageviewvisitorspage_viewsuser_events、Analytics Engineurl
eventuser_events(event_type='custom')name(カスタムイベント名)
identifyidentified_users((website_id, visitor_id) で UPSERT)email、または以下のいずれか: namecustomer_idphonecompany。任意のカスタム属性も併せて指定可能。
goalgoal_completions(名前で目標を検索した上で書き込み)name(app.zenovay.com → 目標 で有効な目標と一致している必要があります)、任意で value
purchasepayment_eventsamount(数値)、任意で currency(既定値 USD)、payment_provider(stripe/lemonsqueezy/polar/server、既定値 server)、status(既定値 succeeded)

レスポンス

認証とバリデーションが成功した場合、エンドポイントは常に HTTP 200 を返します。イベント単位の結果はボディに記述されるため、成功と拒否のイベントを 1 つのバッチに混在させることができます。

{
  "acceptedCount": 4,
  "dedupedCount": 1,
  "rejectedCount": 0,
  "errors": []
}

イベントが拒否された場合、errors には元のインデックスとともに各失敗が一覧表示されます。

{
  "acceptedCount": 0,
  "dedupedCount": 0,
  "rejectedCount": 1,
  "errors": [
    { "index": 0, "reason": "invalid_ts", "message": "ts is missing or beyond ±24h skew tolerance" }
  ]
}

冪等性

各イベントには idempotencyKey を付与してください。以後 24 時間以内に同じ (trackingCode, idempotencyKey) を持つイベントは dedupedCount として返却され、二重には書き込まれません。128 文字を超えるキーや空のキーは invalid_idempotency_key の理由で拒否されます。

冪等性はエッジ(Cloudflare KV キャッシュ)とデータベース(visitorspage_viewsuser_events(website_id, idempotency_key) に対する部分的な一意インデックス)の両方で適用されます。KV 層は高速で、データベース層は同じキーを持つ 2 つのイベントが数ミリ秒のうちに別の Cloudflare コロに到達するというまれなエッジケースを捕捉します。

レート制限

分単位および月単位の上限は、ご利用中のサブスクリプションプランから継承されます。

プラン1 分あたり1 か月あたり
Free101,000
Pro3010,000
Scale60100,000
Enterprise1201,000,000

上限に達すると、エンドポイントは標準の X-RateLimit-* および X-Usage-* レスポンスヘッダーとともに HTTP 429 を返します。指数バックオフを推奨します。

プライバシー

  • IP は決して平文で保存されません。 serverContext.clientIp が指定されている場合、永続化前に日次ソルト付き SHA-256 でハッシュ化されます。指定がない場合は、センチネル値 unknown が保存されます。
  • GPC を尊重します。 Sec-GPC: 1 を伴うリクエストは、バッチ内のすべてのイベントを gpc_opted_out の理由で拒否します。行は一切書き込まれません。
  • イベント単位の同意。 consent: 'opt-out' のイベントは consent_opted_out の理由で個別に拒否されますが、同じバッチ内の他のイベントは引き続き受け入れられる場合があります。
  • サーバーサイドの同意取得は顧客側の責任です。 ブラウザトラッカーは Cookie レスであり、ePrivacy/GDPR の下で同意取得前から合法に動作しますが、サーバーサイドでの取り込みは定義上、お客様自身のアプリケーションにおける同意ポリシーの下で行われます。

サンプル

curl — pageviewBash
curl -X POST https://api.zenovay.com/api/v1/events \
-H "Authorization: Bearer $ZENOVAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "trackingCode": "ZV_YourTrackingCode",
  "events": [{
    "type": "pageview",
    "ts": '"$(date +%s%3N)"',
    "visitorId": "11111111-1111-4111-9111-111111111111",
    "sessionId": "22222222-2222-4222-9222-222222222222",
    "props": { "url": "https://yourdomain.com/pricing" },
    "idempotencyKey": "pv-2026-05-02-pricing-1"
  }]
}'
Node.js — Stripe Webhook からの purchaseJavaScript
async function reportPurchaseToZenovay(stripeEvent, visitorId) {
await fetch('https://api.zenovay.com/api/v1/events', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ZENOVAY_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    trackingCode: process.env.ZENOVAY_TRACKING_CODE,
    events: [{
      type: 'purchase',
      ts: Date.now(),
      visitorId,
      props: {
        amount: stripeEvent.data.object.amount_total / 100,
        currency: stripeEvent.data.object.currency.toUpperCase(),
        payment_provider: 'stripe',
        provider_event_id: stripeEvent.id,
        customer_email: stripeEvent.data.object.customer_email,
        plan_name: stripeEvent.data.object.metadata?.plan,
      },
      idempotencyKey: stripeEvent.id,
    }],
  }),
});
}
Python — サインアップ後の identifyPython
import requests, time, os

def identify_user(visitor_id: str, email: str, attributes: dict):
  requests.post(
      "https://api.zenovay.com/api/v1/events",
      headers={
          "Authorization": f"Bearer {os.environ['ZENOVAY_API_KEY']}",
          "Content-Type": "application/json",
      },
      json={
          "trackingCode": os.environ["ZENOVAY_TRACKING_CODE"],
          "events": [{
              "type": "identify",
              "ts": int(time.time() * 1000),
              "visitorId": visitor_id,
              "props": {"email": email, **attributes},
              "idempotencyKey": f"id-{visitor_id}-{int(time.time())}",
          }],
      },
      timeout=5,
  ).raise_for_status()
Ruby — Rails バックグラウンドジョブからのカスタム eventRuby
require 'net/http'
require 'json'
require 'securerandom'

ZENOVAY_API_KEY = ENV.fetch('ZENOVAY_API_KEY')
ZENOVAY_TRACKING_CODE = ENV.fetch('ZENOVAY_TRACKING_CODE')

def track_event_zenovay(event_name, visitor_id, properties = {})
uri = URI('https://api.zenovay.com/api/v1/events')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 5

request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{ZENOVAY_API_KEY}"
request['Content-Type'] = 'application/json'
request.body = {
  trackingCode: ZENOVAY_TRACKING_CODE,
  events: [{
    type: 'event',
    ts: (Time.now.to_f * 1000).to_i,
    visitorId: visitor_id,
    props: { name: event_name, **properties },
    idempotencyKey: "#{event_name}-#{visitor_id}-#{SecureRandom.hex(8)}"
  }]
}.to_json

response = http.request(request)
raise "Zenovay tracking failed: #{response.code}" unless response.code.to_i == 200
JSON.parse(response.body)
end

# 例: Sidekiq ジョブまたは Rails コントローラから
track_event_zenovay('newsletter_subscribed', visitor_id, source: 'footer_form')

拒否理由

reason発生する条件
gpc_opted_outリクエストに Sec-GPC: 1 が含まれていた場合。バッチ全体が拒否されます。
consent_opted_out当該イベントが consent: 'opt-out' を持っていた場合。
invalid_tsts が欠落、数値以外、またはサーバー時刻から ±24 時間を超えている場合。
invalid_event_typetype がサポートされている 5 種の値のいずれでもない場合。
invalid_idempotency_keyidempotencyKey が空、または 128 文字を超えている場合。
invalid_visitor_id必須の visitorId が欠落、または pageview で UUID 形式でない場合。
invalid_session_id必須の sessionId が欠落、または pageview で UUID 形式でない場合。
unknown_goalprops.name が当該ウェブサイトの有効なカスタム目標のいずれにも一致しない場合。
invalid_payment_providerprops.payment_providerstripe/lemonsqueezy/polar/server のいずれでもない場合。
internal_error想定外のデータベースまたはランタイム障害。同じ idempotencyKey でリトライしても安全です。

これらのイベントはダッシュボードのどこに表示されるか

各イベントタイプは Zenovay の特定の画面に表示されます:

type表示先
pageviewページタブ、リアルタイムアクティビティ、訪問者セッションタイムライン
eventカスタムイベント一覧、訪問者タイムライン(特定のイベントは event_name でフィルタ可能)
identify識別済みユーザーページ、訪問者プロファイル詳細
goal目標タブ、ファネル完了統計
purchase収益ダッシュボード、収益アトリビューション、識別済みユーザープロファイル

各 API キーには app.zenovay.com → 設定 → API キー → [キーをクリック] に専用の サーバーサイドアクティビティ パネルがあります。キーのウェブサイトスコープにおける直近 24 時間および 7 日間の受け入れ済みイベント数、上位イベントタイプ、最新 20 件のイベントを表示します。

ここに表示されるのは受け入れ済みイベントのみです。拒否理由は POST /api/v1/events のレスポンス本体で同期的に返されます — 送信側のデバッグ時にはそちらを確認してください。

このエンドポイントが行わないこと

  • カスタム目標をその場で作成しません。事前に app.zenovay.com → 目標 で定義してください。
  • オフラインの活動をオンラインの訪問者にマッチングしません。この機能は別エンドポイントとしてロードマップ上にあります。
  • ブラウザトラッカーを置き換えません。バックエンド由来のページビューには、props で指定しない限り、デバイス、OS、ブラウザ、ジオデータは含まれません。
  • 訪問者 ID を生成しません。お客様側で既に保有している UUID を使用してください。pageview イベントでは、その UUID は実際の RFC-4122 UUID である必要があります。
このページは役に立ちましたか?