Skip to main content

Transaction feed

Use the transaction feed to reconcile a Hubpay wallet against your own ledger. It returns every entry on the wallet in the order it happened, and each page carries totals you can check against your running totals, so you know whether you have missed anything.

What you need​

  • If you integrate through a connected app, it needs the TRANSACTION_FEED_READ scope, requested through the connection flow. If you integrate your own Hubpay account with your own credentials, use Banking and money movement and no app connection is needed.
  • The wallet to read. List wallets and balances returns the wallets the connection can see; a wallet it cannot see returns 404.

Routes​

RouteUse
GET /v1/walletsList wallets.
GET /v1/wallets/{walletId}Read one wallet's balances and historyStart.
GET /v1/wallets/{walletId}/entriesPage through the wallet's entries.

The entries route takes three query parameters:

  • status: POSTED (the default) or ALL. Any other value returns 400.
  • cursor: the nextCursor from the previous page. Omit it to start from the beginning of the wallet's history. A cursor that cannot be parsed returns 400.
  • limit: the maximum number of entries to return, from 1 to 500.

All timestamps are in UTC and use ISO 8601 format with a Z suffix, for example 2026-10-06T09:30:00Z. Amounts are exact decimal strings.

The wallet​

FieldMeaning
accountIdThe wallet's identifier.
currencyThe wallet's currency.
postedBalanceThe balance of posted entries, including funds that are reserved for payments in progress.
reservedBalanceFunds set aside for payments in progress.
pendingHoldsCard and other holds that have not posted yet.
availableBalancepostedBalance minus reservedBalance minus pendingHolds.
visibleThroughHow far the feed is complete. See Entries appear after a short delay.
historyStartThe time of the wallet's first visible entry.

The balances on the wallet are current. They are not the balance at visibleThrough, so they can include postings that the feed does not show yet. To compare balances at a known point, use balanceAfter on a page.

Entries​

Each entry has these fields and no others.

FieldMeaning
entryIdA stable identifier of the form <prefix>-<journal id>. See Entry identifiers.
statusPOSTED, RESERVED, RELEASED or CONSUMED. See Posted entries and reservation events.
directionCREDIT or DEBIT, from the wallet's point of view.
amountThe amount, as an exact decimal string.
currencyThe currency of the entry.
occurredAtWhen the event happened.
bookingDateThe ledger entry date in the Asia/Dubai time zone. Present on posted entries only.
descriptionThe wording the customer statement uses for this entry, or null when the entry has no statement wording.
categoryWhat kind of entry this is. See Categories.
hubpayReferenceThe Hubpay transaction reference. Entries of one transaction share it.
relatedEntryIdsThe other entries of the same transaction. See Related entries.
counterpartyname, iban and paymentReference of the other party, or null.

Entry identifiers​

An entryId never changes once the entry has been returned. Use it as your key when you store entries. Do not read anything into the journal id inside it.

PrefixEntry
PFUFunding
PCOCollection
PPYPayment
PFEFee
PFXFX
PRTReturn
PTRTransfer
PADAdjustment
POBOpening balance
POTOther
RPLReservation placed
RRLReservation released
RCNReservation consumed
RCFReservation consumed, fee

The P prefixes are posted entries. The R prefixes are reservation events, which you only receive with status=ALL.

Posted entries and reservation events​

With status=POSTED, you receive posted entries only, and every entry has status POSTED.

With status=ALL, you also receive reservation events. A reservation is funds set aside for a payment while it is in progress:

  • RESERVED: funds were set aside.
  • RELEASED: the funds were given back.
  • CONSUMED: the payment completed and used the funds. A completion gives one CONSUMED event for each reserved amount it uses, so a payment with a fee gives a principal event (RCN) and a fee event (RCF).

Reservation events have no bookingDate.

Categories​

category is one of FUNDING, COLLECTION, PAYMENT, FEE, FX, RETURN, TRANSFER, ADJUSTMENT, OPENING_BALANCE or OTHER. The category tells you whether an entry is principal or a fee. Reservation events are PAYMENT, or FEE for a fee.

Categorise entries by category, not by parsing description.

Descriptions​

description carries the customer statement wording where one exists. It is null for an entry that has none; there are no substitute labels.

relatedEntryIds lists the other entries of the same transaction:

  • on a posted entry, the other posted entries of the transaction, for example the fee of a payment, or the original of a return;
  • on RESERVED and RELEASED, the posted entries of the same transaction;
  • on CONSUMED, the posted entries of the completion that consumed it.

Counterparty​

counterparty holds name, iban and paymentReference, or is null when the other party is not known.

Pages​

Each response is a page with these fields.

FieldMeaning
entriesThe entries on this page, oldest first.
nextCursorThe cursor for the next request. When the page is empty it equals the cursor you sent.
balanceAfterThe wallet balance from the start of history to nextCursor.
entryCountThroughThe number of posted entries from the start of history to nextCursor. Reservation events are not counted.
openItemsTotalThe running net of reservation events from the start of history to nextCursor: placed, minus released, minus consumed.
visibleThroughHow far the feed is complete.

balanceAfter, entryCountThrough and openItemsTotal always cover the whole history up to nextCursor, whichever status you ask for. openItemsTotal counts reservation events whether or not the page lists them.

A page holds whole journals, so it can be larger than limit when one journal has more entries than the page has room for. Cursors are opaque and never expire. Do not try to read their contents.

Sync protocol​

There is currently no webhook for the feed, but this will be implemented soon. In the meantime, poll on your own schedule; every 1 to 5 minutes is typical.

  1. Start without a cursor and read pages until one comes back empty.
  2. Store each entry by entryId. Entries never change once returned, so a repeat of an entry you already hold is a no-op.
  3. Keep the nextCursor of the last page. Next time, send it as cursor and carry on from there.
  4. After each page, check your running totals against the page, as described below.

Check every page​

With status=POSTED, keep two running totals of posted entries: the sum of credits minus debits, and the count. After each page, both must match the page:

  • the sum equals balanceAfter;
  • the count equals entryCountThrough.

You need both checks. If you miss one entry and read another twice, or miss two entries whose amounts offset, the sum can still match while the count does not. The count catches these skips.

If either check fails, you have missed an entry. Re-read from the last cursor at which both checks matched, and upsert by entryId.

With status=ALL, the posted checks are unchanged. In addition, keep the running net of the reservation events you read: RESERVED amounts, minus RELEASED amounts, minus CONSUMED amounts. After each page it must equal openItemsTotal. A mismatch means you missed a reservation event. Re-read in the same way.

balanceAfter minus openItemsTotal is the available balance at that point in the history, before card holds.

Entries appear after a short delay​

An entry appears in the feed about two minutes after it happens. This delay keeps the order of the feed consistent, so that an entry never turns up behind a cursor you have already passed.

visibleThrough tells you how far the feed is complete. A page never contains anything after it. postedBalance on the wallet is current, so it can be ahead of what you have read; balanceAfter on a page is the balance at the cursor.

Differences from the customer statement​

The feed and the customer statement show the same wording where an entry has statement wording. They differ in these ways:

  • Scope. The statement covers the wallet's posted entries. The feed also covers the reservations that set funds aside for payments.
  • Reservations. The statement shows a payment when its funds are reserved. With status=POSTED the feed shows it when it completes; with status=ALL the feed also lists the reservation event at the time funds were set aside.
  • Entries without wording. The statement leaves out entries that have no statement wording. The feed includes them, with description set to null and a category.