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 touser U1
webhook W1
fail - asset.transferredasset A
is then transferred touser U2
webhook W2
ok - asset.transferred- client side:
asset A
is owned byuser U2
webhook W1
is replayed and ok - asset.transferred- client side:
asset A
is owned byuser 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).
Updated 6 days ago