Transactions Sync migration guide
Learn how to migrate from the /transactions/get endpoint to the /transactions/sync endpoint
Overview
/transactions/sync
is a newer endpoint that replaces /transactions/get
and provides a simpler and easier model for managing transactions updates. While /transactions/get
provides all transactions within a date range, /transactions/sync
instead uses a cursor to provide all new, modified, and removed transactions that occurred since your previous request. With this cursor-based pagination, you do not need to worry about making redundant API calls to avoid missing transactions. Updates returned by /transactions/sync
can be patched into your database, allowing you to avoid a complex transaction reconciliation process or having to keep track of which updates have already been applied.
This guide outlines how to update your existing /transactions/get
integration to use the /transactions/sync
endpoint and simplify your Plaid integration.
Looking for an example in code? Check out Pattern on GitHub for a complete, best-practice implementation of the Transactions Sync API within a sample app.
Update your client library
If you are using client libraries, you may need to update your current library to use /transactions/sync
. The following are the minimum Plaid client library versions required to support /transactions/sync
for each language:
- Python: 9.4.0
- Node: 10.4.0
- Ruby: 15.5.0
- Java: 11.3.0
- Go: 3.4.0
Detailed upgrade notes are language-specific may be found in the README and Changelog of the specific library. See the library's repo on the Plaid GitHub for more information.
Update callsites and pagination logic
Replace all instances of /transactions/get
with /transactions/sync
. /transactions/sync
has a slightly different call signature from /transactions/get
and does not have the count
parameter inside the options
object and uses a cursor
instead of a start_date
and end_date
. Pagination logic is also different and relies on the has_more
flag instead of the transactions_count
value. Note that when requesting paginated updates with /transactions/sync
, unlike when using /transactions/get
, it is important to retrieve all available updates before persisting the transactions updates to your database.
Unlike /transactions/get
, /transactions/sync
does not allow specifying a date range within which to retrieve transactions. If your implementation requires getting transactions within a certain date range, implement transaction filtering after calling /transactions/sync
.
For copy-and-pastable examples of how to call /transactions/sync
, including complete pagination logic, see the API reference code samples for /transactions/sync
.
If a call to /transactions/sync
fails when retrieving a paginated update as a result of the TRANSACTIONS_SYNC_MUTATION_DURING_PAGINATION
error, the entire pagination request loop must be restarted beginning with the cursor for the first page of the update, rather than retrying only the single request that failed.
Update callsites for Item data
Unlike /transactions/get
, /transactions/sync
does not return an Item object. If your app relies on getting Item data, such as Item health status, use /item/get
.
Update webhook handlers
When using /transactions/sync
, you should not listen for the webhooks HISTORICAL_UPDATE
, DEFAULT_UPDATE
, INITIAL_UPDATE
, or TRANSACTIONS_REMOVED
. While these webhooks will still be sent in order to maintain backwards compatibility, they are not required for the business logic used by /transactions/sync
.
Instead, update your webhook handlers to listen for the SYNC_UPDATES_AVAILABLE
webhook and to call /transactions/sync
when this webhook is received.
Update initial call trigger
Unlike the /transactions/get
webhooks, the SYNC_UPDATES_AVAILABLE
webhook will not be fired for an Item unless /transactions/sync
has been called at least once for that Item. For this reason, you must call /transactions/sync
at least once before any sync webhook is received. After that point, rely on the SYNC_UPDATES_AVAILABLE
webhook.
Unlike /transactions/get
, /transactions/sync
will not return the PRODUCT_NOT_READY
error if transactions data is not yet ready when /transactions/sync
is first called. Instead, you will receive a response with no transactions and a null cursor. Even if no transactions data is available, this call will still initialize the SYNC_UPDATES_AVAILABLE
webhook, and it will fire once data becomes available.
The first call to /transactions/sync
once historical updates are available will often have substantially higher latency (up to 8x) than the equivalent call in a /transactions/get
-based implementation. Depending on your application's logic, you may need to adjust user-facing messaging or hard-coded timeout settings.
Update transaction reconciliation logic
The response to /transactions/sync
includes the patches you will need to apply in the added
, removed
, and modified
arrays within its response. You should apply these to your transactions records. Any additional logic required to fetch or reconcile transactions data can be removed.
Migrating existing Items
You likely already have transactions stored for existing Items. If you onboard an existing Item onto /transactions/sync
with "cursor": ""
in the request body, the endpoint will return all historical transactions data associated with that Item up until the time of the API call (as "adds"). You may reconcile these with your stored copy of transactions to ensure that it reflects the the Item's true state.
If you have a large number of Items to update, this reconciliation process may be slow and generate excessive system load. One other option for onboarding existing Items onto /transactions/sync
is using "cursor": "now"
in the request body. The endpoint will return a response containing no transaction updates, but only a cursor that will allow you to retrieve all transactions updates associated with that Item going forward, after the time of the API call. Accordingly, you should ensure that your local copy of transactions for an Item is up-to-date at the time you call /transactions/sync
with "cursor": "now"
for it, or else any transaction updates that occurred between the time that you last pulled fresh data and the time of your /transactions/sync
call may be missing.
"cursor": "now"
will work exactly like a cursor that was found by starting with "cursor": ""
and paginating through all updates, with the only difference being that a transaction created before, but modified after, those requests would be returned as "added" if using "cursor": "now"
, and "modified" if using "cursor": ""
.
If you ever want to completely rebuild your local copy of transactions for an Item previously onboarded with "cursor": "now"
, you may still do so with "cursor": ""
.
Note that we strongly recommend that this cursor only be used with Items for which you've already used with /transactions/get
, and not any new Items, which should always be onboarded with "cursor": ""
.
Test your integration
You can perform basic testing of your integration's business logic in Sandbox, using the /sandbox/item/fire_webhook
endpoint to simulate SYNC_UPDATES_AVAILABLE
. If this testing succeeds, you should then test your integration with internal test accounts before releasing it to your full userbase.
Example code
For a full working example of a Plaid-powered app using /transactions/sync
, see Plaid Pattern.