開発者ガイド

訪問者識別API

ログイン中のユーザーをプログラムで識別すれば、メールアドレスの入力を求められることがなくなり、会話は自動で連絡先に紐づきます。

使うべき場面

Webサイトにログイン中のユーザーがいる場合(SaaSアプリ、ダッシュボード、会員ポータルなど)、あなたはすでにその人が誰かを知っています。identify コマンドでメールアドレスをSonnyに渡せば、ユーザーが再入力する必要はありません。

  • ログイン中のユーザーにはメールアドレスの入力を一切求めない
  • チャットの会話を自動で連絡先の記録に紐づける
  • 訪問者の名前とメールアドレスを受信トレイですぐに確認できる
  • 認証済みIDで、ブラウザやデバイスをまたいで履歴を引き継げる

デバイスをまたいだ継続

認証済みの訪問者ID

メールアドレスによる基本の識別は便利ですが、ブラウザはどんなメールアドレスでも名乗れます。認証済みIDでは、サーバーが署名した有効期限の短いJWTを追加します。これにより、Sonnyは顧客の連絡先をWebサイトのチャット履歴の所有者として安全に扱えるようになり、別のブラウザやデバイスでも同じ会話が表示されます。

オフ

デフォルトです。既存の埋め込みや訪問者には何も変わらず、履歴はブラウザの訪問者IDに紐づいたままです。

任意の認証 (おすすめ)

有効なJWTがあれば、デバイスをまたいだ履歴を利用できます。JWTのない訪問者は、これまでどおり匿名またはメールアドレスによる識別で利用できます。

識別に証明を必須にする

匿名のチャットは引き続き使えますが、identify とカスタム属性の呼び出しには有効な署名付きJWTが必要です。

認証モードによって、チャット前の必須項目が任意になることはありません。各モードで必須のメールアドレスがどのように満たされるかは、チャット前の必須情報をご覧ください。

1. 署名シークレットを生成する

チャネル → あなたのチャネル → ライブチャット を開き、安全な訪問者ID を探して、モードを選び、シークレットを生成します。平文は一度だけ表示されます。バックエンドのシークレットマネージャーに SONNY_IDENTITY_SECRET として保存してください。署名シークレットを、ブラウザのコード、埋め込みスニペット、公開される環境変数、ソースリポジトリに含めないでください。

2. バックエンドで有効期限の短いJWTを発行する

HS256 で署名します。Sonnyには user_id、email、iat、exp が必要です。user_id は、メールアドレスではなく、自社のデータベースの固定のIDにしてください。トークンの有効期限は最大24時間で、15分がおすすめのデフォルトです。時刻のずれは最大60秒まで許容されます。

merchantName、subdomain、plan、platform、role など、信頼できるサポート情報として、サーバーで署名した traits オブジェクトを含めることもできます。属性の値は、文字列、有限の数値、真偽値、null のいずれかにする必要があります。Sonnyはこれらを、Webサイト単位の認証済み連絡先IDに保存します。ブラウザから送られた属性は未認証のままで、これらの署名付き属性に昇格されることはありません。

Node.js
import { SignJWT } from 'jose';

export async function createSonnyIdentityToken(user) {
  const secret = new TextEncoder().encode(process.env.SONNY_IDENTITY_SECRET);

  return new SignJWT({
    user_id: String(user.id),
    email: user.email,
    traits: {
      merchantName: user.merchant.name,
      subdomain: user.merchant.subdomain,
      plan: user.merchant.plan,
      platform: user.merchant.platform,
      role: user.role
    }
  })
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime('15m')
    .sign(secret);
}
Ruby
require 'jwt'

def sonny_identity_token(user)
  now = Time.now.to_i
  payload = {
    user_id: user.id.to_s,
    email: user.email,
    traits: {
      merchantName: user.merchant.name,
      subdomain: user.merchant.subdomain,
      plan: user.merchant.plan,
      platform: user.merchant.platform,
      role: user.role
    },
    iat: now,
    exp: now + 15 * 60
  }

  JWT.encode(payload, ENV.fetch('SONNY_IDENTITY_SECRET'), 'HS256')
end
Python
import os
from datetime import datetime, timedelta, timezone
import jwt

def sonny_identity_token(user):
    now = datetime.now(timezone.utc)
    return jwt.encode({
        "user_id": str(user.id),
        "email": user.email,
        "traits": {
            "merchantName": user.merchant.name,
            "subdomain": user.merchant.subdomain,
            "plan": user.merchant.plan,
            "platform": user.merchant.platform,
            "role": user.role,
        },
        "iat": now,
        "exp": now + timedelta(minutes=15),
    }, os.environ["SONNY_IDENTITY_SECRET"], algorithm="HS256")
PHP
use Firebase\JWT\JWT;

function sonnyIdentityToken($user): string {
    $now = time();
    return JWT::encode([
        'user_id' => (string) $user->id,
        'email' => $user->email,
        'traits' => [
            'merchantName' => $user->merchant->name,
            'subdomain' => $user->merchant->subdomain,
            'plan' => $user->merchant->plan,
            'platform' => $user->merchant->platform,
            'role' => $user->role,
        ],
        'iat' => $now,
        'exp' => $now + (15 * 60),
    ], $_ENV['SONNY_IDENTITY_SECRET'], 'HS256');
}

3. ウィジェットにはJWTだけを渡す

通常のアプリケーションのセッションで保護されたエンドポイントからJWTを返します。identify は init の前後どちらでも呼び出せます。ウィジェットはJWTをメモリー内にのみ保持し、localStorage、Cookie、URLに保存することはありません。

JavaScript
// Fetch a short-lived JWT from your authenticated backend.
// Your signing secret never reaches this code.
const { userJwt } = await fetch('/api/sonny-identity').then((res) => res.json());

sonny('identify', { userJwt });
sonny('init', { siteId: 'YOUR_SITE_ID' });

// Sonny asks for a fresh token after expiry or an emergency key change.
window.addEventListener('sonny:identity-required', async () => {
  const { userJwt } = await fetch('/api/sonny-identity').then((res) => res.json());
  sonny('identify', { userJwt });
});

更新、ログアウト、ローテーションを安全に行う

  • 更新:JWTの期限が切れる前に(15分のトークンなら約10分後に)更新し、sonny('identify', { userJwt }) を呼び出します。期限切れや署名キーの変更に備えて、sonny:identity-required もリッスンしてください。
  • ログアウト:sonny('reset') を呼び出します。メモリー内のJWTが破棄され、ブラウザのウィジェットセッションがクリアされ、新しい匿名の訪問者として開始します。
  • アカウントの切り替え:ユーザーBのJWTを取得して sonny('identify', { userJwt }) を呼び出す前に、ユーザーAについて sonny('reset') を呼び出します。あるユーザーのトークンを、別のログインセッションに持ち込まないでください。
  • 定期的なローテーション:Sonnyは以前のシークレットを24時間受け付けるため、すべてのバックエンドのインスタンスを更新する時間があります。この重複期間が終わるまで、次の定期ローテーションはできません。
  • シークレットの漏えいが疑われる場合:今すぐ置き換え を選びます。古い署名キーは両方とも、新しいリクエストの認証にすぐ使えなくなり、接続中のウィジェットはホストページに新しいJWTを要求します。
  • 認証をオフにする:Sonnyは現在と以前の署名シークレットを破棄し、認証済みのウィジェットを切断します。認証を再び有効にする前に、新しいシークレットを生成してください。

IDの競合は自動で統合されません

固定のユーザーIDとメールアドレスが別々のSonnyの連絡先を指している場合、認証では顧客の記録を勝手に統合せず、競合を返します。トークンを修正するか、Sonnyで連絡先を統合してから、再試行してください。

以前に署名なしのメールアドレスだけで紐づけられたチャットは、自動的に認証済みの履歴としては扱われません。これにより、ブラウザから送られたメールアドレスでほかの顧客の会話にアクセスできてしまうことを防ぎます。

ヘルプセンターのログイン

既存の顧客ログインを、ホストされたヘルプページや独自ドメインのヘルプページに接続します。ウィジェットと同じWebサイトの署名シークレットと、有効期限の短いJWTを使います。メールアドレスによる基本の識別やSonnyのスタッフ用ログインでは、認証済みの読者としてのアクセスは付与されません。

  1. このチャネルについて、上記の認証済み訪問者IDの設定を完了してください。チャネル → あなたのチャネル → ライブチャットで、「任意の認証」または「識別に証明を必須にする」を有効にします。
  2. { "userJwt": "SIGNED_TOKEN" } を返す認証済みのエンドポイントをアプリに実装します。トークンは、ログイン中の顧客と信頼できる属性からサーバーで発行してください。ブラウザから要求されたプランやロールを受け付けてはいけません。
  3. チャネル → あなたのチャネル → ヘルプセンター → ヘルプセンターのアクセス設定で、「認証済みの顧客」と、必要に応じて顧客オーディエンスを選びます。「顧客ログインURL」にアプリのログインページを入力し、「アクセス設定を保存」を選択します。
  4. 顧客がログインしたら、YOUR_SLUG をヘルプセンターのアドレスに置き換えて、以下の交換処理を実行します。ログインURLだけでは不十分で、アプリがこの交換処理を完了する必要があります。
JavaScript
// Run after your application authenticates the customer.
// This endpoint must use the server's session and trusted customer traits.
const response = await fetch('/api/sonny-identity');
if (!response.ok) throw new Error('Unable to sign in to the help center');
const { userJwt } = await response.json();
const signIn = new URL('https://www.usesonny.com/help/YOUR_SLUG/auth');
signIn.searchParams.set('token', userJwt);
signIn.searchParams.set('return', '/help/YOUR_SLUG');
window.location.assign(signIn);

独自ドメインの場合は、signIn を https://help.example.com/auth に設定し、/ または /articles/getting-started に戻します。ホストされたページの戻り先は /help/YOUR_SLUG で始まります。戻り先は同じホストにする必要があります。

交換処理では、安全なHTTP-only Cookieが設定され、URLからトークンを取り除くためにリダイレクトされます。読者のセッションはJWTとともに、最大24時間で期限切れになります。有効期限の短いトークンを使い、交換URLをログに残さないようにしてください。JWTはこの1回のログイン交換のためのもので、共有する記事のリンクに含めるものではありません。

ログインとアクセスをテストする

  1. ログアウトした状態で非公開のヘルプセンターにアクセスします。ログインボタンからアプリにログインし、同じホストまたは独自ドメインのヘルプセンターに戻ることを確認します。
  2. 閲覧、検索、直接のURLで、許可された記事と拒否された記事をそれぞれ1つずつ確認します。ヘルプセンターのオーディエンス外の顧客にはアクセスのメッセージが表示され、拒否された記事では「見つかりません」が返されます。
  3. ヘルプセンターの「ログアウト」を使い、非公開のコンテンツが表示されなくなることを確認します。属性の異なる顧客でも繰り返します。

更新とログアウト

ウィジェットは現在のJWTを自動で送信します。Sonnyから要求されたら更新し、顧客がログアウトしたら sonny('reset') を呼び出します。ウィジェットのIDとヘルプセンターのCookieは別のものです。ウィジェットをリセットしても、ホストされたヘルプセンターのセッションからはログアウトされません。

ホストされたページからログアウトするには /help/YOUR_SLUG/auth/logout に、独自ドメインでは /auth/logout に移動します。両方のセッションを終了する必要がある場合は、この操作をアプリのログアウト処理に組み込んでください。無効または期限切れの認証情報では、訪問者としてアクセスします。ID認証がオフの場合、すべての読者は訪問者としてアクセスします。

顧客の属性が変わったら、新しいJWTを発行してウィジェットで再度識別し、ホストされたページではログインの交換処理を繰り返します。既存の読者のCookieは、期限が切れるまで以前の署名付き属性を保持します。オーディエンスのルールの変更は、次のリクエストから適用されます。

オーディエンスの作成、アクセスのプレビュー、制限のトラブルシューティング

メールアドレスによる識別は認証ではありません

ブラウザから送られるメールアドレスはサポートの文脈を充実させますが、訪問者を認証するものではありません。訪問者は自分のページでJavaScriptを確認・実行できるため、メールアドレス、名前、カスタム属性によってほかの顧客のSonnyの履歴にアクセスできるようになることはありません。認証済みIDには、上記のサーバー署名付きJWTが必要です。自社製品での操作の認可は、ログイン中のアプリケーション内で行ってください。

訪問者の認証が有効な場合、受信トレイの 確認済み バッジは、サーバー署名付きのJWTで会話が紐づけられたことを意味します。未確認 バッジは、サーバー署名付きのIDで会話が認証されていないことを意味します。表示される名前やメールアドレスは訪問者が提供したサポート用の情報で、本人であることの証明ではありません。

コード例

最もシンプルな呼び出しです。ユーザーのメールアドレスを渡すだけです。

JavaScript
// Identify a logged-in user
sonny('identify', {
  email: 'jane@example.com'
});

ユーザーの名前も渡すと、エージェントが受信トレイで確認できます。

JavaScript
// Identify with full name
sonny('identify', {
  email: 'jane@example.com',
  name: 'Jane Smith'
});

識別時に役立つサポート情報を追加したり、後から更新したり、ページを開いている間に変わる値を監視したりできます。

JavaScript
// Identify with support context
sonny('identify', {
  email: 'jane@example.com',
  name: 'Jane Smith',
  attributes: {
    plan: 'starter',
    seats: 5,
    trial: true
  }
});

// Update only the values that changed
sonny('setAttributes', {
  plan: 'growth',
  seats: 8,
  trial: null // Clears this property
});

// Keep a changing value in sync (checks every 10 seconds)
sonny('watchAttributes', () => ({
  monthly_usage: window.currentUsage
}), { interval: 10000 });

非同期スニペットを使った完全な例です。identify は init の前に呼び出せる点に注意してください。IDはキューに入れられ、ウィジェットが接続されるとすぐに送信されます。

HTML
<!-- Sonny widget snippet -->
<script>
  (function(w,d,s,o,f,js,fjs){
    w['Sonny']=o;w[o]=w[o]||function(){
    (w[o].q=w[o].q||[]).push(arguments)};
    js=d.createElement(s);fjs=d.getElementsByTagName(s)[0];
    js.id=o;js.src=f;js.async=1;fjs.parentNode.insertBefore(js,fjs);
  })(window,document,'script','sonny','https://www.usesonny.com/widget.js');

  // Identify before init — identity is queued and sent on connect
  sonny('identify', {
    email: 'jane@example.com',
    name: 'Jane Smith'
  });

  sonny('init', { siteId: 'YOUR_SITE_ID' });
</script>

ユーザーがログアウトしたら reset を呼び出して、IDをクリアし、新しいセッションを開始します。

JavaScript
// Reset on logout — clears identity and starts a fresh session
sonny('reset');

仕組み

  1. 01

    ウィジェットが読み込まれて接続する

    ウィジェットはSonnyに接続し、参加時に保存されているIDがあれば一緒に送信します。

  2. 02

    IDがサーバーに送信される

    Sonnyはワークスペース内でそのメールアドレスの連絡先を探し、まだなければ作成します。

  3. 03

    会話が紐づけられる

    現在のブラウザの訪問者セッションにある未紐づけの会話が、識別された連絡先に紐づけられます。識別によって、ブラウザやデバイス間の履歴が統合されることはありません。

  4. 04

    メールアドレスの入力が省略される

    訪問者はすでに識別されているため、ウィジェット内のメールアドレス入力は表示されず、ユーザーの邪魔をしません。

カスタム属性のルール

  • 1回の呼び出しで送れる属性は最大50個です。
  • 値には文字列、数値、真偽値、null を使えます。文字列は最大1000文字です。保存済みの値をクリアするには、null または空文字列を渡します。
  • キーは英字で始まり、英字、数字、アンダースコアのみを含む、64文字以内にする必要があります。
  • 次のキーは予約済みのため無視されます:email, name, id, phone, createdAt, updatedAt。

APIリファレンス

sonny('identify', { email, name?, attributes? })
現在の訪問者を識別します。メールアドレスと任意の名前を設定し、メールアドレスの入力を省略して、IDをサーバーに送信します。init の前後どちらでも呼び出せます。
  • emailstring訪問者のメールアドレス
  • namestring?訪問者の表示名
  • attributesobject?連絡先に付けるカスタム属性(キーと値のルールはウィジェット設定ガイドを参照)
sonny('identify', { userJwt, name?, attributes? })
サーバーで生成したJWTから、現在ログイン中の顧客を安全に識別します。トークンはメモリー内にのみ保持され、認証済みのリクエストまたはソケットのペイロードでのみ送信されます。
  • userJwtstring認証済みのバックエンドで新しく発行したHS256のJWT
  • namestring?訪問者の表示名
  • attributesobject?認証済みの連絡先に付けるカスタム属性
sonny('setAttributes', { ... })
識別済みの訪問者のカスタム属性を更新します。変更された値だけが送信されます。訪問者がまだ識別されていない場合、更新は identify の実行後に送信されます。
  • attributesobject設定するキーと値のペア。属性をクリアするには、値に null を渡します。
sonny('watchAttributes', getter, { interval? })
一定間隔でゲッター関数を呼び出し、変更された属性を自動で同期します。プランや使用量など、ページを開いている間に変わる値に便利です。
  • getterfunction現在の属性オブジェクトを返す関数
  • intervalnumber?確認する間隔(ミリ秒)。デフォルトは10000、最小は2000です。
sonny('reset')
現在のブラウザのウィジェットID、メールアドレス、名前、ローカルの会話履歴をクリアし、新しい訪問者セッションを開始します。Sonny内の連絡先や保存済みのプロパティは削除されません。ログアウト時に使います。

お困りですか?

ウィジェット設定ガイドをご覧いただくか、チームにお問い合わせください。

関連ドキュメント