middle
Что такое Webhooks в Jira и как их использовать?
Webhook — механизм уведомления внешних систем о событиях в Jira через HTTP POST-запросы. Когда в Jira происходит определённое событие, Jira отправляет JSON-payload на указанный URL.
Аналогия из жизни: webhook — это как SMS-уведомление от банка. Вместо того чтобы каждые 5 минут проверять баланс (polling), банк сам отправляет вам сообщение при каждой операции (push).
Доступные события
| Событие | Описание |
|---|---|
jira:issue_created |
Создание задачи |
jira:issue_updated |
Обновление задачи |
jira:issue_deleted |
Удаление задачи |
jira:worklog_updated |
Обновление worklog |
sprint_created |
Создание спринта |
sprint_started |
Старт спринта |
sprint_closed |
Закрытие спринта |
board_created |
Создание доски |
project_created |
Создание проекта |
comment_created |
Добавление комментария |
issuelink_created |
Создание связи |
Регистрация webhook через REST API
Пример
public void registerWebhook(String jiraBaseUrl, String token) {
String body = """
{
"name": "My Integration Webhook",
"url": "https://my-app.example.com/api/jira/webhook",
"events": [
"jira:issue_created",
"jira:issue_updated"
],
"filters": {
"issue-related-events-section": "project = PROJ AND type = Bug"
},
"excludeBody": false
}
""";
restClient.post()
.uri(jiraBaseUrl + "/rest/webhooks/1.0/webhook")
.header("Authorization", "Bearer " + token)
.body(body)
.retrieve()
.toBodilessEntity();
}
Структура payload (issue_updated)
Пример
{
"timestamp": 1700000000000,
"webhookEvent": "jira:issue_updated",
"issue_event_type_name": "issue_generic",
"user": {
"accountId": "5a1234567890",
"displayName": "Ivan Petrov"
},
"issue": {
"key": "PROJ-123",
"fields": {
"summary": "Исправить баг авторизации",
"status": {"name": "In Progress"},
"assignee": {"displayName": "Ivan Petrov"},
"priority": {"name": "Critical"}
}
},
"changelog": {
"items": [
{
"field": "status",
"fromString": "Open",
"toString": "In Progress"
}
]
}
}
Spring Boot webhook listener
Код JiraWebhookController
@RestController
@RequestMapping("/api/jira/webhook")
public class JiraWebhookController {
private static final Logger log = LoggerFactory.getLogger(JiraWebhookController.class);
@PostMapping
public ResponseEntity<Void> handleWebhook(
@RequestBody Map<String, Object> payload,
@RequestHeader(value = "X-Atlassian-Webhook-Identifier",
required = false) String webhookId) {
String event = (String) payload.get("webhookEvent");
log.info("Получен webhook: event={}, id={}", event, webhookId);
switch (event) {
case "jira:issue_created" -> handleIssueCreated(payload);
case "jira:issue_updated" -> handleIssueUpdated(payload);
default -> log.warn("Неизвестное событие: {}", event);
}
return ResponseEntity.ok().build();
}
private void handleIssueCreated(Map<String, Object> payload) {
Map<String, Object> issue = (Map<String, Object>) payload.get("issue");
String key = (String) issue.get("key");
Map<String, Object> fields = (Map<String, Object>) issue.get("fields");
String summary = (String) fields.get("summary");
log.info("Создана задача: {} - {}", key, summary);
}
private void handleIssueUpdated(Map<String, Object> payload) {
Map<String, Object> changelog = (Map<String, Object>) payload.get("changelog");
if (changelog != null) {
List<Map<String, Object>> items =
(List<Map<String, Object>>) changelog.get("items");
for (Map<String, Object> item : items) {
if ("status".equals(item.get("field"))) {
log.info("Статус изменён: {} → {}",
item.get("fromString"), item.get("toString"));
}
}
}
}
}
Безопасность
- Shared Secret (DC): при регистрации webhook указывается secret, Jira подписывает payload HMAC-SHA256
- IP Whitelisting: ограничение входящих запросов по IP Jira-инстанса
- HTTPS: обязательно для production
- Идемпотентность: webhook может быть доставлен повторно — обработчик должен быть идемпотентным
Частые ошибки
- Синхронная тяжёлая обработка в webhook-обработчике — нужно принять webhook, ответить 200 и обработать асинхронно
- Отсутствие валидации подписи — любой может отправить поддельный webhook
- Нет обработки дубликатов — Jira может повторно отправить webhook при timeout
- Регистрация webhook без JQL-фильтра — получение всех событий инстанса перегружает приложение
Как используется в 2026
- В Cloud рекомендуется использовать Forge Triggers вместо прямой регистрации webhooks
- Jira Automation (встроенная) заменяет простые webhook-сценарии (if event, then action)
- Для DC webhooks остаются основным механизмом push-интеграции
- Тренд на event-driven архитектуру: Jira webhook -> Kafka -> микросервисы
На собеседовании: подчеркните, что webhook — push-модель в отличие от polling. Обязательно упомяните идемпотентность и асинхронную обработку. Webhook должен ответить за 10 секунд (DC) / 30 секунд (Cloud). Для Cloud упомяните Forge Triggers как более надёжную альтернативу.