Skip to main content

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 Policies

    PayData does not keep data forever and starts deleting transactions and reports after the agreed interval (e.g. 12 months). The count will decrease and first will move closer to the present day.

  • GetReports and GetDataImports are the structured records of what PayData has fetched. Both support a createdAt.start filter, 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:

  1. Keep a cursor: the createdAt of 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 with isTemporary: true (still being retried by PayData, not settled yet).
  2. Each cycle, call GetReports?createdAt.start=<cursor>&order=createdAt (paging with pageNumber if more than one page comes back) instead of re-listing every report the account has ever had.
  3. For each report returned:
    • If its state is final and carries no isTemporary: true error/warning, it's settled: fetch its transactions (see below) and advance your cursor past it.
    • If it carries an isTemporary: true error/warning, it's still being retried by PayData — add it to your pending set instead of advancing the cursor past it.
  4. Each cycle, also re-check your pending set — individually, via GetReport by id, not by re-listing — since GetReports has no modifiedAt filter to find retried reports efficiently. Drop a report from the pending set once it reaches a final state.
  5. Only advance your createdAt.start cursor up to the oldest report still pending, so a retried report never falls out of a future createdAt.start window 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.

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:

  1. Track a cursor: the createdAt of the newest data import you've processed.
  2. Each cycle, call GetDataImports?createdAt.start=<cursor>&order=createdAt.
  3. For each import with status: "Done" and countReceived > 0, fetch exactly its transactions with GetTransactions?dataImportId=<id> — no need to separately track report IDs or date ranges.
  4. Advance your cursor to the newest createdAt you'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:

  • AccountCreated fires as soon as the account is created.
  • AccountUpdated and TransactionsImported fire 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.status transitioning to Updating/PreparingUpdate as your signal that a fetch is in progress — depending on the connector, status may stay Idle throughout (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 shapeRecommended syncNotes
Report-based (hasPspReports: true)DataImports cursorFalls 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 cursorA plain GetTransactions createdAt.start cursor works too, but misses update-only re-imports.
EitherSubscribe to AccountUpdated / TransactionsImportedUse polling (above) as the reconciliation fallback, not the primary mechanism.