OpenAPI Callbacks¶
You could create an API with apath operation that could trigger a request to anexternal API created by someone else (probably the same developer that would beusing your API).
The process that happens when your API app calls theexternal API is named a "callback". Because the software that the external developer wrote sends a request to your API and then your APIcalls back, sending a request to anexternal API (that was probably created by the same developer).
In this case, you could want to document how that external APIshould look like. Whatpath operation it should have, what body it should expect, what response it should return, etc.
An app with callbacks¶
Let's see all this with an example.
Imagine you develop an app that allows creating invoices.
These invoices will have anid,title (optional),customer, andtotal.
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 (fromyour API) to someexternal API provided by that external developer (this is the "callback").
The normalFastAPI app¶
Let's first see how the normal API app would look like before adding the callback.
It will have apath operation that will receive anInvoice body, and a query parametercallback_url that will contain the URL for the callback.
This part is pretty normal, most of the code is probably already familiar to you:
fromfastapiimportAPIRouter,FastAPIfrompydanticimportBaseModel,HttpUrlapp=FastAPI()classInvoice(BaseModel):id:strtitle:str|None=Nonecustomer:strtotal:floatclassInvoiceEvent(BaseModel):description:strpaid:boolclassInvoiceEventReceived(BaseModel):ok:boolinvoices_callback_router=APIRouter()@invoices_callback_router.post("{$callback_url}/invoices/{$request.body.id}",response_model=InvoiceEventReceived)definvoice_notification(body:InvoiceEvent):pass@app.post("/invoices/",callbacks=invoices_callback_router.routes)defcreate_invoice(invoice:Invoice,callback_url:HttpUrl|None=None):""" Create an invoice. This will (let's imagine) let the API user (some external developer) create an invoice. And this path operation will: * Send the invoice to the client. * Collect the money from the client. * Send a notification back to the API user (the external developer), as a callback. * At this point is that the API will somehow send a POST request to the external API with the notification of the invoice event (e.g. "payment successful"). """# Send the invoice, collect the money, send the notification (the callback)return{"msg":"Invoice received"}Tip
Thecallback_url query parameter uses a PydanticUrl type.
The only new thing is thecallbacks=invoices_callback_router.routes as an argument to thepath operation decorator. We'll see what that is next.
Documenting the callback¶
The actual callback code will depend heavily on your own API app.
And it will probably vary a lot from one app to the next.
It could be just one or two lines of code, like:
callback_url="https://example.com/api/v1/invoices/events/"httpx.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 theexternal API correctly, according to the data thatyour 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 thatexternal API should look like to receive the callback fromyour 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 theexternal 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 likeHTTPX orRequests.
Write the callback documentation code¶
This code won't be executed in your app, we only need it todocument how thatexternal API should look like.
But, you already know how to easily create automatic documentation for an API withFastAPI.
So we are going to use that same knowledge to document how theexternal API should look like... by creating thepath 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 thatexternal developer. And that you are currently implementing theexternal API, notyour API.
Temporarily adopting this point of view (of theexternal 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 thatexternal API.
Create a callbackAPIRouter¶
First create a newAPIRouter that will contain one or more callbacks.
fromfastapiimportAPIRouter,FastAPIfrompydanticimportBaseModel,HttpUrlapp=FastAPI()classInvoice(BaseModel):id:strtitle:str|None=Nonecustomer:strtotal:floatclassInvoiceEvent(BaseModel):description:strpaid:boolclassInvoiceEventReceived(BaseModel):ok:boolinvoices_callback_router=APIRouter()@invoices_callback_router.post("{$callback_url}/invoices/{$request.body.id}",response_model=InvoiceEventReceived)definvoice_notification(body:InvoiceEvent):pass@app.post("/invoices/",callbacks=invoices_callback_router.routes)defcreate_invoice(invoice:Invoice,callback_url:HttpUrl|None=None):""" Create an invoice. This will (let's imagine) let the API user (some external developer) create an invoice. And this path operation will: * Send the invoice to the client. * Collect the money from the client. * Send a notification back to the API user (the external developer), as a callback. * At this point is that the API will somehow send a POST request to the external API with the notification of the invoice event (e.g. "payment successful"). """# Send the invoice, collect the money, send the notification (the callback)return{"msg":"Invoice received"}Create the callbackpath operation¶
To create the callbackpath operation use the sameAPIRouter you created above.
It should look just like a normal FastAPIpath 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.
fromfastapiimportAPIRouter,FastAPIfrompydanticimportBaseModel,HttpUrlapp=FastAPI()classInvoice(BaseModel):id:strtitle:str|None=Nonecustomer:strtotal:floatclassInvoiceEvent(BaseModel):description:strpaid:boolclassInvoiceEventReceived(BaseModel):ok:boolinvoices_callback_router=APIRouter()@invoices_callback_router.post("{$callback_url}/invoices/{$request.body.id}",response_model=InvoiceEventReceived)definvoice_notification(body:InvoiceEvent):pass@app.post("/invoices/",callbacks=invoices_callback_router.routes)defcreate_invoice(invoice:Invoice,callback_url:HttpUrl|None=None):""" Create an invoice. This will (let's imagine) let the API user (some external developer) create an invoice. And this path operation will: * Send the invoice to the client. * Collect the money from the client. * Send a notification back to the API user (the external developer), as a callback. * At this point is that the API will somehow send a POST request to the external API with the notification of the invoice event (e.g. "payment successful"). """# Send the invoice, collect the money, send the notification (the callback)return{"msg":"Invoice received"}There are 2 main differences from a normalpath operation:
- It doesn't need to have any actual code, because your app will never call this code. It's only used to document theexternal API. So, the function could just have
pass. - Thepath can contain anOpenAPI 3 expression (see more below) where it can use variables with parameters and parts of the original request sent toyour API.
The callback path expression¶
The callbackpath can have anOpenAPI 3 expression that can contain parts of the original request sent toyour API.
In this case, it's thestr:
"{$callback_url}/invoices/{$request.body.id}"So, if your API user (the external developer) sends a request toyour API to:
https://yourapi.com/invoices/?callback_url=https://www.external.org/eventswith a JSON body of:
{"id":"2expen51ve","customer":"Mr. Richie Rich","total":"9999"}thenyour API will process the invoice, and at some point later, send a callback request to thecallback_url (theexternal API):
https://www.external.org/events/invoices/2expen51vewith a JSON body containing something like:
{"description":"Payment celebration","paid":true}and it would expect a response from thatexternal API with a JSON body like:
{"ok":true}Tip
Notice how the callback URL used contains the URL received as a query parameter incallback_url (https://www.external.org/events) and also the invoiceid from inside of the JSON body (2expen51ve).
Add the callback router¶
At this point you have thecallback path operation(s) needed (the one(s) that theexternal developer should implement in theexternal API) in the callback router you created above.
Now use the parametercallbacks inyour API's path operation decorator to pass the attribute.routes (that's actually just alist of routes/path operations) from that callback router:
fromfastapiimportAPIRouter,FastAPIfrompydanticimportBaseModel,HttpUrlapp=FastAPI()classInvoice(BaseModel):id:strtitle:str|None=Nonecustomer:strtotal:floatclassInvoiceEvent(BaseModel):description:strpaid:boolclassInvoiceEventReceived(BaseModel):ok:boolinvoices_callback_router=APIRouter()@invoices_callback_router.post("{$callback_url}/invoices/{$request.body.id}",response_model=InvoiceEventReceived)definvoice_notification(body:InvoiceEvent):pass@app.post("/invoices/",callbacks=invoices_callback_router.routes)defcreate_invoice(invoice:Invoice,callback_url:HttpUrl|None=None):""" Create an invoice. This will (let's imagine) let the API user (some external developer) create an invoice. And this path operation will: * Send the invoice to the client. * Collect the money from the client. * Send a notification back to the API user (the external developer), as a callback. * At this point is that the API will somehow send a POST request to the external API with the notification of the invoice event (e.g. "payment successful"). """# Send the invoice, collect the money, send the notification (the callback)return{"msg":"Invoice received"}Tip
Notice that you are not passing the router itself (invoices_callback_router) tocallback=, but the attribute.routes, as ininvoices_callback_router.routes.
Check the docs¶
Now you can start your app and go tohttp://127.0.0.1:8000/docs.
You will see your docs including a "Callbacks" section for yourpath operation that shows how theexternal API should look like:








