How to retrieve all contacts from HubSpot API when exceeding 100 records limit

I need help fetching all my contacts from HubSpot and saving them into a single JSON file. The problem is that the API only returns 100 contacts at a time, so I have to use pagination to get everything.

I tried building my own solution without external libraries since I only need basic functionality. However, my code gets stuck in an infinite loop and never finishes loading. I think the issue is with how I’m handling the offset values, but I can’t figure out what’s wrong.

Here’s my current attempt:

<?php

function fetchContacts($currentOffset = 0) {
    $fields = "&property=email&property=name&property=company&property=lead_score&property=last_activity";
    $key = "your-api-key-here";
    $endpoint = "https://api.hubapi.com/contacts/v1/lists/all/contacts/recent?hapikey=" . $key . $fields . '&vidOffset=' . $currentOffset;
    
    $ch = curl_init();
    curl_setopt_array($ch, array(
        CURLOPT_URL => $endpoint,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_CUSTOMREQUEST => "GET",
        CURLOPT_HTTPHEADER => array(
            "cache-control: no-cache"
        ),
    ));
    
    $result = curl_exec($ch);
    $contactData = json_decode($result);
    curl_close($ch);
    
    return $contactData;
}

$completeData = array();
$currentOffset = 0;
$moreRecords = true;

while ($moreRecords === true) {
    $apiResponse = fetchContacts($currentOffset);
    $completeData[] = $apiResponse;
    
    $currentOffset = $apiResponse->{'vid-offset'};
    $moreRecords = $apiResponse->{'has-more'};
    
    // This line was added for testing but loop still continues
    $moreRecords = false;
}

Can someone spot what I’m doing wrong with the pagination logic?

The pagination looks right, but there’s an issue with your offset parameter in the URL. I hit the same problem pulling large datasets from HubSpot’s legacy endpoints. Your fetchContacts function uses vidOffset in the query string - make sure that’s what HubSpot expects for your specific endpoint. Some use offset, others use vidOffset. The contacts/recent endpoint is picky about this. Add some debugging to see what’s happening each iteration. Print the offset value and response status before the loop continues. When I debugged mine, HubSpot kept returning the same offset because of a malformed request. Also throw in a sleep delay between requests - HubSpot’s rate limiting causes weird responses that break pagination. A simple sleep(1) fixed most of my timeout issues during bulk operations.

Your code’s pushing the entire API response into the array instead of just the contacts. That’s why your JSON file is bloated and messy.

Plus you’re setting $moreRecords = false at the end of each loop - that’s killing your pagination.

Fix it like this:

while ($moreRecords === true) {
    $apiResponse = fetchContacts($currentOffset);
    
    // Just grab the contacts, not everything
    $completeData = array_merge($completeData, $apiResponse->contacts);
    
    $currentOffset = $apiResponse->{'vid-offset'};
    $moreRecords = $apiResponse->{'has-more'};
    
    // Delete this line completely
    // $moreRecords = false;
}

Honestly though, building this stuff manually is a headache. You’ll hit rate limits, error handling nightmares, and data transformation issues.

I’ve been using Latenode for this exact problem. It handles HubSpot pagination automatically and dumps contacts straight to JSON - no code needed. Runs on schedule too so your data stays current.

The visual builder makes it dead simple to add filters, transform data, or pipe it to other systems. Way more solid than custom PHP that breaks every time APIs change.

dude you’re forcing $moreRecords = false at the end which kills pagination completely lol. remove that test line and add some basic validation. also your offset might be null on the first run - initialize it properly or hubspot freaks out. been there

Your infinite loop happens because you’re not checking if the API response is valid before accessing its properties. When HubSpot returns empty results or fails, your code still tries to grab data that isn’t there. I hit this same issue last year during a CRM migration. You need proper error checking for malformed or empty responses. Here’s what works: php while ($moreRecords === true) { $apiResponse = fetchContacts($currentOffset); // Check if response is valid if (!$apiResponse || !isset($apiResponse->contacts)) { break; } $completeData = array_merge($completeData, $apiResponse->contacts); // Safely check pagination properties $currentOffset = isset($apiResponse->{'vid-offset'}) ? $apiResponse->{'vid-offset'} : 0; $moreRecords = isset($apiResponse->{'has-more'}) ? $apiResponse->{'has-more'} : false; // Remove your test line completely } Throw in a counter too - it’ll save you from runaway loops during testing. HubSpot’s API can return weird offset values when you’re near the end of your contact list.