OpenAPI Callbacks

    The current page still doesn’t have a translation for this language.

    But you can help translating it: Contributing.

    You could create an API with a path operation that could trigger a request to an external API created by someone else (probably the same developer that would be using your API).

    The process that happens when your API app calls the external API is named a “callback”. Because the software that the external developer wrote sends a request to your API and then your API calls back, sending a request to an external API (that was probably created by the same developer).

    In this case, you could want to document how that external API should look like. What path operation it should have, what body it should expect, what response it should return, etc.

    Let’s see all this with an example.

    Imagine you develop an app that allows creating invoices.

    These invoices will have an id, title (optional), customer, and total.

    The user of your API (an external developer) will create an invoice in your API with a POST request.

    Then your API will (let’s imagine):

    • Send the invoice to some customer of the external developer.
    • Collect the money.
    • Send a notification back to the API user (the external developer).
      • This will be done by sending a POST request (from your API) to some external API provided by that external developer (this is the “callback”).

    Let’s first see how the normal API app would look like before adding the callback.

    It will have a path operation that will receive an Invoice body, and a query parameter callback_url that will contain the URL for the callback.

    This part is pretty normal, most of the code is probably already familiar to you:

    Tip

    The callback_url query parameter uses a Pydantic type.

    The only new thing is the callbacks=messages_callback_router.routes as an argument to the path operation decorator. We’ll see what that is next.

    And it will probably vary a lot from one app to the next.

    It could be just one or two lines of code, like:

    1. callback_url = "https://example.com/api/v1/invoices/events/"
    2. requests.post(callback_url, json={"description": "Invoice paid", "paid": True})

    But possibly the most important part of the callback is making sure that your API user (the external developer) implements the external API correctly, according to the data that your API is going to send in the request body of the callback, etc.

    So, what we will do next is add the code to document how that external API should look like to receive the callback from your API.

    That documentation will show up in the Swagger UI at /docs in your API, and it will let external developers know how to build the external API.

    This example doesn’t implement the callback itself (that could be just a line of code), only the documentation part.

    Tip

    The actual callback is just an HTTP request.

    When implementing the callback yourself, you could use something like or Requests.

    This code won’t be executed in your app, we only need it to document how that external API should look like.

    But, you already know how to easily create automatic documentation for an API with FastAPI.

    So we are going to use that same knowledge to document how the external API should look like… by creating the path operation(s) that the external API should implement (the ones your API will call).

    Tip

    When writing the code to document a callback, it might be useful to imagine that you are that external developer. And that you are currently implementing the external API, not your API.

    Temporarily adopting this point of view (of the external developer) can help you feel like it’s more obvious where to put the parameters, the Pydantic model for the body, for the response, etc. for that external API.

    First create a new APIRouter that will contain one or more callbacks.

    1. from typing import Union
    2. from fastapi import APIRouter, FastAPI
    3. from pydantic import BaseModel, HttpUrl
    4. app = FastAPI()
    5. class Invoice(BaseModel):
    6. id: str
    7. title: Union[str, None] = None
    8. customer: str
    9. total: float
    10. class InvoiceEvent(BaseModel):
    11. description: str
    12. paid: bool
    13. class InvoiceEventReceived(BaseModel):
    14. ok: bool
    15. invoices_callback_router = APIRouter()
    16. @invoices_callback_router.post(
    17. )
    18. def invoice_notification(body: InvoiceEvent):
    19. pass
    20. def create_invoice(invoice: Invoice, callback_url: Union[HttpUrl, None] = None):
    21. """
    22. Create an invoice.
    23. This will (let's imagine) let the API user (some external developer) create an
    24. invoice.
    25. And this path operation will:
    26. * Send the invoice to the client.
    27. * Collect the money from the client.
    28. * Send a notification back to the API user (the external developer), as a callback.
    29. * At this point is that the API will somehow send a POST request to the
    30. external API with the notification of the invoice event
    31. (e.g. "payment successful").
    32. """
    33. # Send the invoice, collect the money, send the notification (the callback)
    34. return {"msg": "Invoice received"}

    It should look just like a normal FastAPI path operation:

    • It should probably have a declaration of the body it should receive, e.g. body: InvoiceEvent.
    • And it could also have a declaration of the response it should return, e.g. response_model=InvoiceEventReceived.

    There are 2 main differences from a normal path operation:

    • It doesn’t need to have any actual code, because your app will never call this code. It’s only used to document the external API. So, the function could just have pass.
    • The path can contain an OpenAPI 3 expression (see more below) where it can use variables with parameters and parts of the original request sent to your API.

    The callback path can have an OpenAPI 3 expression that can contain parts of the original request sent to your API.

    In this case, it’s the str:

    1. "{$callback_url}/invoices/{$request.body.id}"

    So, if your API user (the external developer) sends a request to your API to:

    1. https://yourapi.com/invoices/?callback_url=https://www.external.org/events

    with a JSON body of:

    Then your API will process the invoice, and at some point later, send a callback request to the callback_url (the external API):

    1. https://www.external.org/events/invoices/2expen51ve

    with a JSON body containing something like:

    1. {
    2. "description": "Payment celebration",
    3. "paid": true
    4. }

    and it would expect a response from that external API with a JSON body like:

    Tip

    Notice how the callback URL used contains the URL received as a query parameter in callback_url (https://www.external.org/events) and also the invoice id from inside of the JSON body (2expen51ve).

    At this point you have the callback path operation(s) needed (the one(s) that the external developer should implement in the external API) in the callback router you created above.

    Now use the parameter callbacks in your API’s path operation decorator to pass the attribute .routes (that’s actually just a list of routes/path operations) from that callback router:

    1. from typing import Union
    2. from fastapi import APIRouter, FastAPI
    3. from pydantic import BaseModel, HttpUrl
    4. app = FastAPI()
    5. title: Union[str, None] = None
    6. customer: str
    7. total: float
    8. class InvoiceEvent(BaseModel):
    9. description: str
    10. paid: bool
    11. class InvoiceEventReceived(BaseModel):
    12. ok: bool
    13. invoices_callback_router = APIRouter()
    14. @invoices_callback_router.post(
    15. "{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived
    16. )
    17. def invoice_notification(body: InvoiceEvent):
    18. pass
    19. @app.post("/invoices/", callbacks=invoices_callback_router.routes)
    20. def create_invoice(invoice: Invoice, callback_url: Union[HttpUrl, None] = None):
    21. """
    22. Create an invoice.
    23. This will (let's imagine) let the API user (some external developer) create an
    24. invoice.
    25. And this path operation will:
    26. * Send the invoice to the client.
    27. * Collect the money from the client.
    28. * Send a notification back to the API user (the external developer), as a callback.
    29. * At this point is that the API will somehow send a POST request to the
    30. external API with the notification of the invoice event
    31. (e.g. "payment successful").
    32. """
    33. # Send the invoice, collect the money, send the notification (the callback)

    Tip

    Notice that you are not passing the router itself (invoices_callback_router) to callback=, but the attribute .routes, as in invoices_callback_router.routes.

    Now you can start your app with Uvicorn and go to .