Skip to content

OpenAPI callbacks and webhooks

Route-level Callbacks need no special handling: a callbacks=[...] argument on a route is carried over to every versioned copy of it automatically:

callback_router = APIRouter()

@callback_router.post("{$url}")
def on_event(body: dict) -> None: ...

@router.post("/items", callbacks=callback_router.routes)
@api_version((1, 0))
def create_item() -> dict: ...

Webhooks (app.webhooks) are visible in every version's schema by default. Pass webhook_routers to version them the same way as regular routes, with @api_version on each definition:

webhook_router = APIRouter()

@webhook_router.post("/order-created")
@api_version((1, 0))
def webhook_order_v1(body: OrderV1) -> None: ...

# Same path and method as v1: replaces it instead of adding a second entry
@webhook_router.post("/order-created")
@api_version((2, 0))
def webhook_order_v2(body: OrderV2) -> None: ...

@webhook_router.post("/payment-failed")
@api_version((1, 0), remove_in=(2, 0))
def webhook_payment_v1(body: dict) -> None: ...

RouterVersioner(
    app=app,
    routers=router,
    webhook_routers=webhook_router,
    version_format=VersionFormat.SEMVER,
).versionize()

In webhook_versioning_app.py, which follows this pattern, v1.0 lists two webhooks and v2.0 only order-created, with its v2 definition (the example's second webhook is payment-received rather than payment-failed):

Swagger UI of version 1.0, with a Webhooks section listing order-created and payment-received

Swagger UI of version 2.0, with a Webhooks section listing only order-created

A webhook version only becomes visible once a route version reaches that same prefix, since both follow the same remove_in lifecycle.