Вебхуки
Расширьте возможности Zealy с помощью подключений вебхуков.
Автор louis, Fredrika, benjamin

На этой странице
Вебхук Zealy отправляет HTTP POST на выбранный вами URL при наступлении одного из девяти событий сообщества, среди которых вступление участника, успешное или неуспешное выполнение квеста, запуск спринта и блокировка пользователя. Каждый payload содержит id события, тип, данные, метку времени в миллисекундах и секрет, подтверждающий подлинность запроса.
Основы
Zealy предоставляет вебхуки , которые позволяют получать HTTP push-уведомления при создании или обновлении данных. Это позволяет создавать интеграции поверх Zealy. Вы можете запускать CI-сборки, выполнять вычисления по данным квестов или отправлять сообщения при определённых условиях — на ваш выбор.
Перейдите в Settings > Integrations > Zapier & Webhooks, чтобы настроить вебхуки.
Как работает вебхук?
Push вебхука — это просто HTTP POST запрос, отправленный на выбранный вами URL. Push автоматически запускается Linear при обновлении данных. Пример того, какие данные содержит payload, см. в разделе Payload вебхука.
Ваш потребитель вебхука — это простой HTTP-эндпоинт. Он должен соответствовать следующим условиям:
- Он доступен по публичному HTTPS URL, не localhost
- Он отвечает на push вебхука Zealy (HTTP POST запрос) ответом
HTTP 200("OK")
Если доставка не удалась (т.е. сервер недоступен, ответ занял больше 5 секунд, тело ответа было больше 5000 байт или ответил кодом статуса HTTP, отличным от 200), push будет повторён 15 раз. Здесь используется экспоненциальная задержка: попытка будет повторена примерно через 10 минут, затем через 30 минут и так далее. Если URL вебхука продолжает не отвечать, вебхук может быть отключён Zealy, и его нужно будет включить снова вручную.
Чтобы убедиться, что вебхук POST действительно создан Zealy, мы генерируем случайный секрет, который будет прикреплён к телу запроса в поле secret .
Для дополнительной информации о вебхуках есть несколько хороших ресурсов:
- RequestBin: Webhooks – The Definitive Guide
- requestbin.com — отличный инструмент для тестирования вебхуков
- GitHub Developer Guide: Webhooks
Для чего я могу это использовать?
- Интеграция с Zapier
- Автоматизация пользовательских наград
- Уведомления о квестах, ожидающих проверки, в Telegram
И многое-многое другое!
Начало работы с вебхуками Zealy
- Определите, какие события вы хотите отслеживать.
- Разработайте функцию эндпоинта вебхука для получения POST-запросов с данными событий.
- Зарегистрируйте свой эндпоинт в Zealy с помощью панели вебхуков в Settings > Integrations > Zapier & Webhooks.
- Защитите свой эндпоинт вебхука.
Создание простого потребителя вебхука
Вы можете рассмотреть использование чего-то вроде 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.
Payload вебхука
HTTP payload вебхука будет содержать информацию как в HTTP-заголовках, так и в теле запроса.
Payload будет отправлен со следующими 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
};Защита вебхуков
При создании вебхука создаётся случайный секрет, который вы можете использовать для проверки того, что запрос приходит от вебхуков Zealy. Скопируйте секрет в правом верхнем углу панели проверки вебхука и убедитесь, что секрет, установленный в теле POST-запроса, совпадает.
В случае утечки секрета вы можете перегенерировать его на той же странице.

Тестирование вебхуков
Из панели проверки вебхука вы можете вручную запустить тестовое событие. Нажмите кнопку в правом верхнем углу и выберите «Send test event». Вы сможете выбрать тип события среди отслеживаемых типов событий. Событие будет запущено с mock-данными, и будет отображён статус.
Отладка
Вы можете просмотреть логи всех прошлых событий, запущенных вебхуком, если нажмёте на вебхук на странице Webhooks. Фильтруя по статусу, вы можете использовать логи для отладки, если что-то пошло не так. Для каждого события вы можете увидеть полученное сообщение об ошибке и отправленный запрос.
Вы также можете повторить любое событие с этой страницы.
