Zealy ドキュメント

自動翻訳 · 英語で読む

Webhook

Webhook接続でZealyの機能を拡張しましょう。

作成者 louis, Fredrika, benjamin

Webhook
このページの内容

ZealyのWebhookは、9種類のコミュニティイベント(メンバーの参加、クエストのclaimの成功または失敗、スプリントの開始、ユーザーのBANなど)が発生した際に、指定したURLへHTTP POSTを送信します。各ペイロードにはイベントID、type、data、ミリ秒単位のタイムスタンプ、そしてリクエストが本物であることを証明するsecretが含まれます。

基本

Zealyは Webhook を提供しており、データが作成または更新されるたびにHTTPプッシュ通知を受け取ることができます。これによりZealy上にインテグレーションを構築できます。CIビルドのトリガー、クエストデータの計算、特定の条件でのメッセージ送信など、活用方法は自由です。

Webhookを設定するには、Settings > Integrations > Zapier & Webhooks にアクセスしてください。

Webhookの仕組み

Webhookプッシュは、指定したURLに送信される単なる HTTP POST リクエストです。プッシュはデータの更新時にLinearによって自動的にトリガーされます。ペイロードに含まれるデータの例については、 Webhookペイロード.

Webhookの受信側はシンプルなHTTPエンドポイントです。以下の条件を満たす必要があります:

  • 公開アクセス可能なHTTPSの非localhost URLであること
  • ZealyのWebhookプッシュ(HTTP POSTリクエスト)に対して HTTP 200 (「OK」)レスポンスを返すこと

配信が失敗した場合(サーバーが利用不可、レスポンスが5秒以上かかった、レスポンスボディが5000バイトを超えた、または200以外のHTTPステータスコードを返した場合など)、プッシュは15回再試行されます。ここでは指数バックオフ遅延が用いられ、約10分後、次に30分後、というように再試行されます。Webhook URLが応答しない状態が続くと、ZealyによってWebhookが無効化される場合があり、手動で再度有効化する必要があります。

Webhookが POST 本当にZealyによって作成されたものであることを確認するため、リクエストのボディの secret フィールドに付与されるランダムなsecretを生成します。

Webhookに関する追加情報として、以下の優れたリソースがあります:

何に使えるか

  • Zapierとの連携
  • カスタム報酬の自動化
  • レビュー待ちのクエストをTelegramで通知

その他多数の活用が可能です!

Zealy Webhookの始め方

  1. 監視したいイベントを特定します。
  2. イベントデータのPOSTリクエストを受け取るWebhookエンドポイント関数を開発します。
  3. Settings > Integrations > Zapier & Webhooks のWebhooksダッシュボードから、Zealyにエンドポイントを登録します。
  4. Webhookエンドポイントを保護します。

シンプルなWebhook受信側の作成

Netlify Functionsのようなものを利用すると、シンプルなHTTP(S)エンドポイントを簡単にデプロイできます: https://www.netlify.com/blog/2018/09/13/how-to-run-express.js-apps-with-netlify-functions/.

// server.js
//
// Use this sample code to handle webhook events in your integration.
//
// 1) Paste this code into a new file (server.js)
//
// 2) Install dependencies
//   npm install express
//
// 3) Run the server on http://localhost:4242
//   node server.js
 
// The library needs to be configured with your account's secret key.
// Ensure the key is kept out of any version control system you might be using.
const express = require('express');
const app = express();
 
// This is your Zealy webhook secret for testing your endpoint locally.
const endpointSecret = '5888a6800dbac634e52d8504f097e064';
 
app.post('/webhook', express.raw({ type: 'application/json' }), (request, response) => {
  const payload = req.body;
  // Does createdAt make more sense than time?
  const { id, type, data, time, secret } = payload;
 
  if (secret === endpointSecret) {
    // Do something neat with the data received!
  }
 
  // Return a 200 response to acknowledge receipt of the event
  response.send();
});
 
app.listen(4242, () => console.log('Running on port 4242'));

設定での構成

Settings > Integrations > Webhooks でWebhookと監視するイベントを設定します。

Webhookペイロード

WebhookのHTTPペイロードには、HTTPヘッダーとリクエストボディの両方に情報が含まれます。

ペイロードは以下のHTTPヘッダーとともに送信されます:

User-Agent: Zealy-Webhook
Content-Type: application/json; charset=utf-8
{
  id: string // unique ID for this event (UUID v4)
  type: "JOINED_COMMUNITY" | "LEFT_COMMUNITY" | "QUEST_SUCCEEDED" | "QUEST_CLAIMED" | "QUEST_FAILED" | "QUEST_CLAIM_STATUS_UPDATED" | "SPRINT_STARTED" | "SPRINT_ENDED" | "USER_BANNED", // event type
  data: EventData, // event data, see below
  time: number, // timestamp, in milliseconds
  secret: string, // secret generated when you created your webhook, used for checking origin
}

イベントごとのEventDataフィールド:

// JOINED_COMMUNITY
type JoinedCommunityEventData = {
  community: WebhookCommunityPayload;
  user: WebhookUserPayload;
};
 
// LEFT_COMMUNITY
type LeftCommunityEventData = {
  community: WebhookCommunityPayload;
  user: WebhookUserPayload;
};
 
// QUEST_SUCCEEDED
type QuestSucceededEventData = {
  community: WebhookCommunityPayload;
  user: WebhookUserPayload;
  quest: WebhookQuestPayload;
  taskInputs?: Array<{
    taskId: string, // task ID (uuid v4)
    taskType: 'number' | 'date' | 'api' | 'text' | 'discord' | 'url' | 'telegram' | 'quiz' | 'invites' | 'visitLink' | 'file' | 'poll' | 'opinion' | 'twitterFollow' | 'twitterSpace' | 'tweetReact' | 'tweet',
    input: WebhookQuestTaskInputPayload,
  }>;
  status: 'success';
};
 
// QUEST_CLAIMED
type QuestClaimedEventData = {
  community: WebhookCommunityPayload;
  user: WebhookUserPayload;
  quest: WebhookQuestPayload;
  taskInputs?: Array<{
    taskId: string, // task ID (uuid v4)
    taskType: 'number' | 'date' | 'api' | 'text' | 'discord' | 'url' | 'telegram' | 'quiz' | 'invites' | 'visitLink' | 'file' | 'poll' | 'opinion' | 'twitterFollow' | 'twitterSpace' | 'tweetReact' | 'tweet',
    input: WebhookQuestTaskInputPayload,
  }>;
};
 
// QUEST_FAILED
type QuestFailedEventData = {
  community: WebhookCommunityPayload;
  user: WebhookUserPayload;
  quest: WebhookQuestPayload;
  taskInputs?: Array<{
    taskId: string, // task ID (uuid v4)
    taskType: 'number' | 'date' | 'api' | 'text' | 'discord' | 'url' | 'telegram' | 'quiz' | 'invites' | 'visitLink' | 'file' | 'poll' | 'opinion' | 'twitterFollow' | 'twitterSpace' | 'tweetReact' | 'tweet',
    input: WebhookQuestTaskInputPayload,
  }>;
};
 
// QUEST_CLAIM_STATUS_UPDATED
type QuestClaimStatusUpdatedEventData = {
  community: WebhookCommunityPayload;
  user: WebhookUserPayload;
  quest: WebhookQuestPayload;
  taskInputs?: Array<{
    taskId: string, // task ID (uuid v4)
    taskType: 'number' | 'date' | 'api' | 'text' | 'discord' | 'url' | 'telegram' | 'quiz' | 'invites' | 'visitLink' | 'file' | 'poll' | 'opinion' | 'twitterFollow' | 'twitterSpace' | 'tweetReact' | 'tweet',
    input: WebhookQuestTaskInputPayload,
  }>;
  status: 'success' | 'pending' | 'failed';
};
 
// SPRINT_STARTED
type SprintStartedEventData = {
  community: WebhookCommunityPayload;
  sprint: WebhookSprintPayload;
};
 
// SPRINT_ENDED
type SprintEndedEventData = {
  community: WebhookCommunityPayload;
  sprint: WebhookSprintPayload;
};
 
// USER_BANNED
type UserBannedEventData = {
  community: WebhookCommunityPayload;
  user: WebhookUserPayload;
};

共通エンティティ:

type WebhookCommunityPayload = {
  id: string; // community ID (uuid V4)
  name: string; // community name
  subdomain: string; // community subdomain
  blockchain: string;
  discord: string | null; // discord URI of the community, e.g., https://discord.gg/zealy
  twitter: string | null; // twitter handle of the community, e.g., zealy_io
  website: string | null; // website URI of the community
};
type WebhookUserPayload = {
  id: string; // user ID (uuid V4)
  name: string | null; // user display name
  twitter: {
    id: string; // user twitter ID
    username: string | null; // user twitter handle
  } | null;
  discord: {
    id: string; // user discord ID
    handle: string | null; // user discord handle
  } | null;
  email: string | null; // user email
  addresses: Dict<string>; // key/value dictionary for wallets
  verifiedAddresses: Dict<string>; // key/value dictionary for all users verified wallets
};
type WebhookQuestPayload = {
  id: string; // quest ID (uuid V4)
  name: string; // quest display name
  categoryId: string; // category ID (uuid V4)
  categoryName: string | null; // category display name
  xp: number | null; // XP reward
  published: boolean | null; // true if quest is published, false otherwise
  autoValidate: boolean | null;
  tasks: TaskPayload[];
  lastReviewerId: string; // user ID (uuid V4)
};
type WebhookQuestTaskInputPayload =
 | { } // for task type 'visitLink', 'invites', 'discord', 'telegram', 'twitterFollow', 'twitterSpace'
 | { value: string } // for task type 'text', 'date', 'url', 'opinion'
 | { value: number } // for task type 'number'
 | { values: string[] } // for task type 'poll', 'quiz'
 | { fileUrls: string[] } // for task type 'fileUrls'
 | { tweetId?: string } // for task type 'tweetReact', 'tweet'
 | { requestId: string, statusCode: number } // for task type 'api'
type WebhookSprintPayload = {
  id: string; // sprint ID (uuid V4)
  startingAt: ISOString; // sprint starting date - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
  endingAt: ISOString; // sprint ending date
};

Webhookの保護

Webhookを作成すると、リクエストがZealyのWebhookから来たものであることを検証するためのランダムなsecretが生成されます。Webhook検査パネルの右上にあるsecretをコピーし、POSTリクエストのボディに設定されたsecretが同じであることを確認してください。

secretが漏洩した場合は、同じページから再生成できます。

Copy secretボタンと、Edit webhook、Disable、Trigger test event、Regenerate secret、Delete webhookを一覧表示するメニューが開いたWebhook検査パネル

Webhookのテスト

Webhookの検査パネルから、テストイベントを手動でトリガーできます。右上のボタンをクリックし、「Send test event」を選択してください。監視対象のイベントタイプから1つ選択でき、モックデータでイベントがトリガーされ、ステータスが表示されます。

デバッグ

WebhooksページでWebhookをクリックすると、そのWebhookによってトリガーされた過去のすべてのイベントのログを確認できます。ステータスでフィルタリングすることで、問題が発生した場合のデバッグにログを利用できます。各イベントについて、受信したエラーメッセージと送信されたリクエストを確認できます。

このページから任意のイベントを再試行することもできます。

Join communityイベントを一覧表示するWebhookイベントログ。1つのイベントの配信試行回数、リクエストペイロード、エラーレスポンスが表示されている

FAQ