Creating Industry-Standard Webhooks with Standard Webhooks

Posted in
Standard Webhooks logo

Standard Webhooks is an initiative to bring common standards and guidelines to webhook development.

In the API economy, the need for new standards appears to never stop. We use them at every turn, whether it’s using API specification languages like OpenAPI and AsyncAPI to describe our APIs or those focused on a given vertical like open banking. Some of the API styles we rely on are, from a standards perspective, subsumed by high-level standards that can describe them using only coarse-grained terms.

Take Webhooks, for example. Webhooks are an acknowledged strand in the fabric of the API economy, and API providers use them to return information on long-running, asynchronous processes to API consumers. They serve to return the information in a timely manner and reduce the need for API consumers to poll for updates.

In this context, API providers can easily describe the webhooks they implement or expect to be implemented by API consumers with OpenAPI, creating either a template document that an API consumer can implement or by using the webhooks property in the API description document they publish. They can use the semantics of HTTP to describe the URL (where it is mandated), supported HTTP methods (most likely just a POST), and expected HTTP return codes (probably a 200).

Most implementers will be aware, however, that the nuances of describing webhooks needs more consideration than simply defining the signature of the implementation in OpenAPI. How is security defined comprehensively? How can a reference rather than a full payload be sent? These considerations have led to the creation of the Standard Webhooks specification, an effort to provide a richer means to specify Webhooks.

In this post, we’ll dig into the Standard Webhooks specification, look at its key features, and assess what it can provide over or alongside using an existing specification language like OpenAPI.

What Standard Webhooks Provides

Unlike the aforementioned AsyncAPI and OpenAPI, Standard Webhooks is not a specification language. The specification describes itself as “a set of strict webhook guidelines based on the existing industry best practices.” It does not propose a syntax for defining a webhook nor a description document for providing a definition.

What it does provide is a “set of conventions” that producers and consumers can observe to ensure webhooks are as interoperable as they can be. The conventions largely govern the data that is transmitted and the properties therein.

The conventions include:

  • How the data is transmitted over the wire: The specification recommends sending events over HTTP using a JSON payload.
  • Expected JSON properties: A formatted event type, timestamp, and data wrapper property are described.
  • The category of the event being transmitted: The specification describes “thin” vs. “full” payloads whereby an event contains all the data a consumer would require or just updates.
  • How the authenticity of a webhook can be checked: The specification provides copious guidance on signing and verification approaches.
  • Header requirements: Where webhooks are signed, the headers required to ensure that an event can be verified successfully, including measures to ensure that the message signature can still be verified during key rotation activities.
  • What are termed “operational considerations”: Guidance on setting event types, retries, and preferred HTTP status codes.

This list vastly summarizes the guidance provided by the specification, which, unlike many standards, uses more “friendly” language to specify the requirements. It also calls out that adherence to the standard on its own is not likely enough to communicate the shape of a given webhook: Using OpenAPI or JSON Schema should act as a complement to provide the shape of request and response payloads.

Alongside the specification itself, Standard Webhooks provides libraries in several programming languages with signing and verification functions and simple examples. These provide a concrete, lightweight specification implementation, including guidance around timestamp tolerances.

Real-World Example: Payment Responses with Standard Webhooks

As with most standards, it helps to explain them with a concrete example. Communicating the result of an asynchronous operation is an obvious and already discussed motivation for an API provider to require customers to implement a webhook, and this is particularly true when one considers payments use cases. The majority of payment systems are asynchronous, and providing a synchronous and complete response to a payment initiation request via an API is simply not feasible. Many payment providers, therefore, use webhooks to communicate payment status.

For example, a payment provider in the UK might send a customer an event that indicates a wholesale payment has been completed using the CHAPS system that provides real-time gross settlement. They set the type to chaps.payment.completed, include a timestamp, and provide a number of ISO 20022 identifiers prescribed by the backend payment system:

  "type": "chaps.payment.completed",
  "timestamp": "2023-12-25T23:58:10.123456Z",
  "data": {
    “id”: “8817adab-38ac-4667-bda4-0d2411016c5e”,
    "paymentInitiationId": "8ed08d73-3a7d-4b87-885c-7a4119f4b2db",
    "endToEndIdentification": "3eafcd70-c904-4c96-95a3-b6d4cfbc8a30",
    "uetr": "3bb2f5a2-8752-450b-913a-e232e13411c8"

The content of the message follows the “thin” message style as only a subset of information is sent that allows the webhook consumer to correlate the event with the payment instruction in their system-of-record for payments. Given the use case, however, the webhook producer needs to sign this message for non-repudiation purposes.

A signature is therefore calculated, and the message is transmitted using the {msg_id}.{timestamp}.{payload} notation as shown below (JSON is abridged for readability):

POST /chaps-notifications

webhook-id: 2ace281c-ffed-478c-b61b-6dc6bd07e1cc
webhook-timestamp: 1703548799
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= v1a,hnO3f9T8Ytu9HwrXslvumlUpqtNVqkhqw/enGzPCXe5BdqzCInXqYXFymVJaA7AZdpXwVLPo3mNl8EM+m7TBAg==

2ace281c-ffed-478c-b61b-6dc6bd07e1cc.1703548799.{ ... }

On receipt, the webhook consumer will be able to verify the message, using either a Standard Webhooks library or a custom implementation. The key being used is correlated using the signature identifier that prefixes the value of webhook-signature, a mechanism that loosely resembles the key identifier or kid used for signed JSON Web Tokens (JWTs). The webhook consumer will then be able to parse the payload and correlate the event with their payment record, marking it as complete.

Final Thoughts: Standard Webhooks and API Descriptions

It’s clear that the Standard Webhooks specification provides a means for webhook producers to specify consistent and deterministic webhooks with libraries that make implementation easier. The specification itself is a body of work that tends towards implementation advice rather than providing concrete requirements or constraints for structuring webhooks.

As the Standard Webhooks specification fully acknowledges, however, there is still work to be done to fully describe a webhook. The specification meets specific needs but does not provide the complete picture. Using a description language like OpenAPI, for example, can provide a schematic representation of the contents of the webhook messages expressed using a Schema Object.

When you consider the intersection between the Standard Webhooks specification and an API specification language like OpenAPI, there’s an important point to be made. Sometimes, tools that should be complementary do not work seamlessly together. For example, fully describing the Standards Webhooks signature in OpenAPI is difficult because the built-in typing mechanisms tend towards objects and not plain strings. Sure, you could use a string with a pattern to describe the structure, but there needs to be an extra hop to extract the metadata and payload using a fully-formed Schema Object. In native OpenAPI, this is difficult and reflects the same situation with JWTs in OpenAPI that are similarly serialized but equally cannot be directly represented in a Schema Object.

This is where specification owners and the communities that support them should be looking to plug the gap with supplements to the standards, overlays, or tooling that can support the parsing process. In this specific case, we can also hope that the Moonwalk initiative, which is building version 4 of OpenAPI, addresses such use cases. Ensuring such interconnectedness of specifications is vital to ensuring that standards can support each other where there is a clear and relevant intersection. This can only make everyone’s lives easier — human or machine — when it comes to understanding the interfaces we publish.