Developer console

Best Practices

Managing Errors and Replays

If an error occurs during the dispatch of a webhook, you will see this in the Webhook logs.

When an error occurs, we will attempt to resend the event until we receive a success response code (2xx).

The retry rate varies, however we will typically resend the event up to 10 times per hour, up to a maximum of 72 hours after the initial event was registered.

If after retrying for 72 hours we still do not receive a success response code, we will mark the event as expired and no longer attempt to resend it.

During the 72 hours, you are able to change the webhook configuration, in case the URL or access credentials are incorrect. We will resend the event using the up-to-date credentials.


Receiving an out of date event

Each event will contain the event_time (the time that the event was initially registered in UTC) and a sent_at time (the time that this dispatch took place in UTC), as well as the number of attempts x-webhook-attempt.

{
  "data": {
    "asset": {
      "uid: "ABC1234567"
      ...
    }
  },  
  "event_time": "2023-08-01T11:24:36.531141+00:00",  
  "sent_at": "2023-07-31T17:27:01.764524+00:00",  
  "x-webhook-attempt": "100",  
}

If there is a large difference between the event_time and sent_at time, it may be that the event becomes irrelevant. For example, observe the following edge case:

  • asset A is transferred to user U1
  • webhook W1 fail - asset.transferred
  • asset A is then transferred to user U2
  • webhook W2 ok - asset.transferred
  • client side: asset A is owned by user U2
  • webhook W1 is replayed and ok - asset.transferred
  • client side: asset A is owned by user U1

In this case, as a contingency you should check the difference between the event_time and sent_at times, and possibly the x-webhook-attempt.

If the x-webhook-attempt is greater than a certain threshold, or the difference between the event_time and sent_at are greater than a certain threshold, then it is recommended to make a request to get the current state of the asset:

from datetime import timedelta, datetime
from api import get_asset_api # Calls GET /asset/{uid}

attempt_threshold = 10
time_diff_threshold_in_mins = 10

def handle_asset_webhook_event(webhook_event: dict):
    asset_uid = webhook_event["data"]["asset"]["uid"]
    attempt_number = webhook_event["x-webhook-attempt"]
    sent_at = datetime.fromisoformat(webhook_event["sent_at"])
    event_time = datetime.fromisoformat(webhook_event["event_time"])

    # Check if either the attempt threshold or time difference threshold is exceeded
    if attempt_number > attempt_threshold:
        asset_data = get_asset_api(asset_uid)

    elif sent_at - event_time > timedelta(minutes=time_diff_threshold_in_mins):
        asset_data = get_asset_api(asset_uid)

    else:
        asset_data = webhook_event["data"]["asset"]

    # handle asset_data
    ...

In general, it is good practice to use the webhook as a signal that "something changed" and then make a request to get the latest data if you expect there to be many transactions of a particular resource or to catch edge cases such as the one highlighted above.

However in the majority of cases, the latest webhook event will be the latest one, and this can be verified with the event time (in UTC).