Webhooks/Callbacks


#1

I’m looking at strategies for handling long-running and/or asynchronous calls. So far I’ve identified two possibilities:

  1. POST an “order” or “request” resource and return a 200. Allow clients to poll via GET queries to the order resource. Order resource may contain a status attribute and relationships to other resources generated during processing. (A 202/303 strategy may not work here due to the creation of multiple resources.)
  2. POST an order resource and include a links property that specifies a callback or webhook link. If present, POST the updated order resource back to the supplied webhook link whenever there is a status change.

Is it acceptable to include links in a POST/PATCH document? If not, where would it be most appropriate to specify a callback URL?


#2

It is somewhat late so my response might be a bit crude, but in my mind your query to me boils down to “create a resource with hypermedia to define relationships, with the key of the GET polled URI” or “somehow make an RPC call within a restful service”.

Without knowing the details you have in mind when you mention the 202/303 strategy, my suggestion would be the following. I would suggest using a domain specific vocabulary for your hypermedia would be the best way to indicate the long running process.

However, short of taking a full dive into a hypermedia API, my next suggestion would be create your resources to align towards the more singular and standard ‘crud’ mentality, and return a 202 status with the Location header set to the URI to for polling purposes. Figure out and provide the caller with a Retry-After header and the appropriate HTTP Date or integer count of seconds to begin polling. Subsequent calls to the polling URI should also return 202’s and Retry-After headers if the process has yet to complete. Multiple resources being created with this long running process should not be an issue if there is a documented vocabulary which can describe the relationship between the primary resource and the additional new resources.


#3

Apologies; my original post was pretty light on context and details.

The idea is to define a resource (e.g. widget-orders) that will be persisted an queryable both during and after the order lifetime. During fulfillment of the order, one or more widgets resources, an invoices resource, and other related resources may be generated and related back to the original order. The order process may involve manual fulfillment or error correction steps and duration may vary as a result.

Clients (through an order page) can view the status of the order and related resources as they become available. Since the widget-orders resource is an actual part of the DSL it makes sense to me to respond to the initial POST with a 200 OK and invite the client to query for state (include status attribute) or cancel via related POST or PATCH requests.

However, there is also the idea that the order lifetime could be relatively short in duration–say a few minutes. In this case, it may be helpful to POST back the order resource state to the client when there is a status change to enable real time updates on the order screen or inform the user when the order has completed. This could be done behind the scenes via a lower-level queueing mechanism, but I’m exploring options to minimize the number of communication schemes and related coupling.

One option I’m considering is allowing the client to pass a webhook/callback URL in the request to receive POSTs of the updated widget-orders resource when there is a status change. There are a few ways to go about that; the client could pre-register a webhook to receive updates on all status changes, or clients could specify webhook URLs in the request. The latter could be done with attributes on the widget-orders resource, but I was curious whether the intent of the links object in the request document includes things like webhooks and whether it would be appropriate to allow them in POST requests.


#4

Thanks for the additional details, I think I have a clearer idea of what you intend to accomplish.

You certainly COULD include within the links section a link like: {“callback”:{“href”:“https://example.org/resource/callback”}}. However, best practice would be to utilize both the transport protocol and application protocol specifications to accomplish what you need.

My gut reaction would still be to stay mindful of statelessness and how much state you are willing to include in your application; how much responsibility you want to own in your design decisions. Specifically, I would suggest you avoid bringing subscription responsibility into your service layer and away from the consumer of your API via your callback suggestion. This will likely prove to be a serious design concern if your solution is required to rapidly scale, through increased per request resource requirements on your server and inability to leverage HTTP caching. It is counter-intuitive, but the web can scale because it can cache and short circuit requests.

To provide a 202 (Accepted but not done processing) status, with Location and Retry-After headers set would be the best way to support a single resource with async processing. You could use this in conjunction with the IANA relations monitor and monitor-group, and create a monitoring resource to enable as granular monitoring on the transaction as necessary without concerning yourself with consumer-centric decisions like broadcast timing. You could even go as far as to return the resource a GET to the Location header would return, where self link = Location header.

At the end of the day, you could probably do what you’re thinking within the JsonApi spec, but my very emphatic suggestion would be simply don’t. Adding veiled state to your server will make everything more difficult in the long run. Let clients decide when they want to know about something, and provide the means for them to do so.