Zendesk チケットコメント API の public デフォルト動作を検証

Zendesk チケットコメント API の public デフォルト動作を検証

Zendesk の Ticket Comments API でチケットにコメントを追加する際、public パラメータを未指定にした場合の動作を詳しく検証しました。実際に試してみると思いがけない動作をすることが分かりました。意図しない情報漏洩を防ぐための実装上の注意点もここで解説します。

はじめに

Zendesk の Ticket Comments API を使用してチケットにコメントを追加する際、一般的には「チケット作成時の public 設定が引き継がれる」と認識されがちですが、実際の動作は異なる可能性があります。本記事では、public パラメータを指定しない場合の実際の動作を検証し、開発者が陥りやすい誤解を解消します。

Zendesk とは

Zendesk は、カスタマーサポート業務を効率化するクラウド型のヘルプデスクソフトウェアです。チケット管理、ナレッジベース、チャットサポートなどの機能を提供し、企業の顧客対応を一元管理できます。Zendesk では、顧客からの問い合わせや要望を「チケット」として管理し、各チケットに対してエージェント(=サポート担当者)や顧客がコメントを追加することでやり取りを行います。

Zendesk API を使用してチケットにコメントを追加するには、Tickets API の PUT /api/v2/tickets/{id}.json エンドポイントを使用します。リクエストボディに comment オブジェクトを含めることで、新しいコメントを追加できます。

// Node.js での実装例
const response = await axios.put(`https://${domain}/api/v2/tickets/${ticketId}.json`, {
    ticket: {
        comment: {
            body: "コメント内容",
            public: true, // 顧客も閲覧可能
            author_id: userId
        }
    }
}, {
    headers: {
        'Authorization': `Basic ${authToken}`,
        'Content-Type': 'application/json'
    }
});

この API 呼び出し時のパラメータのうち public について挙動を確認することが本記事の目的となります。

対象読者

  • Zendesk API を使用してチケット管理システムを開発している開発者
  • Zendesk の自動化やインテグレーションを構築している技術者
  • Zendesk API の動作仕様について詳しく理解したい方

参考資料

検証してみた

まず、公式ドキュメントで public パラメータがどのように説明されているかを確認します。 Ticket Comments API のドキュメント によると、public パラメータについて以下の記載があります。

Name Type Read-only Mandatory Description
public boolean false false true if a public comment; false if an internal note. The initial value set on ticket creation persists for any additional comment unless you change it

この記事からは、「チケット作成時の初期値が、以降のコメント追加時も継続される」と読み取れますが、この「初期値が継続される」という動作が具体的にどのような挙動なのかが不明確です。したがって、実際に API を呼び出し検証してみることにしました。

検証環境

  • Zendesk プラン: Enterprise Plus
  • API バージョン: v2
  • 認証方式: Basic 認証 (API Token)
  • 実行環境: Node.js
  • 検証日時: 2025年7月

検証方法

以下のパターンでコメント追加を行い、実際の動作を確認します。

  1. パターン1: 新規チケット作成時に public: true → その後 public 未指定でコメント追加
  2. パターン2: 新規チケット作成時に public: false → その後 public 未指定でコメント追加
  3. パターン3: 新規チケット作成時に public 未指定 → その後 public 未指定でコメント追加
  4. パターン4: 既存チケットに対して異なる public 値でコメント追加後、再度 public 未指定でコメント追加
  5. パターン5: public: false で作成したチケットに対して同様の検証
  6. パターン6: 初回コメント「パブリック返信」で Web UI で作成したチケットに対しコメント追加
  7. パターン7: 初回コメント「社内メモ」で Web UI で作成したチケットに対しコメント追加

検証用のコードは以下のとおりです。

検証用コード
const axios = require('axios');

const ZENDESK_DOMAIN = 'your-domain.zendesk.com';
const API_TOKEN = 'your-api-token';
const EMAIL = 'your-email@example.com';

const authHeader = Buffer.from(`${EMAIL}/token:${API_TOKEN}`).toString('base64');

// チケット作成関数
async function createTicket(publicValue = undefined) {
    const commentData = {
        body: "初回コメント",
        author_id: 123456789 // 実際のユーザー ID
    };

    if (publicValue !== undefined) {
        commentData.public = publicValue;
    }

    const response = await axios.post(`https://${ZENDESK_DOMAIN}/api/v2/tickets.json`, {
        ticket: {
            subject: "検証用チケット",
            comment: commentData,
            requester_id: 123456789 // 実際のユーザー ID
        }
    }, {
        headers: {
            'Authorization': `Basic ${authHeader}`,
            'Content-Type': 'application/json'
        }
    });

    return response.data.ticket.id;
}

// コメント追加関数
async function addComment(ticketId, publicValue = undefined) {
    const commentData = {
        body: "追加コメント",
        author_id: 123456789
    };

    if (publicValue !== undefined) {
        commentData.public = publicValue;
    }

    const response = await axios.put(`https://${ZENDESK_DOMAIN}/api/v2/tickets/${ticketId}.json`, {
        ticket: {
            comment: commentData
        }
    }, {
        headers: {
            'Authorization': `Basic ${authHeader}`,
            'Content-Type': 'application/json'
        }
    });

    return response.data;
}

// コメント一覧取得関数
async function getComments(ticketId) {
    const response = await axios.get(`https://${ZENDESK_DOMAIN}/api/v2/tickets/${ticketId}/comments.json`, {
        headers: {
            'Authorization': `Basic ${authHeader}`,
            'Content-Type': 'application/json'
        }
    });

    return response.data.comments;
}

検証結果

パターン1: チケット作成時 public: true → 追加コメント public 未指定

// 実行
const ticketId1 = await createTicket(true);
await addComment(ticketId1); // public 未指定
const comments1 = await getComments(ticketId1);

console.log('パターン1の結果:');
comments1.forEach((comment, index) => {
    console.log(`コメント${index + 1}: public = ${comment.public}`);
});
パターン1の結果:
コメント1: public = true
コメント2: public = true

パターン1

パターン2: チケット作成時 public: false → 追加コメント public 未指定

// 実行
const ticketId2 = await createTicket(false);
await addComment(ticketId2); // public 未指定
const comments2 = await getComments(ticketId2);

console.log('パターン2の結果:');
comments2.forEach((comment, index) => {
    console.log(`コメント${index + 1}: public = ${comment.public}`);
});
パターン2の結果:
コメント1: public = false
コメント2: public = false

パターン2

パターン3: チケット作成時 public 未指定 → 追加コメント public 未指定

// 実行
const ticketId3 = await createTicket(); // public 未指定
await addComment(ticketId3); // public 未指定
const comments3 = await getComments(ticketId3);

console.log('パターン3の結果:');
comments3.forEach((comment, index) => {
    console.log(`コメント${index + 1}: public = ${comment.public}`);
});
パターン3の結果:
コメント1: public = true
コメント2: public = true

パターン3

パターン4: 既存チケットに対して異なる public 値でコメント追加後、再度 public 未指定でコメント追加

// 実行
const ticketId4 = await createTicket(true); // public: true で作成
await addComment(ticketId4, false); // public: false で追加
await addComment(ticketId4); // public 未指定で追加
const comments4 = await getComments(ticketId4);

console.log('パターン4の結果:');
comments4.forEach((comment, index) => {
    console.log(`コメント${index + 1}: public = ${comment.public}`);
});
パターン4の結果:
コメント1: public = true
コメント2: public = false
コメント3: public = true

パターン4

パターン5: public: false で作成したチケットに対して同様の検証

// 実行
const ticketId5 = await createTicket(false); // public: false で作成
await addComment(ticketId5, true); // public: true で追加
await addComment(ticketId5); // public 未指定で追加
const comments5 = await getComments(ticketId5);

console.log('パターン5の結果:');
comments5.forEach((comment, index) => {
    console.log(`コメント${index + 1}: public = ${comment.public}`);
});
パターン5の結果:
コメント1: public = false
コメント2: public = true
コメント3: public = true

パターン5

パターン6: 初回コメント「パブリック返信」で Web UI で作成したチケットに対しコメント追加

チケット作成パブリック返信

// Web UI で作成されたチケット ID を使用
const webUITicketId = 12345; // 実際のチケット ID

// まず既存のコメントを確認
const existingComments = await getComments(webUITicketId);
console.log('Web UI で作成されたチケットの初期コメント:');
existingComments.forEach((comment, index) => {
    console.log(`コメント${index + 1}: public = ${comment.public}`);
});

// API で public 未指定のコメントを追加
await addComment(webUITicketId); // public 未指定
const updatedComments = await getComments(webUITicketId);

console.log('パターン6の結果:');
updatedComments.forEach((comment, index) => {
    console.log(`コメント${index + 1}: public = ${comment.public}`);
});
Web UI で作成されたチケットの初期コメント:
コメント1: public = true
パターン6の結果:
コメント1: public = true
コメント2: public = true

パターン6

パターン7: 初回コメント「社内メモ」で Web UI で作成したチケットに対しコメント追加

チケット作成社内メモ

// Web UI で作成されたチケット ID を使用
const internalNoteTicketId = 12346; // 実際のチケット ID

// 既存のコメントを確認
const internalComments = await getComments(internalNoteTicketId);
console.log('Web UI で作成されたチケットの初期コメント:');
internalComments.forEach((comment, index) => {
    console.log(`コメント${index + 1}: public = ${comment.public}`);
});

// API で public 未指定のコメントを追加
await addComment(internalNoteTicketId); // public 未指定
const updatedInternalComments = await getComments(internalNoteTicketId);

console.log('パターン7の結果:');
updatedInternalComments.forEach((comment, index) => {
    console.log(`コメント${index + 1}: public = ${comment.public}`);
});
Web UI で作成されたチケットの初期コメント:
コメント1: public = false
パターン7の結果:
コメント1: public = false
コメント2: public = false

パターン7

検証結果の分析

これらの検証結果から、以下のことが明らかになりました。

  1. デフォルト値は private ではなく public
    チケット作成時に public を未指定にした場合、デフォルトは true (public) となります。

  2. public: true が優先される特殊な動作
    パターン4とパターン5の結果、以下が明らかになりました。

    • パターン4: true で作成 → false で追加 → 未指定で追加 → true になる
    • パターン5: false で作成 → true で追加 → 未指定で追加 → true になる

    両方のケースで未指定時に true になっていますが、その理由は以下の可能性が考えられます。

    • 仮説1: public: true が一度設定されると優先される
    • 仮説2: より複雑な内部ルールが存在する

    現在の検証結果では、おそらく仮説1が有力と思われますが、確定的な結論は出せません。

  3. Web UI と API で一貫した動作
    パターン6とパターン7の結果から、チケットの作成方法 (API または Web UI) に関係なく、同様の動作が確認できました。

実装上の注意点

この検証結果を踏まえ、実装時には以下の点に注意が必要です。

  1. 予期しない動作が発生する可能性があるため、明示的な public 指定を強く推奨
// 推奨: 常に明示的に指定
const commentData = {
    body: "コメント内容",
    public: false, // 明示的に指定
    author_id: userId
};
  1. 特に社内メモを追加したい場合は要注意
// 危険: public 未指定
const commentData = {
    body: "内部メモ", // 顧客に見せたくない内容
    // public を指定していない → 過去に public: true があれば顧客に見える!
    author_id: userId
};

// 安全: 明示的に指定
const commentData = {
    body: "内部メモ",
    public: false, // 必ず明示的に指定
    author_id: userId
};
  1. 既存チケットへのコメント追加時は特に注意
    既存のチケットにコメントを追加する場合、そのチケットで過去に public: true が設定されたことがあるかを把握することは困難です。そのため、必ず明示的に public を指定することを強く推奨します。

まとめ

Zendesk の Ticket Comments API における public パラメータの動作について検証を行った結果、以下が明らかになりました。

  1. チケット作成時に public を未指定にした場合のデフォルトは true (public)
  2. public 未指定時の動作は複雑で予測困難。特定の条件下で意図しない public: true になる可能性がある。予測困難な動作により、意図せず顧客に内部情報を公開するリスクが存在する

したがって開発においては、public パラメータの値を明示的に指定する対応が推奨されます。複雑で予測困難な動作のため、デフォルト動作に依存した実装は避けるべきでしょう。適切な理解に基づいて API を使用することで、意図しない情報漏洩を防ぐことができます。

この記事をシェアする

facebookのロゴhatenaのロゴtwitterのロゴ

© Classmethod, Inc. All rights reserved.