Know when inventory and gains are current
Why inventory and gains lag the transactions you send, how long the lag usually is, and how to know when an account's figures are current.
Taxbit calculates inventory and gains automatically every time it processes a transaction, but the calculation runs in the background. POST /v1/transactions/external-id returns as soon as the transaction is ingested, before inventory reflects it. So there is always a short window in which a read of inventory or gains shows the account as it was before the transaction you sent.
How long the lag is
How long that window lasts depends on where the transaction lands in the account's history.
- The newest transaction for an account is typically reflected in under a second when transactions arrive in order. This is observed timing, not an SLA (service-level agreement).
- A transaction that lands earlier in an account's history is a historical change and has no SLA.
The first figure is a typical P95, not an SLA: 95 percent of end-to-end inventory calculations on append transactions finish within it. Append transactions are the latest for their account. A historical change has no SLA because it can require recalculating the account's entire inventory from that point forward.
This is why Send transactions and read gains asks you to send transactions in order, as soon as your platform processes them. Sent that way, each transaction is the newest for its account when it arrives.
Changes that recalculate history
Some changes always act on history, however promptly you send them:
- Backfilling older transactions, or correcting an older one by resending its
id. - Adding a backdated entry to an account's cost-basis method (
disposition_method) history. Gains from the entry's effective date forward recalculate, typically within a few seconds; this is observed timing, not an SLA. See Choose a cost-basis method.
After any of these, figures you read or showed earlier can change. Don't cache inventory or gains across a backfill or a method change.
Knowing when figures are current
Two signals exist, and neither is a guarantee on its own.
- The
INVENTORY_UPDATEwebhook is the better one. Taxbit sends it when an account's inventory changes, with the start of the affected range, so you know what to read again. It is not guaranteed after every write. calculation_statusappears on inventory and gains responses asin_progressorcomplete.completemeans no calculation is currently tracked for the account. It does not mean the latest write is reflected: a cost-basis method history entry, for example, does not mark the account as calculating, so a read right after one can showcompletewith the old figures.
So treat complete as "nothing is known to be running", not as confirmation that a particular change has landed. To confirm a specific change, check that the figures reflect it, as in Confirm your change is reflected.
Refresh on the webhook
With a webhook subscription, Taxbit delivers an INVENTORY_UPDATE event when an account's inventory changes. Poll as a fallback, as in Confirm your change is reflected; delivery is not guaranteed after every write. Each delivery wraps its events in a data array; iterate over it rather than reading only the first entry.
{
"timestamp": "2026-07-15T13:05:41Z",
"data": [
{
"id": "evt_3f81c2d0",
"event_type": "INVENTORY_UPDATE",
"account_id": "acct_5f2e7d10",
"latest_transaction_datetime": "2026-07-15T13:05:40Z",
"update_from_datetime": "2026-07-15T13:05:40Z",
"latest_transaction_id": "trade_88214",
"update_from_transaction_id": "trade_88214"
}
]
}When it arrives, re-read the account's inventory with GET /v1/inventory/summaries or GET /v1/inventory, as in Retrieve inventory. update_from_datetime is the start of the affected range: figures from that point forward may have changed, including gains you've already shown. When transactions were deleted, the event carries no *_transaction_id fields, so rely on the datetimes. The Webhook events page has the full event reference.
Confirm your change is reflected
To confirm that a specific write has landed, check the figures instead of waiting a fixed time. The summary in GET /v1/inventory and in each entry of GET /v1/inventory/summaries carries:
latest_transaction_datetime: the datetime of the latest transaction for the asset.client_modified_by_transaction_id: youridfor that transaction.
The response also carries a top-level calculation_status. Your change is reflected when calculation_status is complete and the asset's client_modified_by_transaction_id is the last transaction you sent for that asset. complete alone is not enough, as Knowing when figures are current explains.
curl -G $BASE_URL/v1/inventory/summaries \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "account_id=acct_5f2e7d10"{
"data": [
{
"asset": { "code": "ETH", "name": "Ethereum", "type": "Crypto", "uuid": "b7a005b5-f4d5-44ea-ae80-d4f9e8313558" },
"summary": {
"total_quantity": "1.5",
"total_quantity_with_cost_basis": "1.5",
"total_cost": "4875.00",
"average_unit_cost": "3250.00",
"latest_transaction_datetime": "2026-07-15T13:05:40Z",
"client_modified_by_transaction_id": "trade_88214"
}
}
],
"calculation_status": "complete"
}Poll with a short backoff until both conditions hold, then read the figures:
SENT_ID="trade_88214"
for attempt in 1 2 3 4 5 6; do
RESPONSE=$(curl -s -G $BASE_URL/v1/inventory/summaries \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "account_id=acct_5f2e7d10")
STATUS=$(echo "$RESPONSE" | jq -r '.calculation_status')
LATEST=$(echo "$RESPONSE" | jq -r '.data[] | select(.asset.code == "ETH") | .summary.client_modified_by_transaction_id')
if [ "$STATUS" = "complete" ] && [ "$LATEST" = "$SENT_ID" ]; then
echo "Inventory reflects $SENT_ID"
break
fi
sleep $((attempt * 2))
doneIf the loop runs out, the change may be a historical one that is still recalculating; see How long the lag is.
Where to go next
- Send transactions and read gains: the end-to-end walkthrough.
- Show tax data in your app: keep the figures on your tax screens current.
Updated 19 days ago
