The webhooks supported since OpenAPI 3.1 define clear points in a process at which other APIs perform operations in a clearly defined way. The special feature of webhooks is that – in contrast to callbacks – they run synchronously with the process. The process can further process the results of the webhook.
What is the purpose of webhooks in OpenAPI 3.1?
Webhooks are actually nothing special in internet applications. The secret of WordPress‘ success lies, among other things, in the in-depth integration of webhooks in the entire application. It is only through this technology that WordPress can be extended so flexibly with plug-ins.
Principle use of webhooks with WordPress
A website consists of several areas, both technically and visually. It has at least a header, often a menu area, a content area and a footer.
The output of such a website is therefore a clearly defined process on WordPress. And this process consists of several steps. Before, during or after each of these steps, WordPress has defined points into which plug-ins can hook, the so-called webhooks.
In the simplified example shown here, the plug-in extends the menu of the website. In addition, it replaces a block on a page with the corresponding output. The special feature is that the plug-in influences the processing of data in the main WordPress process.
OpenAPI 3.1 webhooks are something like callbacks, only different
With OpenAPI 3.1, callbacks are of course still supported. Nowadays, callbacks are used especially for event-driven management. The API consumer can subscribe to a callback (subscription). In doing so, it informs the callback of the address for the notification. This is usually an API endpoint again. However, depending on the implementation, it could also be an email address. Some callbacks still support the transfer of certain (filter) parameters during subscription.
Callback example in online retail
We have all experienced a typical practical example of this kind of application: We have ordered something from an online retailer. And he sends the parcel. We are informed (at the latest) on the day of delivery that the package will arrive soon. But some providers now go so far as to send messages like “The driver is still 5 stops away”. What happened here?
Theoretically, a callback was set up in which a Geofence around the target address (here 5 stops) was also specified. When the event condition is reached, the API provider executes the callback, and we receive the corresponding message. And now we can become active ourselves, but we don’t have to. Some of these applications now allow us to track the position of the delivery truck on a map in real time. But we have to actively call up this application. If we do not do this, it has no influence on the delivery process. If we are not present and have not given permission for the delivery to be made, only then does an exception occur in the delivery process (insert paper message in letterbox).
This design has a decisive disadvantage regarding the implementation of the API Consumer. It means that a second, complete API must be created here. On the one hand, the consumer needs the endpoint for the callback. For this, a secure connection must be established from the API provider to the API consumer. On the other hand, it also needs a secure connection in the opposite direction for the other calls. The two APIs must therefore trust each other and support the respective opposing interfaces. However, the callback itself behaves passively.
Active event-driven control via webhooks as of OpenAPI 3.1
If webhooks are used instead of callbacks, the scenario could look somewhat different. Especially with just-in-time deliveries, it is important to have the right article at the right place at the right time. Often, however, several items are loaded on a truck that are needed at points in the production process that are close together in time. A haulage contractor has a rather narrow time window in which he has to deliver his goods to the respective ramp.
Traditionally, this is often difficult – despite the long experience and the high level of automation. Which ramp does the driver drive to first when he is standing at the barrier to the company premises? Here, a synchronous decision is required in the “delivery” process. The process must be interrupted and can only be continued when the necessary data is available.
The service process of the API provider is interrupted by the webhook. The webhook performs an action on the API consumer. The results of this action can then be processed further in the API provider’s service.
This return is a nice thing. But not absolutely necessary. It is also conceivable to define webhooks that only expect a positive return, e.g.:
Nothing but webhooks in the API
A major new feature of webhooks with OpenAPI 3.1 is now that APIs can be specified that consist solely of webhooks. When I first heard this, I found it strange. On further reflection, however, it makes perfect sense.
For example, APIs can be specified that serve purely for process monitoring. This is roughly comparable to the control centre of a power plant. The actual operation is fully automatic. Clearly defined events are displayed on the instruments of the control centre. The operators have the possibility to react to the individual events in a controlling manner.
Another approach would be to define APIs that follow a similar concept to WordPress. The API provider executes one or more clearly defined processes. For example, the calculation of an invoice, the booking of a ticket or the production of goods. Extension points are defined in this process, comparable to extension points in XML messages. And other APIs can hook into these extension points to extend the basic function dynamically and flexibly.
Let’s say we have written an API that can calculate and create very simple invoices. “Very simple” here means that it only supports one seller, one buyer and simple item positions. If webhooks are defined in this process at the right steps, this process can easily be extended. For example, a plug-in could add the consideration of discounts. Another plug-in the support of invoices in a foreign currency.
The skilful use of webhooks makes the difference
And this is exactly where the difficulty lies again, but at the same time a powerful opportunity. If I have also specified the plug-in API in such a way that it can handle additional APIs or even be extended again itself, webhooks become compelling tools.
But this is perhaps the most crucial point when designing an API with webhooks. I have to trust the (foreign) APIs. I have to trust them to change the data for my process in the way it is basically intended. On the API provider side, I have to consider that the data is manipulated in a way that I may not be able to use.
But on the other hand, the OpenAPI 3.1 specification also works against this. The API provider can also specify the data structure of the return format. However, a check for correctness of content or meaningfulness may have to be carried out additionally. If the provider does not take this situation into account or if the plug-in with the discount function has an error, the entire invoice could become incorrect. For example, if it subsequently (apparently) says on the item level that only EUR 17 is to be paid in total for an item with quantity 5 and a unit price of EUR 4.
A webhook example, created with GEFEG.FX
Since the official webhook examples of the OpenAPI initiative are feeble, I would like to conclude by showing a simple example in YAML format.
openapi: 3.1.0 info: title: GEFEG CrossIndustryInvoice Webhook example version: 1.0.0 webhooks: createLineItem: post: summary: Inform external API that a new LineItem is created requestBody: description: Information about a new line item in the invoice content: application/json: schema: $ref: '#/components/schemas/LineItem' required: true responses: 200: description: Return a 200 status to indicate that the data was processed successfully. The response body may contain the extended line item. content: application/json: schema: $ref: '#/components/schemas/LineItem' validateLineItem: get: summary: Validate the LineItem requestBody: description: Information about the LineItem to be validated content: application/json: schema: $ref: '#/components/schemas/LineItem' required: true responses: 406: description: Not Acceptable, The validation went wrong. 200: description: OK, the line item is valid components: schemas: LineItem: type: object properties: AssociatedDocumentLineDocument: $ref: '#/components/schemas/DocumentLineDocumentType' SpecifiedTradeProduct: $ref: '#/components/schemas/TradeProductType' SpecifiedLineTradeAgreement: $ref: '#/components/schemas/LineTradeAgreementType' SpecifiedLineTradeDelivery: $ref: '#/components/schemas/LineTradeDeliveryType' SpecifiedLineTradeSettlement: $ref: '#/components/schemas/LineTradeSettlementType' required: - AssociatedDocumentLineDocument - SpecifiedTradeProduct - SpecifiedLineTradeAgreement - SpecifiedLineTradeDelivery - SpecifiedLineTradeSettlement DocumentLineDocumentType: type: object properties: LineID: $ref: '#/components/schemas/IDType' LineTradeAgreementType: type: object properties: NetPriceProductTradePrice: $ref: '#/components/schemas/TradePriceType' required: - NetPriceProductTradePrice LineTradeDeliveryType: type: object properties: BilledQuantity: $ref: '#/components/schemas/QuantityType' required: - BilledQuantity LineTradeSettlementType: type: object properties: SpecifiedTradeSettlementLineMonetarySummation: $ref: '#/components/schemas/TradeSettlementLineMonetarySummationType' required: - SpecifiedTradeSettlementLineMonetarySummation TradeProductType: type: object properties: Name: $ref: '#/components/schemas/TextType' IDType: type: string QuantityType: type: number TextType: type: string TradePriceType: type: object properties: ChargeAmount: $ref: '#/components/schemas/AmountType' required: - ChargeAmount TradeSettlementLineMonetarySummationType: type: object properties: LineTotalAmount: $ref: '#/components/schemas/AmountType' required: - LineTotalAmount AmountType: type: numberCode language: YAML (yaml)
In this example, two webhooks are defined. The first
createLineItem is called when a new item is inserted. The webhook thus performs the POST operation in the external API and transfers the information about the current line item. The position that has been extended (if necessary by extension) is expected as the return value.
The second webhook
validateLineItem is used to be able to extend the validation of the item. The external API would thus be able to check the discount calculation, for example. If this is correct, it returns the code
200. If something went wrong, it returns the code
This example may not yet be fully developed in all respects, but is intended to show the possibility of using webhooks with OpenAPI 3.1.