Staying Up to Date
Once an account exists, PayData keeps fetching new data from the payment provider on its own schedule. Re-reading everything an account has (all reports, or all transactions) on every check works, but it does not scale: accounts can accumulate thousands of transactions, and most checks find nothing new. This page describes how to fetch only what changed, for both report-based and non-report-based account types, and how to avoid polling altogether by using notifications.
The three signals available
-
GetAccount's
elementStatistics(count/first/last) is the cheapest possible check: it tells you whether the account's transaction count changed at all, but nothing about what changed or where. Useful as a quick "is it worth doing more work" gate, not as a sync mechanism by itself.Data Retention PoliciesPayData does not keep data forever and starts deleting transactions and reports after the agreed interval (e.g. 12 months). The
countwill decrease andfirstwill move closer to the present day. -
GetReports and GetDataImports are the structured records of what PayData has fetched. Both support a
createdAt.startfilter, so you can ask for only what's new since your last check instead of re-listing everything. -
Webhook notifications push the same information to you the moment it happens, removing the need to guess a polling interval at all.
Report-based accounts
Accounts whose account type has hasPspReports: true (see Reports) group transactions into
reports, and a report only becomes trustworthy once it reaches one of its
final states. The recommended loop:
- Keep a cursor: the
createdAtof the newest report you've fully processed, plus a small set of "pending" report IDs — reports whose last known state carried an error or warning withisTemporary: true(still being retried by PayData, not settled yet). - Each cycle, call
GetReports?createdAt.start=<cursor>&order=createdAt(paging withpageNumberif more than one page comes back) instead of re-listing every report the account has ever had. - For each report returned:
- If its state is final and carries no
isTemporary: trueerror/warning, it's settled: fetch its transactions (see below) and advance your cursor past it. - If it carries an
isTemporary: trueerror/warning, it's still being retried by PayData — add it to your pending set instead of advancing the cursor past it.
- If its state is final and carries no
- Each cycle, also re-check your pending set — individually, via
GetReport by id, not by re-listing — since
GetReportshas nomodifiedAtfilter to find retried reports efficiently. Drop a report from the pending set once it reaches a final state. - Only advance your
createdAt.startcursor up to the oldest report still pending, so a retried report never falls out of a futurecreatedAt.startwindow before it settles.
Non-report-based accounts
Accounts with hasPspReports: false have no reports layer — sync directly against transactions:
GetTransactions?createdAt.start=<cursor>&order=createdAt, paginated via pageReference (the cursor style
GetTransactions documents; it fully encapsulates your original filters
and page size, so you only need to store the token itself). Track your cursor as the latest transaction
createdAt you've seen.
This alone has a gap: a transaction's own createdAt never changes, even when PayData corrects its data
later (see Changing transactions). A pure createdAt cursor will not
notice that an already-seen transaction was updated — only that no new transaction appeared. The
DataImports-based approach below closes that gap, and works the same way for both account shapes.
Syncing via DataImports (recommended for both account shapes)
GetDataImports lists every fetch PayData has performed against the
provider for an account — regardless of whether the account has reports — with countReceived,
countAdded, countUpdated, a status (Done / Importing / Failed), and, when applicable, the
reportId it came from. It supports the same createdAt.start/createdAt.end filtering as reports.
Because GetTransactions accepts a dataImportId filter, you can
sync through this one endpoint alone:
- Track a cursor: the
createdAtof the newest data import you've processed. - Each cycle, call
GetDataImports?createdAt.start=<cursor>&order=createdAt. - For each import with
status: "Done"andcountReceived > 0, fetch exactly its transactions withGetTransactions?dataImportId=<id>— no need to separately track report IDs or date ranges. - Advance your cursor to the newest
createdAtyou've now processed.
The advantage over a plain transactions cursor: PayData creates a new data import — with its own,
current createdAt — every time it re-fetches data for an account, including a support-case correction
that only updates existing transactions (countUpdated > 0, countAdded possibly 0). A
createdAt.start cursor over data imports picks this up immediately and tells you exactly which (small)
set of transactions to re-fetch; a createdAt.start cursor over transactions directly would never surface
it, since none of the affected transactions are new.
Use elementStatistics.count from GetAccount first as a cheap "has anything at all happened" gate before
calling GetDataImports, if you're polling many accounts on a schedule — but see the timing caveats below
before relying on it to mean "done".
Notifications instead of polling
Subscribing to AccountCreated, AccountUpdated, AccountDeleted and TransactionsImported
webhooks lets PayData push you this
information the moment it happens, rather than you guessing a polling interval. A few things worth knowing:
AccountCreatedfires as soon as the account is created.AccountUpdatedandTransactionsImportedfire once PayData has actually fetched and imported the account's data. This is usually quick, but treat the delay as variable rather than assuming a fixed turnaround — it depends on the provider and on PayData's own load, and can occasionally take noticeably longer than usual.- Don't rely on
GetAccount.statustransitioning toUpdating/PreparingUpdateas your signal that a fetch is in progress — depending on the connector,statusmay stayIdlethroughout (see Accounts) rather than surfacing a transitional value.
Because timing isn't guaranteed either way, notifications remove the guesswork a polling loop otherwise carries (how often to poll, how long to wait). Keep a periodic reconciliation poll using the DataImports loop above as a safety net regardless — PayData's retry budget for a failed delivery is finite (see Retry Behavior), and a notification that exhausts it is not resent.
Summary
| Account shape | Recommended sync | Notes |
|---|---|---|
Report-based (hasPspReports: true) | DataImports cursor | Falls back cleanly to the report-by-report loop if you need report-level state (e.g. to show import errors to a user). |
Non-report-based (hasPspReports: false) | DataImports cursor | A plain GetTransactions createdAt.start cursor works too, but misses update-only re-imports. |
| Either | Subscribe to AccountUpdated / TransactionsImported | Use polling (above) as the reconciliation fallback, not the primary mechanism. |