Callbacks and Webhooks
These are the most advanced OpenAPI features in this guide, so they live on their own page.
Webhooks
OpenAPI webhooks document event notifications that your API may send to subscribers. They are top-level in the OpenAPI document under webhooks.
This is different from callbacks, which are tied to a specific operation.
OpenAPI webhooks describe the payloads and contract, but do not define how consumers subscribe. Subscription management is usually modeled as regular API endpoints such as
POST /subscriptions.
Define webhook payload schemas
[OpenApiSchemaComponent(RequiredProperties = ('event_id', 'event_type', 'timestamp', 'data'))]
class OrderEventPayload {
[OpenApiPropertyAttribute(Description = 'Unique identifier for this event', Format = 'uuid')]
[string]$event_id
[OpenApiPropertyAttribute(Description = 'Event type', Example = 'order.created')]
[string]$event_type
[OpenApiPropertyAttribute(Description = 'When the event occurred', Format = 'date-time')]
[datetime]$timestamp
[OpenApiPropertyAttribute(Description = 'Event payload')]
[hashtable]$data
}
Declare a webhook
function orderCreatedWebhook {
[OpenApiWebhook(HttpVerb = 'post', Pattern = '/webhooks/order.created')]
param(
[OpenApiRequestBody(Description = 'Order creation event payload')]
[OrderEventPayload]$Body
)
}
Generate and view
- Register OpenAPI generation:
Add-KrOpenApiRoute - Build and validate:
Build-KrOpenApiDocumentandTest-KrOpenApiDocument - Fetch the document and confirm
webhooksexists
Callbacks
OpenAPI callbacks document requests that your API may send as part of handling a specific operation. They live under paths.{path}.{verb}.callbacks and are typically driven by a callback URL supplied by the client.
Typical pattern
- The client calls an operation like
POST /v1/paymentsand includes callback URLs in the request body - The API documents one or more callback requests using:
[OpenApiCallback]to declare the callback operation[OpenApiCallbackRef]to attach it to the main operation undercallbacks
In Kestrun, callbacks can be declared inline or as reusable callback components under components.callbacks.
At runtime, callback URLs are resolved from a URL template that can contain:
- A request-body runtime expression like
{$request.body#/callbackUrls/status} - Token placeholders like
{paymentId}populated from callback function parameters
Example: payment create + callback
[OpenApiSchemaComponent(RequiredProperties = ('eventId', 'timestamp', 'paymentId', 'status'))]
class PaymentStatusChangedEvent {
[OpenApiPropertyAttribute(Format = 'uuid')]
[string]$eventId
[OpenApiPropertyAttribute(Format = 'date-time')]
[datetime]$timestamp
[OpenApiPropertyAttribute(Example = 'PAY-12345678')]
[string]$paymentId
[ValidateSet('authorized', 'captured', 'failed')]
[string]$status
}
function paymentStatusCallback {
[OpenApiCallback(
Expression = '$request.body#/callbackUrls/status',
HttpVerb = 'post',
Pattern = '/v1/payments/{paymentId}/status',
Inline = $true
)]
param(
[OpenApiParameter(In = 'path', Required = $true)]
[string]$paymentId,
[OpenApiRequestBody(ContentType = 'application/json')]
[PaymentStatusChangedEvent]$Body
)
}
function createPayment {
[OpenApiPath(HttpVerb = 'post', Pattern = '/v1/payments')]
[OpenApiResponse(StatusCode = '201', Description = 'Payment created')]
[OpenApiCallbackRef(Key = 'paymentStatus', ReferenceId = 'paymentStatusCallback', Inline = $true)]
param(
[OpenApiRequestBody(ContentType = 'application/json')]
[object]$body
)
Write-KrJsonResponse @{ paymentId = 'PAY-12345678'; status = 'pending' } -StatusCode 201
}
Enabling callback automation
The callback metadata describes callback requests your API may send. To actually dispatch them at runtime from PowerShell, enable Kestrun’s callback automation middleware.
Add-KrAddCallbacksAutomation
Enable-KrConfiguration
You can configure dispatch behavior directly:
Add-KrAddCallbacksAutomation -DefaultTimeout 30 -MaxAttempts 5 -BaseDelay 2 -MaxDelay 60
Or with a typed options object:
$opts = [Kestrun.Callback.CallbackDispatchOptions]::new()
$opts.DefaultTimeout = [TimeSpan]::FromSeconds(30)
$opts.MaxAttempts = 5
$opts.BaseDelay = [TimeSpan]::FromSeconds(2)
$opts.MaxDelay = [TimeSpan]::FromSeconds(60)
Add-KrAddCallbacksAutomation -Options $opts
How callback URLs are resolved
The callback URL is built by combining:
- The client-provided value referenced by the callback
Expression - The callback
Pattern - Token values such as
{paymentId}taken from callback function parameters
If the resolved URL is absolute, it is used as-is. If it is relative, Kestrun combines it with a default base URI derived from the current request.
Warning: When callback URLs come from client-supplied data, using them as-is can introduce SSRF risk. In production, validate and constrain callback targets with a strict host/protocol allowlist or a custom resolver.
Using callbacks inside an operation
A common pattern is:
- Accept callback URLs in the operation request body
- Perform the main work
- Invoke one or more callback functions with the required path tokens and body payload
Client-side request body example:
$body = @{
amount = 129.99
currency = 'USD'
callbackUrls = @{
status = 'https://client.example.com/callbacks/payment-status'
reservation = 'https://client.example.com/callbacks/reservation'
shippingOrder = 'https://client.example.com/callbacks/shipping-order'
}
} | ConvertTo-Json -Depth 10
Server-side callback invocation example:
paymentStatusCallback -PaymentId $paymentId -Body $paymentStatusChangedEvent
reservationCallback -OrderId $orderId -Body $reservationEvent
shippingOrderCallback -OrderId $orderId -Body $shippingOrderEvent