Skip to content

Commit e5d5fe7

Browse files
committed
feat: webhooks join the Data Center surface
Nine operations under `jira.webhooks`, the one part of this surface Atlassian describes in prose rather than in a document. They come from the Jersey WADL a running instance serves and from calling each endpoint against Data Center 10.3, and the live suite exercises all nine — it is the only evidence the shapes are right, so it covers every one rather than a sample. The path is the reason this is worth having rather than hand-rolling: `/rest/webhooks/1.0/webhook` served Jira 9 and answers 404 on every 10.x. These use `/rest/jira-webhook/1.0/webhooks`. Four hundred and forty-four operations across sixty-one modules; the ledger reaches four hundred and thirty-eight of them and accounts for the other six.
1 parent afb964f commit e5d5fe7

24 files changed

Lines changed: 519 additions & 4 deletions

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
## 6.3.0
44

5-
Jira Data Center gets a client. `createServerClient` is a fourth surface alongside Cloud, Agile and Service Management — not the Cloud client pointed elsewhere, because the two APIs differ in more than their address: `/rest/api/2` against `/rest/api/3`, wiki markup against Atlassian Document Format, `name` and `key` against `accountId`. Of four hundred and thirty-five operations, two hundred and six share a name with a Cloud one and eighty-seven share a model name; nothing else is common but the transport.
5+
Jira Data Center gets a client. `createServerClient` is a fourth surface alongside Cloud, Agile and Service Management — not the Cloud client pointed elsewhere, because the two APIs differ in more than their address: `/rest/api/2` against `/rest/api/3`, wiki markup against Atlassian Document Format, `name` and `key` against `accountId`. Of four hundred and forty-four operations, two hundred and six share a name with a Cloud one and eighty-seven share a model name; nothing else is common but the transport.
66

77
Every one of those operations has been called against a running Jira Data Center instance. That is what the rest of these notes are: Atlassian generates the Data Center document from Java annotations rather than writing it, and it is wrong in ways reading cannot reveal.
88

99
### Features
1010

11-
* **`createServerClient` and `jira.js/server`.** Four hundred and thirty-five operations across sixty modules, generated from the Jira Data Center 11.3 LTS specification and usable against **Jira Data Center 10.0 and later**. Data Center publishes its platform, Agile and session endpoints as one document, so unlike Cloud there is no separate Agile factory — boards and sprints sit in the same client as issues.
11+
* **`createServerClient` and `jira.js/server`.** Four hundred and forty-four operations across sixty-one modules, generated from the Jira Data Center 11.3 LTS specification and usable against **Jira Data Center 10.0 and later**. Data Center publishes its platform, Agile and session endpoints as one document, so unlike Cloud there is no separate Agile factory — boards and sprints sit in the same client as issues.
1212

1313
```ts
1414
import { createServerClient } from 'jira.js';
@@ -23,6 +23,10 @@ Every one of those operations has been called against a running Jira Data Center
2323

2424
The nine operations that arrived after 10.0 carry an `@since` note naming the release each can be relied on from. Jira 9.x is not supported: Atlassian never published an OpenAPI document for it, and the line reached end of life on 26 June 2026.
2525

26+
* **Webhooks.** `createWebhook`, `getWebhooks`, `updateWebhook`, `deleteWebhook` and the five reads beside them, under `jira.webhooks`. They are the one part of this surface Atlassian describes in prose and in no specification, so they were written from the Jersey WADL a running instance serves at `/rest/jira-webhook/1.0/application.wadl` — which describes the requests and, its `grammars` element being empty, nothing about the bodies — and from calling each one against a live Data Center 10.3. The live suite exercises all nine.
27+
28+
Note the path. `/rest/webhooks/1.0/webhook` served Jira 9 and earlier and answers 404 on every 10.x; these use `/rest/jira-webhook/1.0/webhooks`, which is where Jira 10 moved them.
29+
2630
* **Basic authentication accepts a username and password.** A self-hosted account has no Atlassian address and no API token, so `auth: { type: 'basic', ... }` now takes either pair. The Cloud form is unchanged, and mixing the two halves is a validation error rather than a 401 an hour later.
2731

2832
* **OAuth 2.0 against a Data Center instance.** `generateServerAuthorizationUrl`, `exchangeServerAuthorizationCode` and `refreshServerOAuth2Token`, plus `auth: { type: 'oauth2Server' }` for a client that refreshes on its own. A self-hosted instance is its own authorization server, so none of this goes near `auth.atlassian.com` or the Atlassian gateway, and there is no cloud id to resolve.

docs/guide/data-center.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ publishes them as a single document, so unlike Cloud there is no separate Agile
2222
## Supported versions
2323

2424
Generated from the Jira Data Center 11.3 LTS specification, and usable against **Jira Data Center 10.0 and
25-
later**. The two releases differ by nine operations out of four hundred and thirty-five; each of those carries
25+
later**. The two releases differ by nine operations out of four hundred and forty-four; each of those carries
2626
an `@since` note in its documentation, and calling one on an older instance answers 404.
2727

2828
Jira 9.x is not supported. Atlassian never published an OpenAPI document for it, and the whole line reached end
@@ -111,6 +111,30 @@ The scopes are `READ`, `WRITE`, `ADMIN` and `SYSTEM_ADMIN`, each implying the on
111111
`redirectUri` belongs with the refresh credentials, not only with the initial exchange: the Data Center provider
112112
validates it on the refresh grant too, and omitting it earns an `invalid_grant` that explains nothing.
113113

114+
## Webhooks
115+
116+
The one part of this surface Atlassian does not describe in a specification. `createWebhook` and the eight
117+
operations beside it were written from the Jersey WADL a running instance serves at
118+
`/rest/jira-webhook/1.0/application.wadl`, which describes the requests and — its `grammars` element being
119+
empty — nothing about the bodies, and from calling each one against a live Data Center instance.
120+
121+
```typescript
122+
const webhook = await jira.webhooks.createWebhook({
123+
name: 'issue created',
124+
url: 'https://example.com/hooks/jira',
125+
events: ['jira:issue_created'],
126+
});
127+
128+
const statistics = await jira.webhooks.getWebhookStatistics({ webhookId: webhook.id });
129+
```
130+
131+
They live under `/rest/jira-webhook/1.0/`, which is where Jira 10 moved them. The older
132+
`/rest/webhooks/1.0/webhook` served Jira 9 and earlier and answers 404 on every release this client supports.
133+
134+
`getWebhookTransitions` and `getLatestWebhookInvocation` return `unknown`: an instance that has never
135+
delivered a webhook answers with an empty list and a 204, so there was nothing to describe, and guessing
136+
would have been worse than leaving the narrowing to the caller.
137+
114138
## Coming from Cloud
115139

116140
| | Cloud | Data Center |

docs/ru/guide/data-center.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const issue = await jira.issues.getIssue({ issueIdOrKey: 'PROJ-1' });
2222
## Поддерживаемые версии
2323

2424
Сгенерировано из спецификации Jira Data Center 11.3 LTS и работает начиная с **Jira Data Center 10.0**. Между
25-
этими выпусками разница в девять операций из четырёхсот тридцати пяти; у каждой из них в документации стоит
25+
этими выпусками разница в девять операций из четырёхсот сорока четырёх; у каждой из них в документации стоит
2626
пометка `@since`, а на более старом инстансе такой вызов отвечает 404.
2727

2828
Jira 9.x не поддерживается: Atlassian никогда не публиковала для неё OpenAPI-документ, а вся ветка завершила
@@ -110,6 +110,30 @@ const jira = createServerClient({
110110
`redirectUri` относится к набору для обновления, а не только к первичному обмену: провайдер Data Center проверяет
111111
его и на refresh-гранте, и без него приходит `invalid_grant`, который ничего не объясняет.
112112

113+
## Вебхуки
114+
115+
Единственная часть этой поверхности, которую Atlassian не описывает спецификацией. `createWebhook` и восемь
116+
операций рядом написаны по Jersey WADL, который отдаёт живой инстанс по адресу
117+
`/rest/jira-webhook/1.0/application.wadl` — он описывает запросы и, при пустом элементе `grammars`, ничего не
118+
говорит о телах, — и по вызовам каждой из них на живом инстансе Data Center.
119+
120+
```typescript
121+
const webhook = await jira.webhooks.createWebhook({
122+
name: 'issue created',
123+
url: 'https://example.com/hooks/jira',
124+
events: ['jira:issue_created'],
125+
});
126+
127+
const statistics = await jira.webhooks.getWebhookStatistics({ webhookId: webhook.id });
128+
```
129+
130+
Живут они по `/rest/jira-webhook/1.0/` — туда их перенесла Jira 10. Старый `/rest/webhooks/1.0/webhook`
131+
обслуживал Jira 9 и раньше и отвечает 404 на любом выпуске, который поддерживает этот клиент.
132+
133+
`getWebhookTransitions` и `getLatestWebhookInvocation` возвращают `unknown`: инстанс, который ни разу не
134+
доставил вебхук, отвечает пустым списком и 204 — описывать было нечего, а догадка была бы хуже, чем оставить
135+
уточнение типа вызывающему.
136+
113137
## Переход с облака
114138

115139
| | Cloud | Data Center |

src/server/api/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ export * from './upgrade';
108108

109109
export * from './users';
110110

111+
export * from './webhooks';
112+
111113
export * from './websudo';
112114

113115
export * from './workflowSchemes';

src/server/api/webhooks.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { WebhookSchema, type Webhook } from '../models/webhook';
2+
import { WebhookStatisticsSchema, type WebhookStatistics } from '../models/webhookStatistics';
3+
import {
4+
GetWebhookStatisticsSummarySchema,
5+
type GetWebhookStatisticsSummary,
6+
} from '../models/getWebhookStatisticsSummary';
7+
import type { GetWebhooks } from '../parameters/getWebhooks';
8+
import type { CreateWebhook } from '../parameters/createWebhook';
9+
import type { GetWebhook } from '../parameters/getWebhook';
10+
import type { UpdateWebhook } from '../parameters/updateWebhook';
11+
import type { DeleteWebhook } from '../parameters/deleteWebhook';
12+
import type { GetWebhookStatistics } from '../parameters/getWebhookStatistics';
13+
import type { GetWebhookStatisticsSummary as GetWebhookStatisticsSummaryParameters } from '../parameters/getWebhookStatisticsSummary';
14+
import type { GetWebhookTransitions } from '../parameters/getWebhookTransitions';
15+
import type { GetLatestWebhookInvocation } from '../parameters/getLatestWebhookInvocation';
16+
import type { Client, SendRequestOptions } from '#/core';
17+
import { z } from 'zod';
18+
19+
/** Returns the webhooks registered in this instance. Requires administrator permission. */
20+
export async function getWebhooks(client: Client, parameters?: GetWebhooks): Promise<Webhook[]> {
21+
const config: SendRequestOptions<Webhook[]> = {
22+
url: '/rest/jira-webhook/1.0/webhooks',
23+
method: 'GET',
24+
searchParams: {
25+
event: parameters?.event,
26+
statistics: parameters?.statistics,
27+
start: parameters?.start,
28+
limit: parameters?.limit,
29+
},
30+
schema: z.array(WebhookSchema),
31+
};
32+
33+
return await client.sendRequest(config);
34+
}
35+
36+
/** Registers a webhook. Requires administrator permission. */
37+
export async function createWebhook(client: Client, parameters: CreateWebhook): Promise<Webhook> {
38+
const config: SendRequestOptions<Webhook> = {
39+
url: '/rest/jira-webhook/1.0/webhooks',
40+
method: 'POST',
41+
body: {
42+
name: parameters.name,
43+
url: parameters.url,
44+
events: parameters.events,
45+
filters: parameters.filters,
46+
excludeBody: parameters.excludeBody,
47+
configuration: parameters.configuration,
48+
sslVerificationRequired: parameters.sslVerificationRequired,
49+
},
50+
schema: WebhookSchema,
51+
};
52+
53+
return await client.sendRequest(config);
54+
}
55+
56+
/** Returns a registered webhook. Requires administrator permission. */
57+
export async function getWebhook(client: Client, parameters: GetWebhook): Promise<Webhook> {
58+
const config: SendRequestOptions<Webhook> = {
59+
url: `/rest/jira-webhook/1.0/webhooks/${parameters.webhookId}`,
60+
method: 'GET',
61+
schema: WebhookSchema,
62+
};
63+
64+
return await client.sendRequest(config);
65+
}
66+
67+
/** Replaces a registered webhook. Requires administrator permission. */
68+
export async function updateWebhook(client: Client, parameters: UpdateWebhook): Promise<Webhook> {
69+
const config: SendRequestOptions<Webhook> = {
70+
url: `/rest/jira-webhook/1.0/webhooks/${parameters.webhookId}`,
71+
method: 'PUT',
72+
body: {
73+
name: parameters.name,
74+
url: parameters.url,
75+
events: parameters.events,
76+
filters: parameters.filters,
77+
excludeBody: parameters.excludeBody,
78+
configuration: parameters.configuration,
79+
sslVerificationRequired: parameters.sslVerificationRequired,
80+
},
81+
schema: WebhookSchema,
82+
};
83+
84+
return await client.sendRequest(config);
85+
}
86+
87+
/** Unregisters a webhook. Requires administrator permission. */
88+
export async function deleteWebhook(client: Client, parameters: DeleteWebhook): Promise<void> {
89+
const config: SendRequestOptions<void> = {
90+
url: `/rest/jira-webhook/1.0/webhooks/${parameters.webhookId}`,
91+
method: 'DELETE',
92+
};
93+
94+
return await client.sendRequest(config);
95+
}
96+
97+
/** Returns how a webhook has been delivering. Requires administrator permission. */
98+
export async function getWebhookStatistics(
99+
client: Client,
100+
parameters: GetWebhookStatistics,
101+
): Promise<WebhookStatistics> {
102+
const config: SendRequestOptions<WebhookStatistics> = {
103+
url: `/rest/jira-webhook/1.0/webhooks/${parameters.webhookId}/statistics`,
104+
method: 'GET',
105+
schema: WebhookStatisticsSchema,
106+
};
107+
108+
return await client.sendRequest(config);
109+
}
110+
111+
/** Returns the delivery statistics of a webhook, one entry per event it delivers. Requires administrator permission. */
112+
export async function getWebhookStatisticsSummary(
113+
client: Client,
114+
parameters: GetWebhookStatisticsSummaryParameters,
115+
): Promise<GetWebhookStatisticsSummary> {
116+
const config: SendRequestOptions<GetWebhookStatisticsSummary> = {
117+
url: `/rest/jira-webhook/1.0/webhooks/${parameters.webhookId}/statistics/summary`,
118+
method: 'GET',
119+
schema: GetWebhookStatisticsSummarySchema,
120+
};
121+
122+
return await client.sendRequest(config);
123+
}
124+
125+
/**
126+
* Returns the transitions a webhook has been through. Requires administrator permission. The shape of an entry is not
127+
* described here: an instance that has never delivered a webhook answers with an empty list, and guessing what a
128+
* populated one holds would be worse than leaving it to the caller.
129+
*/
130+
export async function getWebhookTransitions(client: Client, parameters: GetWebhookTransitions): Promise<unknown> {
131+
const config: SendRequestOptions<unknown> = {
132+
url: `/rest/jira-webhook/1.0/webhooks/${parameters.webhookId}/transitions`,
133+
method: 'GET',
134+
};
135+
136+
return await client.sendRequest(config);
137+
}
138+
139+
/**
140+
* Returns the most recent delivery of a webhook. Requires administrator permission. Until the webhook has been
141+
* delivered once Jira answers 204 and this resolves to `undefined`; the 204 is deliberately not declared, because
142+
* declaring it is what makes the whole call type as `void` and hides the body that does arrive.
143+
*/
144+
export async function getLatestWebhookInvocation(
145+
client: Client,
146+
parameters: GetLatestWebhookInvocation,
147+
): Promise<unknown> {
148+
const config: SendRequestOptions<unknown> = {
149+
url: `/rest/jira-webhook/1.0/webhooks/${parameters.webhookId}/latest`,
150+
method: 'GET',
151+
};
152+
153+
return await client.sendRequest(config);
154+
}

src/server/createServerClient.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import * as workflowSchemes from './api/workflowSchemes';
5959
import * as issueWorklogs from './api/issueWorklogs';
6060
import * as session from './api/session';
6161
import * as websudo from './api/websudo';
62+
import * as webhooks from './api/webhooks';
6263
import type {
6364
MoveIssuesToBacklog,
6465
GetAllBoards,
@@ -441,6 +442,15 @@ import type {
441442
GetIdsOfWorklogsModifiedSince,
442443
Login,
443444
Release,
445+
GetWebhooks,
446+
CreateWebhook,
447+
GetWebhook,
448+
UpdateWebhook,
449+
DeleteWebhook,
450+
GetWebhookStatistics,
451+
GetWebhookStatisticsSummary,
452+
GetWebhookTransitions,
453+
GetLatestWebhookInvocation,
444454
} from './parameters';
445455
import type {
446456
Page,
@@ -578,6 +588,9 @@ import type {
578588
WorklogChangedSince,
579589
CurrentUser,
580590
AuthSuccess,
591+
Webhook,
592+
WebhookStatistics,
593+
GetWebhookStatisticsSummary as GetWebhookStatisticsSummaryModel,
581594
} from './models';
582595

583596
export function createServerClient(clientConfig: ClientConfig | Client) {
@@ -1409,6 +1422,22 @@ export function createServerClient(clientConfig: ClientConfig | Client) {
14091422
websudo: {
14101423
release: (parameters: Release): Promise<void> => websudo.release(client, parameters),
14111424
},
1425+
webhooks: {
1426+
getWebhooks: (parameters?: GetWebhooks): Promise<Webhook[]> => webhooks.getWebhooks(client, parameters),
1427+
createWebhook: (parameters: CreateWebhook): Promise<Webhook> => webhooks.createWebhook(client, parameters),
1428+
getWebhook: (parameters: GetWebhook): Promise<Webhook> => webhooks.getWebhook(client, parameters),
1429+
updateWebhook: (parameters: UpdateWebhook): Promise<Webhook> => webhooks.updateWebhook(client, parameters),
1430+
deleteWebhook: (parameters: DeleteWebhook): Promise<void> => webhooks.deleteWebhook(client, parameters),
1431+
getWebhookStatistics: (parameters: GetWebhookStatistics): Promise<WebhookStatistics> =>
1432+
webhooks.getWebhookStatistics(client, parameters),
1433+
getWebhookStatisticsSummary: (
1434+
parameters: GetWebhookStatisticsSummary,
1435+
): Promise<GetWebhookStatisticsSummaryModel> => webhooks.getWebhookStatisticsSummary(client, parameters),
1436+
getWebhookTransitions: (parameters: GetWebhookTransitions): Promise<unknown> =>
1437+
webhooks.getWebhookTransitions(client, parameters),
1438+
getLatestWebhookInvocation: (parameters: GetLatestWebhookInvocation): Promise<unknown> =>
1439+
webhooks.getLatestWebhookInvocation(client, parameters),
1440+
},
14121441
};
14131442
}
14141443

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { z } from 'zod';
2+
import { WebhookStatisticsSchema } from './webhookStatistics';
3+
/** Keyed by event name. */
4+
5+
export const GetWebhookStatisticsSummarySchema = z.record(z.string(), WebhookStatisticsSchema);
6+
7+
export type GetWebhookStatisticsSummary = z.infer<typeof GetWebhookStatisticsSummarySchema>;

src/server/models/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ export * from './getReactionsRequest';
228228

229229
export * from './getUsersFromGroup';
230230

231+
export * from './getWebhookStatisticsSummary';
232+
231233
export * from './getWorkflow';
232234

233235
export * from './grantToPermissionInput';
@@ -674,6 +676,12 @@ export * from './voteWatchResult';
674676

675677
export * from './watchers';
676678

679+
export * from './webhook';
680+
681+
export * from './webhookInput';
682+
683+
export * from './webhookStatistics';
684+
677685
export * from './workflow';
678686

679687
export * from './workflowMapping';

src/server/models/webhook.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { z } from 'zod';
2+
import { apiObject } from '#/core';
3+
import { WebhookStatisticsSchema } from './webhookStatistics';
4+
5+
export const WebhookSchema = apiObject({
6+
id: z.number(),
7+
name: z.string(),
8+
url: z.string(),
9+
events: z.array(z.string()).optional(),
10+
createdDate: z.number().optional(),
11+
updatedDate: z.number().optional(),
12+
configuration: z.record(z.string(), z.any()).optional(),
13+
active: z.boolean().optional(),
14+
scopeType: z.string().optional(),
15+
sslVerificationRequired: z.boolean().optional(),
16+
statistics: WebhookStatisticsSchema.optional(),
17+
});
18+
19+
export type Webhook = z.infer<typeof WebhookSchema>;

src/server/models/webhookInput.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { z } from 'zod';
2+
import { apiObject } from '#/core';
3+
4+
export const WebhookInputSchema = apiObject({
5+
name: z.string(),
6+
/** Where Jira posts the event. */
7+
url: z.string(),
8+
/** The events to deliver, e.g. `jira:issue_created`. */
9+
events: z.array(z.string()).optional(),
10+
/** Narrows what is delivered, e.g. `{ "issue-related-events-section": jql }`. */
11+
filters: z.record(z.string(), z.any()).optional(),
12+
/** Deliver the event without its body. */
13+
excludeBody: z.boolean().optional(),
14+
configuration: z.record(z.string(), z.any()).optional(),
15+
sslVerificationRequired: z.boolean().optional(),
16+
});
17+
18+
export type WebhookInput = z.infer<typeof WebhookInputSchema>;

0 commit comments

Comments
 (0)