Skip to content

Webhook event reference

This page documents the outbound structure produced by the current accountant service. It intentionally does not include event names that are absent from the sender implementation.

DELIVERY CONTRACT

Follow the nested event contract

{
  "events": [
    {
      "event": "user.data.change",
      "date": "2026-08-01T10:15:00Z",
      "user": {
        "id": "user_42",
        "username": "acme-customer",
        "reseller_id": "reseller_01"
      },
      "package": {
        "id": "package_9",
        "alias": "residential"
      },
      "order": {
        "id": "order_71",
        "data": 5368709120,
        "data_remaining": 2147483648,
        "ledger_id": "ledger_12",
        "is_unlimited_data": false,
        "data_allocated": 10737418240,
        "data_available": 2147483648,
        "data_available_percentage": 20,
        "latest_data_top_up": 5368709120,
        "latest_data_top_up_date": "2026-07-28T09:00:00Z"
      }
    }
  ],
  "date": "2026-08-01T10:15:01Z"
}
events
One delivery can contain multiple balance events.
user
Affected user identity and its reseller ownership context.
package
Product identifier and accounting-time alias.
order.data
Remaining shared-ledger balance in bytes; overdraft can be negative.
order.data_remaining
Finite child-order balance, or null for root and unlimited orders.
order.data_available_percentage
Normalized available traffic from 0 to 100, rounded to two decimal places.
order.latest_data_top_up
Most recent allocation in bytes, with its UTC timestamp inlatest_data_top_up_date.
date
Appears on both the event and the delivery batch.
01 Read raw body02 HMAC-SHA25603 Base6404 Compare X-Signature
FieldTypeMeaning
eventsarrayOne or more coalesced accounting events
datetimestampTime the delivery batch was created

Both timestamps are serialized by Go’s JSON time encoder and should be parsed as RFC 3339-compatible values rather than compared as arbitrary display strings.

Every element of events has three nested context objects. The event name and occurrence time remain at the event root.

JSON pathTypeMeaning
eventstringDelivered event name
datetimestampTime the underlying accounting event occurred
user.idstringAffected user identifier
user.usernamestringAffected proxy username or user label
user.reseller_idstringOwning reseller identifier; an empty string means no reseller context
package.idstringRelated package identifier
package.aliasstringPackage alias copied into the accounting event
order.idstringRelated order identifier
order.dataintegerRemaining shared-ledger balance in bytes; negative means overdraft
order.data_remaininginteger or nullRemaining finite child-order balance; null for root or unlimited orders
order.ledger_idstringLedger identifier whose shared balance is reported by order.data
order.is_unlimited_databooleanWhether the package has an unlimited traffic allowance
order.data_allocatedinteger or nullCurrent usable allocation in bytes; null for unlimited packages
order.data_availableinteger or nullCurrent available traffic in bytes; null for unlimited packages
order.data_available_percentagenumberAvailable percentage from 0 to 100, rounded to two decimal places
order.latest_data_top_upinteger or nullMost recent allocation in bytes; null when exact history is unavailable
order.latest_data_top_up_datetimestamp or nullUTC time of the most recent allocation; null with unknown history

The normalized balance is calculated after the accounting update has been persisted:

  • root orders aggregate currently usable, non-expired ledgers;
  • virtual child orders report the child’s personal allocation and remaining allowance;
  • sub-users with their own purchased orders report that order’s ledger balance;
  • finite percentages are clamped to 0100 and rounded to two decimal places;
  • unlimited packages report is_unlimited_data: true, data_available_percentage: 100, and null byte totals.

Use data_available_percentage for warning thresholds such as 20%, 10%, or 5%. Treat every event as a current snapshot and suppress duplicate notifications because repeated usage changes may contain the same percentage. The top-up fields describe only an exact latest allocation; legacy orders return null rather than an estimated value.

user.data.change

A user ledger still has balance after usage. Repeated changes for the same user/order/package key may be coalesced into the latest balance before delivery.

user.data.run_out

The user ledger reached zero or below. Repeated run-out signals are subject to the sender cooldown.

order.data.run_out

A finite child order’s order.data_remaining reached zero or below while the accounting event was processed.

The sender can map an internal order.data.change event to user.data.change or user.data.run_out before delivery. Consumers should therefore implement only the public names above unless a later contract version adds another event.

{
"event": "user.data.change",
"date": "2026-08-01T10:15:00Z",
"user": {
"id": "user_42",
"username": "acme-customer",
"reseller_id": "reseller_01"
},
"package": {
"id": "package_9",
"alias": "residential"
},
"order": {
"id": "order_71",
"data": 5368709120,
"data_remaining": 2147483648,
"ledger_id": "ledger_12",
"is_unlimited_data": false,
"data_allocated": 10737418240,
"data_available": 2147483648,
"data_available_percentage": 20,
"latest_data_top_up": 5368709120,
"latest_data_top_up_date": "2026-07-28T09:00:00Z"
}
}
PropertyConsumer expectation
TransportHTTP POST with JSON
SignatureBase64 HMAC-SHA256 in X-Signature
SuccessReturn a direct 2xx; sender currently accepts any status below 400
RetriesPossible for network, timeout, and error-status failures
DuplicatesPossible; make balance application safe to process more than once
OrderingNot guaranteed
Event IDNot present
CompletenessReconcile with analytics rather than assuming an event-only ledger

For receiver code and webhook creation, return to webhook integration.