Zoho Meeting APIでウェビナー登録を自動化するとは、広告のフォームやCRMに入った申込者を、人が画面で登録する代わりに、プログラムからZoho Meetingのウェビナーに登録することです。登録されると、Meetingから参加用のリンク付きの確認メールが本人に届きます。

当社(株式会社etika)は、2026年9月のセミナーで、Metaのリード獲得広告(Facebook・Instagramの広告上で申込フォームまで完結する広告)から入った申込を、15分ごとにZoho CRMとZoho Meetingの両方へ自動で登録する仕組みを作りました。きっかけは、手作業で処理する前提にしていた取り込みが約1週間止まり、その間の申込3件のうち新規の2件への案内が遅れたことです。経緯と構成はetika.lifeの「リード獲得広告のリードを取りこぼさない」に書きました。この記事では、Zoho Meeting APIの部分に絞って、手順と、当社がつまずいたzsoidの取り違えを説明します。内容は2026年9〜10月に当社の環境(データセンターはUS)で確かめた結果です。手順4のコード例は、当社の環境で2026年10月8日に動作を確認したコードです(自分のメールアドレスで1件登録しました)。その後、想定外の応答を失敗として扱う1行を足しました(この行を足した版は未実行です)。お使いの環境でも、まず自分のメールアドレスで動作を確認してからご利用ください。

使うAPIは2つ

ウェビナーへの登録に必要なAPIは、次の2つです。

用途 エンドポイント スコープ
ウェビナーの一覧(meetingKeyとsysIdを調べる) GET /api/v2/{zsoid}/webinar.json ZohoMeeting.webinar.READ
参加者の一括登録 POST /api/v2/{zsoid}/register/{meetingKey}.json ZohoMeeting.webinar.CREATE

公式ドキュメントは「List of Webinar API」と「Bulk Registration API」です。ベースURLは https://meeting.zoho.com で、当社はこの形で動かしています。これは米国のデータセンターの例です。ほかのデータセンターをお使いの場合は、公式の対応表でベースURLを確かめてください。

手順1:Refresh Tokenを用意する

Zoho CRMのAPIでよく使うClient Credentials(ブラウザでの承認なしにトークンを発行する方式)は、Meetingでは使えませんでした。soid=ZohoMeeting.<組織ID> でトークンを発行すると、no_org が返ります。2026年10月8日にも同じ結果でした。Zoho Campaignsでも同じで、こちらの経緯は「Zoho Campaigns APIで下書きキャンペーンを作る」に書きました。

そのため、API ConsoleのSelf Clientで認可コードを発行し、Refresh Tokenに交換して使います。Self Clientの作り方は「Zoho APIをスクリプトから叩く最短ルート」を参照してください。当社が付けたスコープは ZohoMeeting.webinar.READ,ZohoMeeting.webinar.CREATE の2つだけです。

# 認可コードをRefresh Tokenに交換する(DCが日本なら accounts.zoho.jp)
curl -s -X POST "https://accounts.zoho.com/oauth/v2/token" \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "code=YOUR_GENERATED_CODE"

手順2:zsoidを正しく入れる

URLに入れる組織IDが正しくても、CRMの組織IDでも、一覧のAPIは同じ結果を返すが、登録のAPIは正しいIDのときだけ成功し、それ以外は401になることを示した図。

zsoidとは、Zoho Meetingの組織を表すIDで、APIのURLの中に入れます。ここが当社の一番の落とし穴でした。

当社はZoho CRMのAPIを先に使っていたので、手元の設定にCRMの組織IDがありました。これをそのままzsoidとして使ったところ、ウェビナーの一覧は問題なく取れました。ところが登録のAPIを呼ぶと、401 UNAUTHORIZED_USER が返りました。当社の環境では、管理者のアカウントで発行したトークンでも401になりました。

原因を調べるため、zsoidを変えて一覧のAPIを呼び比べました。結果は次のとおりです。

URLに入れた値 一覧API(webinar.json)※1 登録API(register)※2
Meetingの正しい組織ID 200・ウェビナー一覧が返る 成功
Zoho CRMの組織ID 200・同じ一覧が返る 401 UNAUTHORIZED_USER
でたらめな数字 200・同じ一覧が返る 試していない

※1 一覧APIの3通りは、2026年10月8日に改めて実測しました。 ※2 CRMの組織IDでの401は、2026年9月の運用時の記録です(Meetingの正しい組織IDに直して成功しました)。Meetingの正しい組織IDでの成功は、2026年10月8日にも、自分のメールアドレス1件の登録で確かめました(登録者数が1件増えました)。登録APIは呼ぶと実際の登録と確認メールの送信が起きるため、CRMの組織IDでの401は10月8日には再実行していません。でたらめな値での登録も試していません。

一覧のAPIは、URLのzsoidを検証していないように見えます。トークンの持ち主の組織のウェビナーをそのまま返すため、値が間違っていても気づけません。誤りが表に出るのは、登録のAPIを呼んだときだけです。

zsoidが違うとなぜ401になるのか?

当社の理解では、Zoho CRMとZoho Meetingは、同じ会社のアカウントでも組織IDを別々に持っています。登録のAPIは、URLのzsoidとトークンの持ち主の組織を照らし合わせるため、CRMの組織IDを入れると「この組織のユーザーではない」と判断されて401になる、と考えています。エラー名が権限不足に見えるため、スコープやユーザーの権限を疑って時間を使いがちです。

正しいzsoidの取り方について、Zoho Webinar(別製品)の公式ドキュメント「Organization ID」は、組織情報を読むスコープ(manageOrg.READ)で user.json を呼び、応答の zsoid を使う方法を案内しています。当社はMeeting用のトークンにこのスコープを付けていなかったため、user.json は INVALID_OAUTHSCOPE になりました。これから設定する場合は、認可コードの発行時に組織情報の読み取りスコープも付けておくと、zsoidをAPIで確かめられる可能性があります(当社のMeeting環境では未検証)。

確認の手順として当社が勧めるのは、本番の申込者で試す前に、自分のメールアドレスで1件だけ登録APIを呼んでみることです。401が返ればzsoidを疑います。

手順3:meetingKeyとinstanceIdを調べる

登録のAPIには、ウェビナーを表す meetingKey と、開催回を表す instanceId が要ります。どちらも一覧のAPIで取れます。

curl -s "https://meeting.zoho.com/api/v2/YOUR_MEETING_ZSOID/webinar.json?listtype=upcoming&index=1&count=20" \
  -H "Authorization: Zoho-oauthtoken YOUR_ACCESS_TOKEN"

応答の session 配列の各要素に、topic(タイトル)・startTime・meetingKey・sysId・registrationLink などが入っています。当社は sysId の値を instanceId として渡しています。なお、1件のウェビナーの詳細を返すAPI(/webinar/{meetingKey}.json)は、当社の環境では403が返り、原因を特定できていません。必要な値は一覧から取れるので、一覧で代用しています。

手順4:一括登録APIで参加者を登録する

次のコードは、当社の実装(Node.js)から、登録の部分を取り出したものです。

// meeting-register.mjs(Node.js 18以上)
export async function registerWebinarAttendee({ accessToken, zsoid, meetingKey, instanceId, email, familyName, givenName }) {
  const url = `https://meeting.zoho.com/api/v2/${zsoid}/register/${meetingKey}.json?sendMail=true&instanceId=${instanceId}`;
  const post = async (lastName) => {
    const res = await fetch(url, {
      method: "POST",
      headers: { Authorization: `Zoho-oauthtoken ${accessToken}`, "content-type": "application/json" },
      // Meetingの宛名は「firstName lastName 様」の順。日本語の宛名にするため firstName=姓・lastName=名
      body: JSON.stringify({ registrant: [{ email, firstName: familyName, lastName }] }),
    });
    const text = await res.text();
    let json = null;
    try { json = JSON.parse(text); } catch {}
    return { ok: res.ok, status: res.status, json, text };
  };

  // 名が無い場合、空文字が弾かれたら全角スペースで再試行する
  let r = await post(givenName || "");
  if (!givenName && (!r.ok || r.json?.failedCount > 0)) r = await post(" ");

  if (!r.ok) return { status: "failed", detail: `HTTP ${r.status}` }; // 401ならzsoidを疑う
  // 応答がJSONでない・successCountが数値でないときは、登録済みと取り違えないよう失敗にする
  if (typeof r.json?.successCount !== "number") return { status: "failed", detail: "unexpected response" };
  if (r.json.failedCount > 0) return { status: "failed", detail: `failedCount=${r.json.failedCount}` };
  // 応答の joinLink は本人専用の参加リンク。ログに出さない
  return { status: r.json.successCount > 0 ? "registered" : "already" };
}

実装で気をつけた点は4つです。

  1. 宛名の順番:当社が受け取ったMeetingの確認メールでは、「firstName lastName 様」の順で宛名が出ました。日本語で「姓 名 様」にするため、firstNameに姓を入れています。手作業の代行登録で姓名欄に別の人の名前が混ざり、確認メールの宛名がおかしくなったことがあったため、APIでは姓と名を必ず明示しています。ただし、Zoho CRMとの連携を有効にしている場合は、この入れ方だとCRM側の姓と名が逆になります(後述の「CRM連携と組み合わせるときの注意」)
  2. 名が無いときの扱い:広告のフォームで氏名欄に姓しか入っていない申込があります。lastNameを空にして失敗したときは、全角スペースで送り直します
  3. 再登録の扱い:同じメールアドレスで登録し直すと、当社の環境では、HTTP 200・successCount が0で、登録者数は増えませんでした。この性質を使い、successCount が0なら「登録済み」と判定しています。15分ごとに同じ申込を処理しても、二重登録にはなりません。ただし、HTTP 200でもJSONでない応答や successCount が無い応答を「登録済み」と扱うと、登録されていない申込を処理済みとして取りこぼします。successCount が数値でないときは失敗として人に知らせます
  4. 参加リンクを記録しない:応答の joinLink は本人専用のリンクです。ログやCRMには残しません

登録者一覧はAPIで取れるか?

当社の環境では取れませんでした。登録者の一覧らしいURL(/registration/{meetingKey})を試すと Invalid zsoid が返りました。これはZoho Webinar(別製品)のAPIの形で、Meetingのウェビナーには効かないようです。

そのため、登録済みかどうかの判定は、登録APIの応答(新規か既存か)と、こちら側で持つ処理済みの記録で行っています。人が確かめるときはMeetingの画面の登録者一覧を見ます。

CRM連携と組み合わせるときの注意

Zoho MeetingとZoho CRMの連携を有効にしていると、Meetingに登録された人が、CRMに見込み客として自動で作られます(CRMに同じメールアドレスの記録が無い場合。既にある場合は後述)。当社ではこれが、自前の同期処理とぶつかりました。

当社の同期処理は「CRMに同じメールアドレスの見込み客が既にいれば、新規作成しない」作りでした。ところが、Meetingの連携が登録の直後に「氏名とメールアドレスだけ」の見込み客を先に作るため、同期処理はそれを既存と判断して作成を飛ばしていました。結果として、会社名・役職・広告の流入元(UTM)などがCRMに入っていない見込み客が4件できていました。

2026年9月18日に、既存の見込み客の空いている項目だけを埋める処理(既に値がある項目は上書きしない)を足して直しました。Meetingの登録とCRMの作成を両方自動にする場合は、どちらが先に見込み客を作るかを決めておくことをお勧めします。

連携には、もう1つ気をつける動きがありました。2026年10月8日に、CRMに連絡先(Contacts)として既に登録されている自分のメールアドレスで、テスト用の氏名を入れて1件登録しました。このとき、見込み客は新しく作られず、既存の連絡先の姓と名が、登録時の値で上書きされました。当社の環境では、姓と名に加えて、国(Mailing_Country)の値が空になりました(連携の仕様によるものかは分かっていません)。同じメールアドレスの見込み客(Leads)は変わりませんでした。CRMの変更履歴を見ると、当社が登録APIを試した2026年9月17日にも、同じ連絡先の氏名が同じ形で書き換わっていました。

上書きされた値を見ると、Meetingの firstName がCRMの名(First_Name)に、lastName が姓(Last_Name)に入っていました。確認メールの宛名を「姓 名 様」にするために firstName に姓を入れると、CRMでは姓と名が逆に記録されます。既存の顧客がウェビナーに申し込むと、CRMの氏名がフォームの入力で書き換わることも前提にしてください。テストの登録には、CRMに記録の無いテスト専用のメールアドレスを使い、登録後にCRMの氏名も確かめることをお勧めします。氏名以外の項目にも影響が出ないか、テストの前後でCRMの変更履歴を確認してください。CRMの項目をAPIで扱う基本は「Zoho CRM と他ツールのAPI連携で業務効率を最大化する」で紹介しています。

もう1つ、登録APIのボディで受け付けるのはメールアドレス・名・姓だけで、公式ドキュメントにも流入経路を指定する項目は見当たりませんでした。広告経由の登録かどうかをMeeting側で区別したい場合は、CRM側の項目(UTMなど)で持つ設計にしてください。

実装前のチェックリスト

  • Meeting用のRefresh Tokenを、webinar.READ・webinar.CREATEのスコープで発行したか
  • URLのzsoidが、CRMの組織IDではなくMeetingの組織IDになっているか
  • 自分のメールアドレスで1件登録して、401が出ないことを確かめたか
  • sysId を instanceId として渡しているか
  • firstName・lastNameの順番と、名が空のときの扱いを決めたか
  • 参加リンク(joinLink)をログやCRMに残さない作りになっているか
  • CRM連携が先に作る見込み客と、自前の同期処理がぶつからないか
  • 既存の連絡先の氏名がCRM連携で上書きされることと、firstName・lastNameとCRMの姓・名の対応を確かめたか。氏名以外の項目(国など)が消えないか、変更履歴で確かめたか
  • successCount が数値でない応答を、「登録済み」ではなく失敗として扱っているか
  • 登録に失敗したとき、人に知らせる仕組みがあるか(当社はMacの通知で知らせている)

広告やフォームからの申込を、CRM・ウェビナー・メールまでAPIで一続きにつなぐ実装のご相談は、「Zoho CRMカスタマイズ・設定代行」のページからどうぞ。

よくある質問

Zoho Meeting APIのzsoidとは何ですか?

Zoho Meetingの組織を表すIDで、APIのURLの中に入れて使います。Zoho CRMの組織IDとは別の値です。当社では、CRMの組織IDを入れたままにしていたため、登録のAPIが401を返しました。

Zoho Meeting APIで401 UNAUTHORIZED_USERが返るのはなぜですか?

当社の環境では、URLのzsoidが誤っていたことが原因でした。当社の環境では、管理者のアカウントでも同じエラーになりました。一覧のAPIはzsoidを検証しないので、一覧が取れたことは正しさの根拠になりません。

ウェビナーの登録者一覧はAPIで取れますか?

当社の環境では取れませんでした。Zoho Webinar向けの登録者一覧のURLを試すと Invalid zsoid が返り、Meetingのウェビナーには使えないようでした。登録済みかどうかは、登録APIの応答(新規か既存か)で判定しています。

API登録の確認メールで、名前が「名 姓 様」の順になりませんか?

当社が受け取った確認メールでは、宛名が「firstName lastName 様」の順で表示されました。日本語の宛名を「姓 名 様」にしたい場合は、firstNameに姓、lastNameに名を入れています。

Zoho Meeting APIはClient Credentialsで使えますか?

当社の環境では使えませんでした。soidにZohoMeetingと組織IDを指定してトークンを発行すると no_org が返ります。Self Clientで認可コードを発行し、Refresh Tokenに交換して使っています。