> ## Documentation Index
> Fetch the complete documentation index at: https://developers.momogood.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Polling pattern

> Recommended incremental-sync flow using the `since` cursor. Webhooks coming soon.

<Info>
  **Coming Soon — webhook notifications.** Push notifications for resource changes. Until then, use the polling pattern below. Contact [support@givergy.com](mailto:support@givergy.com) to express interest.
</Info>

<Info>
  **Coming Soon — bulk and async export jobs.** For one-time large backfills, ask us about async export — useful when polling individual events at scale isn't the right shape.
</Info>

## The recommended sync flow

The simplest reliable approach is `since`-based polling. The examples below assume the Custom Data Export family; substitute the path prefix for `/salesforce/v1` or `/blackbaud/v1` as appropriate.

1. **On first run**, call `GET /<your-namespace>/v1/events?since=-1` to backfill all events visible to your service user.
2. **For each event id returned** (paginate with `offset` / `limit` if you have more than 1000 events):
   * `GET /<your-namespace>/v1/events/{eventId}/items?since=<lastSyncTs>`
   * `GET /<your-namespace>/v1/events/{eventId}/purchases?since=<lastSyncTs>`
   * `GET /<your-namespace>/v1/events/{eventId}/guests?since=<lastSyncTs>`
3. **Persist `max(updated)` across all records returned** as the next `lastSyncTs`. Keep fine-grained cursors per resource if you need different sync cadences.
4. **On subsequent runs**, repeat with the saved cursor.

<Note>
  The Salesforce and Blackbaud families don't include a list-events endpoint today. Integrations using those families typically receive event IDs out-of-band (e.g. from the destination CRM) and call `/items`, `/purchases`, and `/guests` directly. A list-events endpoint for these families is **Coming Soon** — contact [support@givergy.com](mailto:support@givergy.com) if you need it.
</Note>

## Bundle pagination caveat

For `/items` and `/purchases`, the `offset` and `limit` parameters apply **per category** inside the bundle, not across the whole response.

If you have more than 1000 records in any single category for an event, you must paginate by re-issuing the request with rising `offset` until every category returns an empty array. The bundle wrapper structure (the six keys) is constant; only the array contents shrink.

For example, requesting `/items?offset=0&limit=1000` then `/items?offset=1000&limit=1000` returns rows 0–999 then rows 1000–1999 **of every category independently** — not "the next 1000 items across all categories."

## Recommended polling cadence

| Event state                                      | Suggested interval |
| ------------------------------------------------ | ------------------ |
| Active event (people are bidding / checking out) | Every few minutes  |
| Post-event reconciliation                        | Hourly             |
| Idle / historical sync                           | Less frequently    |

The shared rate-limit zone tolerates this comfortably. Avoid sub-second tight loops — see [Rate limiting](/events-auctions/rate-limiting) for the request-per-second target.

## Cursor storage

Store `lastSyncTs` per resource per event, keyed however your sync job tracks state:

```
sync_state[event_id][resource] = max(updated)
```

If your job crashes partway through an event's resources, that resource's cursor stays at its previous value and the next run picks up where it left off. The `since` filter is inclusive of the cursor (`updated >= since`), so re-fetching a few overlapping records is harmless — your upsert logic should be idempotent anyway.

## When the `since` cursor isn't enough

`since` only tracks changes, not deletions. The list endpoints implicitly filter to records with `status: "active"`, so a record being archived/obfuscated will simply stop appearing — your downstream copy won't see the change unless you periodically reconcile by listing all current records.

For most reporting use cases, this is fine. If you need true deletion semantics, plan a periodic full reconciliation (e.g. weekly): list everything currently active, diff against your local copy, and mark anything missing as deleted on your side.
