Open Api Callback Server
This recipe shows the receiver side of OpenAPI callbacks and webhooks: you generate ASP.NET Core minimal API endpoints that a remote service can call when it needs to notify your application.
What This Shows
| Feature | Where |
|---|---|
| Generating callback/webhook server stubs from OpenAPI 3.2 | corvusjson openapi-callback-server |
| Implementing generated handler interfaces | Program.cs |
| Registering generated minimal API endpoints | app.MapApiEndpoints(...) |
| Receiving per-operation callbacks and top-level webhooks in one app | CallbackReceiver |
| Runtime expression route parameters | OnEventCallbackRoute: "/callbacks/onEvent" |
Customizing each endpoint via the configureEndpoint hook |
app.MapApiEndpoints(..., configureEndpoint: ...) |
Generating the Callback Server
Run the generator from this directory:
corvusjson openapi-callback-server event-api.json \
--rootNamespace EventSubscription.CallbackServer \
--outputPath ./Generated
Expected generated artifacts include:
Generated/IApiCallbacksHandler.csGenerated/IApiWebhooksHandler.csGenerated/ApiEndpointRegistration.csGenerated/OnEventCallbackParams.csGenerated/OnEventCallbackResult.csGenerated/SystemAlertWebhookParams.csGenerated/SystemAlertWebhookResult.csGenerated/Models/for schema-backed payload types
How to Implement the Handler
After generation, implement the handler interfaces produced from the callback and webhook tags:
internal sealed class CallbackReceiver : IApiCallbacksHandler, IApiWebhooksHandler
{
public ValueTask<OnEventCallbackResult> HandleOnEventCallbackAsync(
OnEventCallbackParams parameters,
JsonWorkspace workspace,
CancellationToken cancellationToken = default)
{
return ValueTask.FromResult(OnEventCallbackResult.Ok());
}
public ValueTask<SystemAlertWebhookResult> HandleSystemAlertWebhookAsync(
SystemAlertWebhookParams parameters,
JsonWorkspace workspace,
CancellationToken cancellationToken = default)
{
return ValueTask.FromResult(SystemAlertWebhookResult.Ok());
}
}
The generated middleware handles JSON parsing and schema validation before your method runs, so parameters.Body is already a strongly-typed model from the OpenAPI document.
How to Wire Up Endpoints
Once the generated files exist, map them into ASP.NET Core minimal APIs:
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
CallbackReceiver handler = new(app.Logger);
app.MapApiEndpoints(
callbacksHandler: handler,
webhooksHandler: handler,
OnEventCallbackRoute: "/callbacks/onEvent");
app.Run();
MapApiEndpoints() comes from generated code. It registers one route per callback or webhook operation and dispatches the request to your handler implementation.
The OnEventCallbackRoute parameter is required because the OpenAPI spec defines this callback path as a runtime expression ({$request.body#/callbackUrl}). The actual URL is determined dynamically by the client at subscription time, so you — the server implementer — choose what route to listen on.
Customizing the Generated Endpoints
MapApiEndpoints has a second overload that accepts a configureEndpoint callback. It runs once per generated endpoint, after the route is mapped, with a descriptor for the operation and the route's IEndpointConventionBuilder — the standard ASP.NET extension point for naming, tags, authorization, output caching, rate limiting, and more. The bare overload is preserved, so adopting the hook is source- and binary-compatible.
app.MapApiEndpoints(
callbacksHandler: handler,
webhooksHandler: handler,
OnEventCallbackRoute: "/callbacks/onEvent",
configureEndpoint: static (in EndpointDescriptor endpoint, IEndpointConventionBuilder builder) =>
{
builder.WithName(endpoint.OperationId ?? endpoint.MethodName);
builder.WithTags([.. endpoint.Tags]);
// IsCallback is true for every webhook/callback operation.
if (endpoint.IsCallback)
{
builder.WithMetadata(new EndpointGroupNameAttribute("event-deliveries"));
}
// Apply any declared OpenAPI security using the canonical policy-name convention.
// event-api.json declares no security, so every endpoint is AllowAnonymous.
builder.RequireDeclaredAuthorization(endpoint);
});
EndpointDescriptor carries the OperationId, MethodName, HttpMethod, RouteTemplate, Tags, IsCallback, and SecurityRequirements (a list of OR alternatives) for the operation. See the "Applying OpenAPI Security as Authorization" section of the main OpenAPI guide (docs/OpenApi.md) for the full descriptor reference and the RequireDeclaredAuthorization helper.
Running the Recipe
dotnet run --project docs/ExampleRecipes/034-OpenApiCallbackServer
Registered endpoints
| Method | Route | Purpose |
|---|---|---|
| POST | /systemAlert |
Receives webhook deliveries for the systemAlert webhook |
| POST | /callbacks/onEvent |
Receives callback deliveries for the onEvent callback |
Testing with curl
Send a webhook (system alert):
curl -X POST http://localhost:64910/systemAlert -H "Content-Type: application/json" -d '{"alertId":"alert-001","severity":"critical","message":"Disk usage exceeded 90%."}'
Send a callback (event notification):
curl -X POST http://localhost:64910/callbacks/onEvent -H "Content-Type: application/json" -d '{"eventId":"evt-123","eventType":"subscription.created","timestamp":"2026-01-15T10:30:00Z"}'
Both should return HTTP 200.
Key Concepts
- Webhooks have static paths — the route is known at build time from the webhook name (e.g.
systemAlert). - Callbacks with runtime expressions (e.g.
{$request.body#/callbackUrl}) have dynamic paths — you provide the route at registration time since only you know what URL your clients will use. - The generator produces strongly-typed
ParamsandResultstructs so your handler receives validated data and returns spec-compliant responses.