Skip to main content
To keep your own database in sync with Trackly, don’t re-scan everything on a schedule — drive an incremental change feed off a watermark. Both the messages and billing records endpoints support this pattern: ask for everything that changed since the last time you synced, page through it, and remember the newest change you saw.

The watermark

Each row has an updated_at timestamp that advances whenever the row changes (a delivery receipt revises a message’s status; a charge is refunded). Pass the timestamp of the newest change you’ve already processed:
  • Messages: updated_since (ISO-8601, timezone-aware — include Z or an offset)
  • Billing records: updatedSince (same format)
When you pass a watermark, the endpoint switches from newest-first browsing to an ascending change feed: rows are filtered to updated_at > watermark, sorted oldest-change-first, and keyset-paginated on (updated_at, id). Because pagination is keyset-based, a row that changes while you’re paging is never skipped or duplicated.
Rows that predate the updated_at field are not in the change feed. Do one initial full sync (list without a watermark) to capture the existing state, then switch to incremental.

Cursor pagination

Both endpoints return a pagination object. Follow next_cursor until has_more is false:
The cursor is opaque — pass it back verbatim as the cursor query param. Do not construct or parse it.

The sync loop

  1. Start from your stored watermark (or omit it for the first run).
  2. Request a page with updated_since=<watermark> (and cursor if continuing).
  3. Upsert each row into your store by its id. For messages, let the latest status win — a later receipt can revise delivered to failed.
  4. Follow next_cursor until has_more is false.
  5. Persist the largest updated_at you saw as your new watermark.
Python
Run reconciliation as a safety net in addition to webhooks, not instead of them. Webhooks give you real-time updates; the watermark feed guarantees you catch anything a webhook missed (a downed endpoint, a dead delivery).

Idempotent upserts

Because a status can be revised and the feed is at-least-once safe, your upsert must be idempotent: key on the row id, and for messages take the row with the newer updated_at. Never append — always upsert.

List messages

The message change feed and its filters.

Billing records

The billing change feed for charges and refunds.