GoHighLevel API rate limit handling strategy

    GHL rate-limits per token on a burst and a daily axis. Throttle so you rarely 429, back off with jitter for the ones you do, and cut request count before you ask for more.

    Last updated 2026-09-03~7 min read
    GoHighLeveln8nHTTP RequestRate Limiting

    What GoHighLevel's limits actually are

    The GHL v2 API rate-limits per authenticated token, on two axes at once: a burst limit of roughly 100 requests per 10 seconds, and a daily limit of about 200,000 requests per day. Hit either and you get a 429 Too Many Requests. The burst window is the one that bites in practice — a single n8n loop that fans out over a few hundred contacts will blow through 100-in-10s long before it ever approaches the daily ceiling.

    The limit is keyed on the token, not the endpoint. A Location-scoped Private Integration token shares one bucket across every call it makes — your contact sync, your calendar reads, and your pipeline updates all draw down the same 100-per-10s. This is the single most misunderstood thing about GHL throttling: parallelizing across endpoints does not buy you more headroom, because they all bill to the same token.

    Why naive retries make it worse

    The instinct on a 429 is to retry immediately, or to retry all failed items at once. Both amplify the problem. An immediate retry lands inside the same burst window that just rejected you, so it fails again — and now you've spent two requests to accomplish nothing. Retrying a whole failed batch in parallel is worse: fifty items that 429'd, all retried simultaneously, is a fifty-request spike aimed straight at a bucket that is already empty.

    A 429 is not a transient network blip. It is the server telling you the exact thing you must do — slow down — and a retry that doesn't slow down is ignoring the one instruction you were given. The fix is to treat the limit as a budget you spend deliberately, not an error you paper over with retries.

    The shape of the fix: throttle before you retry

    Two mechanisms, in this order. First, throttle so you stay under the burst limit by design and rarely 429 at all. Second, back off with jitter to absorb the 429s you do hit. Throttling is prevention; backoff is the safety net. Most implementations skip straight to backoff and wonder why they're still slow — they're absorbing 429s they never needed to cause.

    Throttle: token-bucket, or just pace the loop

    The clean model is a token bucket: you have 100 tokens, each request spends one, and the bucket refills at 10 per second. As long as you never spend faster than the refill, you never 429. In n8n you don't need a literal bucket implementation for most jobs — you approximate it by controlling batch size and inserting a wait:

    Loop Over Items (SplitInBatches, batch size 10)
      → HTTP Request to GHL (the 10 calls)
      → Wait (1.2 seconds)
      → loop back
    
    # 10 requests per ~1.2s = ~83 req/10s, comfortably under the
    # 100-per-10s burst limit with headroom for retries.

    Size the batch and the wait to your real limit, and leave headroom — running at exactly 100-per-10s leaves nothing for the retries when something does slip. The production reliability patterns cover why a fixed wait beats an unbounded parallel fan-out for anything client-facing.

    Back off with jitter for the 429s you still hit

    When a 429 does come back, wait and retry — but exponentially, and with jitter. Exponential backoff (1s, 2s, 4s, 8s) gives the bucket time to refill. Jitter — a random fraction added to each wait — is what stops a batch of simultaneously-throttled items from retrying in lockstep and re-colliding. Without jitter, ten items that 429 together will back off together and spike together on every retry.

    // Code node: compute the next wait from the retry count.
    // GHL often sends a Retry-After header on 429 — honour it if present,
    // it is the server telling you exactly how long to wait.
    const attempt = $json.attempt ?? 1;
    const retryAfter = Number($json.headers?.['retry-after']) || 0;
    
    const base = retryAfter > 0
      ? retryAfter * 1000
      : Math.min(1000 * 2 ** (attempt - 1), 30000); // cap at 30s
    
    const jitter = Math.random() * 0.3 * base;       // up to +30%
    return [{ json: { ...$json, wait_ms: Math.round(base + jitter), attempt: attempt + 1 } }];

    Feed wait_ms into a Wait node, then route back to the HTTP Request. Cap the attempts (three is usually enough) so a genuinely dead request doesn't loop forever — after the cap, send it to your error branch, not back to the top.

    Prefer batch endpoints and caching over more requests

    The cheapest 429 is the request you never make. Before tuning backoff, cut the request count: use GHL's batch and search endpoints instead of looping one-contact-at-a-time, and cache the values that don't change per-run (calendar IDs, custom field IDs, pipeline stage IDs) instead of re-fetching them inside the loop. A sync that looked up the same pipeline definition once per contact was making 500 identical calls; hoisting that read out of the loop cut the request volume by more than half and the 429s vanished.

    Observability: you can't tune what you don't log

    Log every 429 with its endpoint, token, and timestamp. Two reasons. First, a rising 429 rate is an early-warning signal that your volume is approaching the limit — you want to see that trend before it becomes failed syncs. Second, if you're multi-tenant with one token per sub-account, per-token 429 logging tells you which tenant's volume is the problem, which is invisible if you only count errors in aggregate. Route the 429 count to the same channel your error workflow already feeds.

    When to request a limit increase (and when not to)

    A limit increase is the right move when you've already throttled, cached, and moved to batch endpoints and are still legitimately bumping the ceiling — a large migration, or a genuinely high-volume production sync. It is the wrong first move when you're 429ing because of an un-throttled loop; GHL support will (reasonably) point you back at your own request pattern. Fix the pattern first, gather the per-token logs that show your real steady-state volume, and bring those numbers to the increase request. "Here is my sustained rate and here is why it's legitimate" gets a yes; "make the errors stop" does not.

    Browse 4,900+ ready-to-import workflow templates →

    Need this workflow customized or debugged? Book a 30-min scope call →

    Stuck configuring this workflow?

    I build and fix n8n workflows for agencies and SaaS teams. 30-minute scope call — bring the problem, leave with an estimate.

    Related pages

    Related workflows