API Integration (Webhooks)

Rationale

35up offers the possibility of sending requests to predefined endpoints in reaction to events when they happen in the platform. Using this strategy, the cronjobs and other “pull” strategies — often expensive and inevitably delayed in time — can be replaced by “push” strategies that process the necessary data in near real-time and are much less resource-intensive.

For example, in order to detect order status changes, a traditional “pull” strategy would be implemented by regularly querying the GET /v1/orders/:order-id endpoint every X minutes/hours interval. As the number of orders accumulates, this cronjob could quickly result in hundreds of API calls on every run, potentially running for a long time, many times per day. This puts a lot of strain on both client and server systems, even it is clear that several of the references are not expected to be updated anytime soon, sometimes for a few days.

At the same time, the “pull” strategy is not very reliable, as it will always be delayed in time potentially for as much as the interval chosen to run the cronjob. If it runs once per day, the data update will potentially arrive 24h later. Also, when not implemented very carefully, cronjob batch processing can very easily lead to common errors, specially for long-running ones, such as abruptly terminating the process before the queue can reach the end or race conditions leading to deadlocks and duplicated operations.

Using a “push” strategy, both systems can leverage from a fully event-driven approach and handle the requests as they happen, only when they happen, as soon as they happen. The system requirements in this case are very different from the “pull” strategy, requiring the client (the seller) to keep a service permanently running and accepting requests coming from the server (35up). It also requires that the server can react and send predefined requests when events happen.

All the necessary requirements on 35up side are already fulfilled. Our systems are composed by distributed, event-driven microservices, only requiring the externalization of the important events to the client. On the client side, bringing a service up and running, accepting external connections should be an extremely easy task using virtually any programming language and framework existing in the market. In fact, it is estimated that such effort is nearly the same, if not easier, than composing a reliable set of cronjobs to perform the same operations, while also being substantially cheaper.

Current Availability

The webhook system running on the 35up platform is currently in “Beta” version and not publicly available, but sellers wanting to use it may request to join the beta program.

During the testing phase, only a limited number of events are available and there’s no user-friendly interface. In fact, the requests are configured by 35up’s Engineers directly according to the sellers’ specifications and the systems’ current capabilities.

Webhook Endpoint Specs

The webhook endpoints on the sellers side are expected to be running 24h per day, every day continuously since the moment the webhook is configured. The webhook service hosted by the client should follow the requirements below:

  • the webhook application should ideally be hosted under a domain or subdomain fully controlled by the seller. Using IP addresses for webhooks is not recommended and should be avoided, unless the seller is the sole user and permanent owner of a static IP address
  • the webhook must be exposed using HTTPS with a reasonably modern TLS certificate (v1.2 or v1.3). The TLS certificate should be issued by a well established Certificate Authority and have sufficiently long private keys (minimum 2048-bit RSA or 256-bit ECDSA). Also, the use of weak ciphers is expected to be disabled at the clients side. Using a private Certificate Authority and privately issued TLS certificates, although technically possible, require special configuration on the server’s side and should be avoided
  • the webhook endpoints must support either basic, digest and/or token-bearing HTTP authentication
  • HTTP 1.1 POST requests must be supported, containing JSON-encoded payload (Content-Type: application/json)
  • the client should respond with status code 202 Accepted to indicate successful receipt of the message, but any status code in the 2xx family will also be interpreted as success
  • a client response containing 4xx or 5xx status codes will be treated as failures
  • client responses containing 3xx status codes won’t follow redirects, for security reasons, and will count as failures
  • in case the webhook endpoint is unreachable, the requested will be retried for a certain number of times using a back-off algorithm. After the maximum number of retries is reached, all subsequent webhook requests will be paused until the outage is resolved
  • the response to a webhook request may include a short body (using JSON is recommended), which will be recorded on our servers and can be helpful for debugging purposes

An important warning: the events system is designed to follow an “at-least-once, entity-ordered” delivery. In an “at-least-once” approach, the client’s system should be able to tolerate duplicated events. Although rare, duplicated requests about the same event can still happen and, therefor, handling of such events must be done in an idempotent manner. By “entity-ordered” it means that the events about the same base entity id are delivered in chronological order, which does not mean that all events are globally chronological.

The constraints above favor a simpler implementation on both the client and server sides, releasing the need of dealing with multiphase commits, acknowledgment protocols and distributed transactions, as long as all events are effectively delivered at least once and idempotency of the operations is always observed.

Available Events

Events in the 35up platform are named using reverse-domain namespace starting with the io.tfup prefix, followed by the scope/audience “subdomain”, entity (and sub-entities) and event type. New scopes/audiences, entities/sub-entities and event types are expected to be added in the future.

The following events are currently available as triggers for webhook requests containing updates for the different entities used to process a customer order:

  • io.tfup.sellers.Order.Created
  • io.tfup.sellers.Order.StatusChanged
  • io.tfup.sellers.Order.Item.Created
  • io.tfup.sellers.Order.Item.StatusChanged
  • io.tfup.sellers.Order.Shipment.Created
  • io.tfup.sellers.Order.Shipment.StatusChanged
  • io.tfup.sellers.Order.Return.Created
  • io.tfup.sellers.Order.Return.StatusChanged

In addition to the above, the following inventory event may be interesting to react to product availability changes. Exposing the event below is considered experimental. The number of pushed events can be very (we mean VERY) numerous. Because of that, only the SKUs included in seller-specific product selections (such as custom categories) will be included in outgoing webhook requests:

  • io.tfup.inventory.Product.Availability.Changed (experimental, coming soon)

Events Payload

The documentation is under development and will be published soon. The data in the request body is JSON-encoded.

The payload of each webhook request will include at very least the ****id field of the entity in which the event occurred and its current status.

During the beta program, the payload of webhook requests can be tailored to the sellers’ needs using any of the fields available under the entity in which the event occurred. More details about the available field in the upcoming documentation.

An exception to this rule are the inventory-related events, which can currently only contain the product SKU and the affected fields (price or availability).

Some examples:

  • an io.tfup.sellers.Order.Created event can be configured to contain any other fields of the newly created Order entity in the payload of the webhook request, but will minimally contain at least the order id and current status
  • an io.tfup.inventory.Product.Availability.Changed event will contain only the product sku and the updated availability. Currently the payload of a webhook containing this event can’t be augmented using other fields from the product entity and will only be fired for products included in seller-specific product selections, in order to narrow down the amount of events