Guides / n8n × HubSpot / Reliability

n8n + HubSpot Pagination: Fixing Missing or Duplicate Records From the “after” Cursor

Why a workflow that paginates through HubSpot contacts, companies, or deals can silently skip records or process the same one twice, and how to build a cursor loop in n8n that catches both failure modes.

Illustrated n8n workflow looping through HubSpot API pages with a cursor token, catching a missed record.
No explicit error — the workflow completes successfully, but the total record count is lower than expected, or the same record appears more than once downstream.
Short answer: HubSpot's CRM v3 endpoints paginate with an opaque `after` cursor, not a page number, and the fix has to loop until the response's `paging.next` field is absent rather than stopping after a fixed number of pages. Silent record loss usually comes from stopping too early, filtering records out of order, or running two overlapping pagination loops against the same growing dataset; duplicates usually come from restarting a loop from the beginning after a partial failure instead of resuming from the last confirmed cursor.

The cursor is opaque and forward-only — treat it that way

HubSpot's CRM v3 list endpoints (contacts, companies, deals, and custom objects) return results with a `paging` object. When more results exist, `paging.next.after` contains the cursor value to send as the `after` query parameter on the next request; when it's absent, you've reached the last page. This is fundamentally different from offset-based pagination — you cannot jump to "page 5" or request the previous page by arithmetic, and the cursor value itself is opaque and should not be parsed or predicted.

The most common n8n mistake is building a Loop Over Items or manual HTTP Request loop with a fixed number of iterations, or a hardcoded page-size × page-count total, based on the record count observed on a previous run. HubSpot portals are live systems — records are created, updated, merged, and deleted continuously, so a count taken minutes earlier is already stale by the time a long-running workflow finishes paginating.

The correct loop condition is: keep requesting the next page using the returned `after` value until the response has no `paging.next` field at all. Any stopping condition based on a fixed iteration count, a fixed total, or reaching a specific `after` value you calculated in advance is a latent source of missed records.

Why records go missing even when the loop looks correct

A real HubSpot Community bug report describes result sets returned out of order when using the `after` cursor, which the reporter says produced both duplicated and missing rows once combined across pages. Independent of that specific bug, the same symptom appears whenever the underlying dataset changes between page requests: if a workflow queries contacts sorted by creation date and new contacts are created while the loop is running, records can shift between pages in a way that causes some to be skipped entirely and others to appear on two consecutive pages.

A second, easier-to-miss cause is client-side filtering applied per page instead of after all pages are collected. If a workflow filters out records that don't match a condition on each page individually, and the condition depends on a field that can be updated mid-run (for example, a lifecycle stage), a record can fail the filter on page 3 in a way it would have passed if evaluated a few minutes earlier or later — this looks like a missing record but is actually a filtering-timing issue, not a pagination bug.

A third cause specific to n8n is running two workflow executions of the same pagination logic concurrently — for example, a scheduled trigger firing again before the previous run finished. Each execution pages through the same live dataset independently, and if either one is interrupted partway, reconciling which records each execution actually captured becomes difficult without a shared, persisted checkpoint.

  • Filter records after collecting all pages, not incrementally per page, when the filter condition can change mid-run.
  • Sort by a field that is set once and never changes (like creation timestamp) rather than a field that updates frequently, if the API supports choosing the sort field.
  • Use n8n's workflow-level concurrency setting or an external lock to prevent two executions of the same pagination job from overlapping.
  • Log the `after` cursor value at each step so a partial run can be diagnosed after the fact.

Why the same record can be processed twice downstream

Duplicate processing is almost always a resume problem, not a HubSpot API problem. If a pagination loop fails on page 6 of 10 and the retry logic restarts the entire workflow from page 1 with no memory of already having processed pages 1 through 5, every record on those pages is fetched and pushed downstream again.

This matters more in n8n than in a simple script because a failed workflow execution in n8n is often re-triggered by re-running the whole workflow manually or by a scheduled trigger firing again on its normal interval, both of which restart from the beginning unless the workflow explicitly persists and reads a checkpoint.

The fix is to persist the last successfully processed `after` cursor somewhere outside the workflow's in-memory execution state — a small database row, a key-value store, or even a dedicated HubSpot-adjacent record — and have the workflow read that checkpoint at the start of every run, resuming from there instead of from the beginning.

If a partial pagination run also triggered downstream actions (creating records elsewhere, sending notifications), make those actions idempotent using the HubSpot record ID as a stable key — that protects you even if a checkpoint is momentarily wrong.

A checkpointed pagination pattern in n8n

Store the checkpoint (the last `after` value and a timestamp) in whatever persistent storage the rest of the workflow already uses — a database node, a simple file via the file system, or a lightweight key-value service. Read it at the very start of the workflow, before the first HubSpot request.

Structure the loop so the cursor is only persisted after the current page's records have been fully processed by every downstream step, not immediately after the page is fetched. This ensures a failure between "fetched page" and "finished processing page" causes that page to be retried rather than skipped.

When the loop finally reaches a response with no `paging.next`, clear or reset the checkpoint (or mark the run as complete) so the next scheduled execution starts a fresh pagination pass from the beginning rather than trying to resume a completed one.

On workflow start:
  Read stored checkpoint { after: string|null, complete: boolean }
  If complete === true → start a fresh pass (checkpoint = { after: null, complete: false })

Loop:
  Request page using current `after` (or first page if null)
  Process every record on this page fully (all downstream actions)
  Persist checkpoint { after: response.paging.next.after, complete: false }
  If response.paging.next is absent:
    Persist checkpoint { after: null, complete: true }
    Exit loop

Add a periodic reconciliation check instead of trusting the loop blindly

Even a correctly built cursor loop is checking a live, changing dataset, so treat an occasional discrepancy as expected rather than assuming any mismatch means the workflow is broken. Periodically compare a simple HubSpot count (using a search or list endpoint's total, when available for the object type) against the count your workflow actually processed over the same rough window, and investigate only when the gap is large or persistent rather than a handful of records that can be explained by records changing mid-run.

For anything downstream that depends on completeness — a nightly export, a compliance report, a full re-sync — build in a lightweight secondary check: spot-verify a sample of record IDs that should have been captured, or compare a total count before and after, rather than trusting an unattended loop that reports success purely because it didn't throw an error.

Verification checklist

  • Loop condition is based on the presence of `paging.next`, not a fixed page count or precomputed total.
  • Filtering happens after all pages are collected when the filter condition can change while the loop runs.
  • Only one execution of this pagination workflow can run at a time (concurrency guard in place).
  • The last successful `after` cursor is persisted outside the workflow's execution memory and read on every run.
  • Downstream actions triggered per record are idempotent, keyed by the HubSpot record ID.

Sources and further reading

HubSpot's paging behavior for CRM v3 endpoints comes from HubSpot's CRM API documentation. The specific out-of-order pagination bug report comes from HubSpot's own community forum. n8n pagination and cursor-handling patterns come from n8n Community discussions describing the same class of missing-record problem across different APIs, not HubSpot-specific threads, since the underlying pattern is the same regardless of API.

Frequently asked questions

Can I request pages of HubSpot records out of order to speed things up?

No. The `after` cursor is forward-only and depends on the previous response — you cannot request page 5 before page 4 has told you the cursor value to use, and attempting to parallelize a single cursor loop against a changing dataset increases the risk of the same missing/duplicate symptoms described here.

Does increasing the page size (limit parameter) reduce the chance of missing records?

It reduces the number of pages needed, which reduces the number of opportunities for the underlying dataset to change mid-run, but it does not eliminate the risk. The checkpointing and post-collection filtering approach in this guide addresses the root cause regardless of page size.

What if the workflow only needs recently created or updated records, not a full historical pull?

Prefer HubSpot's recently-created or recently-modified endpoints with a stored last-run timestamp over paginating the entire object list every run — this shrinks the page count dramatically and reduces both rate-limit pressure and the pagination-consistency risk described here.