0
# Callbacks and Webhooks
1
2
Advanced callback operations for asynchronous API interactions and webhook definitions supporting OpenAPI 3.1 features. This system enables documentation of event-driven API interactions, asynchronous callbacks, and webhook-based communication patterns.
3
4
## Capabilities
5
6
### Callback Operations
7
8
Defines callback operations that describe requests initiated by the server based on specific events or conditions.
9
10
```java { .api }
11
/**
12
* Defines callback operation for asynchronous interactions
13
* Applied to: METHOD, TYPE
14
* Repeatable: Yes
15
*/
16
@Callback(
17
name = "statusUpdateCallback", // Callback name (required)
18
callbackUrlExpression = "{$request.body#/callbackUrl}", // URL expression (required)
19
operation = @Operation( // Callback operation definition
20
method = "POST",
21
summary = "Status update notification",
22
description = "Called when status changes",
23
requestBody = @RequestBody(
24
content = @Content(
25
mediaType = "application/json",
26
schema = @Schema(implementation = StatusUpdateEvent.class)
27
)
28
),
29
responses = @ApiResponse(responseCode = "200", description = "Acknowledged")
30
),
31
extensions = {@Extension(...)} // Custom extensions
32
)
33
```
34
35
**Callback URL Expressions:**
36
37
```java
38
// Request body field
39
callbackUrlExpression = "{$request.body#/callbackUrl}"
40
41
// Request header
42
callbackUrlExpression = "{$request.header.X-Callback-URL}"
43
44
// Query parameter
45
callbackUrlExpression = "{$request.query.callback}"
46
47
// Static URL with parameters
48
callbackUrlExpression = "https://api.example.com/callbacks/{$request.body#/id}"
49
50
// Complex expression
51
callbackUrlExpression = "{$request.body#/baseUrl}/webhooks/{$request.body#/eventType}"
52
```
53
54
**Usage Examples:**
55
56
```java
57
// Payment processing callback
58
@POST
59
@Path("/payments")
60
@Operation(summary = "Process payment")
61
@Callback(
62
name = "paymentStatusCallback",
63
callbackUrlExpression = "{$request.body#/statusWebhookUrl}",
64
operation = @Operation(
65
method = "POST",
66
summary = "Payment status update",
67
description = "Called when payment status changes",
68
requestBody = @RequestBody(
69
description = "Payment status event",
70
content = @Content(
71
mediaType = "application/json",
72
schema = @Schema(implementation = PaymentStatusEvent.class),
73
examples = @ExampleObject(
74
name = "paymentCompleted",
75
value = """
76
{
77
"paymentId": "pay_123456",
78
"status": "completed",
79
"amount": 99.99,
80
"timestamp": "2023-12-01T10:30:00Z"
81
}
82
"""
83
)
84
)
85
),
86
responses = {
87
@ApiResponse(responseCode = "200", description = "Callback acknowledged"),
88
@ApiResponse(responseCode = "404", description = "Callback endpoint not found"),
89
@ApiResponse(responseCode = "500", description = "Callback processing failed")
90
}
91
)
92
)
93
public Response processPayment(@RequestBody PaymentRequest request) {}
94
95
// Order fulfillment callbacks
96
@PUT
97
@Path("/orders/{id}/fulfill")
98
@Operation(summary = "Fulfill order")
99
@Callbacks({
100
@Callback(
101
name = "orderShipped",
102
callbackUrlExpression = "{$request.body#/shippingWebhook}",
103
operation = @Operation(
104
method = "POST",
105
summary = "Order shipped notification",
106
requestBody = @RequestBody(
107
content = @Content(schema = @Schema(implementation = ShippingEvent.class))
108
)
109
)
110
),
111
@Callback(
112
name = "orderDelivered",
113
callbackUrlExpression = "{$request.body#/deliveryWebhook}",
114
operation = @Operation(
115
method = "POST",
116
summary = "Order delivered notification",
117
requestBody = @RequestBody(
118
content = @Content(schema = @Schema(implementation = DeliveryEvent.class))
119
)
120
)
121
)
122
})
123
public Response fulfillOrder(@PathParam("id") Long orderId, @RequestBody FulfillmentRequest request) {}
124
125
// User registration callback with verification
126
@POST
127
@Path("/users/register")
128
@Operation(summary = "Register new user")
129
@Callback(
130
name = "emailVerificationCallback",
131
callbackUrlExpression = "{$request.body#/verificationCallbackUrl}",
132
operation = @Operation(
133
method = "POST",
134
summary = "Email verification result",
135
description = "Called after user clicks verification link",
136
requestBody = @RequestBody(
137
content = @Content(
138
mediaType = "application/json",
139
schema = @Schema(implementation = VerificationEvent.class)
140
)
141
),
142
responses = @ApiResponse(responseCode = "200", description = "Verification processed"),
143
security = @SecurityRequirement(name = "callbackToken")
144
)
145
)
146
public Response registerUser(@RequestBody UserRegistrationRequest request) {}
147
148
// File processing callback with progress updates
149
@POST
150
@Path("/files/process")
151
@Operation(summary = "Process uploaded file")
152
@Callback(
153
name = "processingProgressCallback",
154
callbackUrlExpression = "{$request.body#/progressUrl}",
155
operation = @Operation(
156
method = "POST",
157
summary = "File processing progress update",
158
requestBody = @RequestBody(
159
content = @Content(
160
schema = @Schema(implementation = ProcessingProgressEvent.class)
161
)
162
),
163
responses = @ApiResponse(responseCode = "200", description = "Progress update received")
164
)
165
)
166
public Response processFile(@RequestBody FileProcessingRequest request) {}
167
```
168
169
### Callbacks Container
170
171
Container for multiple callback definitions on a single operation.
172
173
```java { .api }
174
/**
175
* Container for multiple Callback annotations
176
*/
177
@Callbacks({
178
@Callback(name = "success", callbackUrlExpression = "{$request.body#/successUrl}", operation = @Operation(...)),
179
@Callback(name = "failure", callbackUrlExpression = "{$request.body#/failureUrl}", operation = @Operation(...)),
180
@Callback(name = "progress", callbackUrlExpression = "{$request.body#/progressUrl}", operation = @Operation(...))
181
})
182
```
183
184
### Webhook Support (OpenAPI 3.1)
185
186
Defines webhook operations for event-driven API interactions using OpenAPI 3.1 webhook specification.
187
188
```java { .api }
189
/**
190
* Defines webhook operation (OpenAPI 3.1)
191
* Applied to: TYPE, METHOD
192
* Repeatable: Yes
193
*/
194
@Webhook(
195
name = "userCreated", // Webhook name (required)
196
operation = @Operation( // Webhook operation definition
197
method = "POST",
198
summary = "User created notification",
199
description = "Sent when a new user is created",
200
requestBody = @RequestBody(
201
description = "User creation event data",
202
content = @Content(
203
mediaType = "application/json",
204
schema = @Schema(implementation = UserCreatedEvent.class)
205
)
206
),
207
responses = {
208
@ApiResponse(responseCode = "200", description = "Webhook received successfully"),
209
@ApiResponse(responseCode = "410", description = "Webhook endpoint no longer available")
210
},
211
security = @SecurityRequirement(name = "webhookSignature")
212
),
213
extensions = {@Extension(...)} // Custom extensions
214
)
215
```
216
217
**Usage Examples:**
218
219
```java
220
// Application-level webhooks
221
@OpenAPIDefinition(
222
info = @Info(title = "E-commerce API", version = "1.0"),
223
webhooks = {
224
@Webhook(
225
name = "orderCreated",
226
operation = @Operation(
227
method = "POST",
228
summary = "Order created webhook",
229
requestBody = @RequestBody(
230
content = @Content(schema = @Schema(implementation = OrderCreatedEvent.class))
231
)
232
)
233
),
234
@Webhook(
235
name = "paymentProcessed",
236
operation = @Operation(
237
method = "POST",
238
summary = "Payment processed webhook",
239
requestBody = @RequestBody(
240
content = @Content(schema = @Schema(implementation = PaymentProcessedEvent.class))
241
)
242
)
243
)
244
}
245
)
246
public class ECommerceApplication {}
247
248
// Resource-specific webhooks
249
@Path("/subscriptions")
250
@Webhooks({
251
@Webhook(
252
name = "subscriptionActivated",
253
operation = @Operation(
254
method = "POST",
255
summary = "Subscription activated",
256
requestBody = @RequestBody(
257
content = @Content(schema = @Schema(implementation = SubscriptionEvent.class))
258
),
259
security = @SecurityRequirement(name = "webhookSignature")
260
)
261
),
262
@Webhook(
263
name = "subscriptionCancelled",
264
operation = @Operation(
265
method = "POST",
266
summary = "Subscription cancelled",
267
requestBody = @RequestBody(
268
content = @Content(schema = @Schema(implementation = SubscriptionEvent.class))
269
)
270
)
271
)
272
})
273
public class SubscriptionResource {}
274
275
// User lifecycle webhooks
276
@Webhook(
277
name = "userStatusChanged",
278
operation = @Operation(
279
method = "POST",
280
summary = "User status change notification",
281
description = "Triggered when user status changes (active, suspended, deleted)",
282
requestBody = @RequestBody(
283
description = "User status change event",
284
content = @Content(
285
mediaType = "application/json",
286
schema = @Schema(implementation = UserStatusChangeEvent.class),
287
examples = {
288
@ExampleObject(
289
name = "userSuspended",
290
summary = "User account suspended",
291
value = """
292
{
293
"eventType": "user.status.changed",
294
"userId": 12345,
295
"previousStatus": "active",
296
"newStatus": "suspended",
297
"reason": "Terms of service violation",
298
"timestamp": "2023-12-01T15:30:00Z"
299
}
300
"""
301
),
302
@ExampleObject(
303
name = "userDeleted",
304
summary = "User account deleted",
305
value = """
306
{
307
"eventType": "user.status.changed",
308
"userId": 12345,
309
"previousStatus": "active",
310
"newStatus": "deleted",
311
"reason": "User requested account deletion",
312
"timestamp": "2023-12-01T16:45:00Z"
313
}
314
"""
315
)
316
}
317
)
318
),
319
responses = {
320
@ApiResponse(
321
responseCode = "200",
322
description = "Webhook processed successfully"
323
),
324
@ApiResponse(
325
responseCode = "401",
326
description = "Invalid webhook signature"
327
),
328
@ApiResponse(
329
responseCode = "410",
330
description = "Webhook endpoint no longer exists"
331
)
332
},
333
security = @SecurityRequirement(name = "webhookSignature"),
334
extensions = {
335
@Extension(
336
name = "x-webhook-metadata",
337
properties = {
338
@ExtensionProperty(name = "retry-policy", value = "exponential-backoff"),
339
@ExtensionProperty(name = "max-retries", value = "5"),
340
@ExtensionProperty(name = "timeout", value = "30s")
341
}
342
)
343
}
344
)
345
)
346
```
347
348
### Webhooks Container
349
350
Container for multiple webhook definitions.
351
352
```java { .api }
353
/**
354
* Container for multiple Webhook annotations
355
*/
356
@Webhooks({
357
@Webhook(name = "created", operation = @Operation(...)),
358
@Webhook(name = "updated", operation = @Operation(...)),
359
@Webhook(name = "deleted", operation = @Operation(...))
360
})
361
```
362
363
## Advanced Patterns
364
365
### Conditional Callbacks
366
367
```java
368
@POST
369
@Path("/orders")
370
@Operation(summary = "Create order with conditional callbacks")
371
@Callbacks({
372
@Callback(
373
name = "fulfillmentCallback",
374
callbackUrlExpression = "{$request.body#/fulfillmentWebhook}",
375
operation = @Operation(
376
method = "POST",
377
summary = "Order fulfillment update",
378
description = "Called only for physical products requiring fulfillment"
379
)
380
),
381
@Callback(
382
name = "digitalDeliveryCallback",
383
callbackUrlExpression = "{$request.body#/deliveryWebhook}",
384
operation = @Operation(
385
method = "POST",
386
summary = "Digital product delivery",
387
description = "Called only for digital products"
388
)
389
)
390
})
391
public Response createOrder(@RequestBody OrderRequest request) {}
392
```
393
394
### Webhook Security Patterns
395
396
```java
397
@Webhook(
398
name = "secureWebhook",
399
operation = @Operation(
400
method = "POST",
401
summary = "Secure webhook with signature verification",
402
security = {
403
@SecurityRequirement(name = "webhookSignature"),
404
@SecurityRequirement(name = "bearerToken")
405
},
406
extensions = {
407
@Extension(
408
name = "x-webhook-security",
409
properties = {
410
@ExtensionProperty(name = "signature-header", value = "X-Webhook-Signature"),
411
@ExtensionProperty(name = "signature-algorithm", value = "HMAC-SHA256"),
412
@ExtensionProperty(name = "timestamp-header", value = "X-Webhook-Timestamp"),
413
@ExtensionProperty(name = "timestamp-tolerance", value = "300")
414
}
415
)
416
}
417
)
418
)
419
```
420
421
### Error Handling in Callbacks
422
423
```java
424
@Callback(
425
name = "robustCallback",
426
callbackUrlExpression = "{$request.body#/callbackUrl}",
427
operation = @Operation(
428
method = "POST",
429
summary = "Callback with comprehensive error handling",
430
responses = {
431
@ApiResponse(responseCode = "200", description = "Success"),
432
@ApiResponse(responseCode = "400", description = "Invalid callback data"),
433
@ApiResponse(responseCode = "401", description = "Unauthorized callback"),
434
@ApiResponse(responseCode = "404", description = "Callback endpoint not found"),
435
@ApiResponse(responseCode = "408", description = "Callback timeout"),
436
@ApiResponse(responseCode = "410", description = "Callback endpoint permanently unavailable"),
437
@ApiResponse(responseCode = "429", description = "Too many callback attempts"),
438
@ApiResponse(responseCode = "500", description = "Callback endpoint error")
439
},
440
extensions = {
441
@Extension(
442
name = "x-callback-retry",
443
properties = {
444
@ExtensionProperty(name = "max-attempts", value = "5"),
445
@ExtensionProperty(name = "backoff-strategy", value = "exponential"),
446
@ExtensionProperty(name = "initial-delay", value = "1s"),
447
@ExtensionProperty(name = "max-delay", value = "300s")
448
}
449
)
450
}
451
)
452
)
453
```
454
455
## Event Schema Examples
456
457
### Callback Event Schemas
458
459
```java
460
@Schema(description = "Payment status update event")
461
public class PaymentStatusEvent {
462
@Schema(description = "Payment identifier", example = "pay_123456")
463
private String paymentId;
464
465
@Schema(description = "Payment status", allowableValues = {"pending", "completed", "failed", "cancelled"})
466
private String status;
467
468
@Schema(description = "Payment amount", example = "99.99")
469
private BigDecimal amount;
470
471
@Schema(description = "Status change timestamp", format = "date-time")
472
private Instant timestamp;
473
474
@Schema(description = "Optional failure reason")
475
private String failureReason;
476
}
477
478
@Schema(description = "User status change event")
479
public class UserStatusChangeEvent {
480
@Schema(description = "Event type", example = "user.status.changed")
481
private String eventType;
482
483
@Schema(description = "User ID", example = "12345")
484
private Long userId;
485
486
@Schema(description = "Previous status", example = "active")
487
private String previousStatus;
488
489
@Schema(description = "New status", example = "suspended")
490
private String newStatus;
491
492
@Schema(description = "Reason for status change")
493
private String reason;
494
495
@Schema(description = "Change timestamp", format = "date-time")
496
private Instant timestamp;
497
}
498
```