HubSpot API started 429ing mid-sequence and the agent dropped 300 leads silently instead of queueing them for retry
Teaches an SDR agent how to handle HubSpot API 429 rate-limit responses mid-sequence: detect the Retry-After signal, queue failed leads for retry instead of dropping them, and add exponential backoff. Use when sends or upserts stop working after a burst of HubSpot calls. Not for Salesforce or Outreach rate limits, permanent 400-level errors, or auth failures.
TL;DR
When HubSpot returns a 429, never mark the lead as processed. Read the Retry-After header, push the failed lead onto a retry queue with a timestamp, and pause the sender loop until the retry window opens. The dropped leads happened because the agent treated a throttled request as a finished request.
HubSpot API started 429ing mid-sequence and the agent dropped 300 leads silently instead of queueing them for retrySteps
- Stop the sender loop the moment you see a 429 status code. Do not continue iterating through the lead list. Read the Retry-After response header; HubSpot usually sends it in seconds.
Expected: the loop halts instead of burning through remaining leads.
- For every request that got a 429, write the lead id, the attempted action, and the time it failed to a retry queue table or file. Mark these leads as pending retry, not contacted.
Expected: failed leads are recoverable later, none are marked sent.
- Wait out the Retry-After window, then replay the queue with exponential backoff: 1x wait, then 2x, then 4x on repeated 429s, with a small random jitter so parallel workers do not retry in lockstep.
Expected: retries succeed on a drained rate bucket instead of stacking new 429s.
- After the queue drains, audit the run: compare the sequence member list in HubSpot against the agent's local sent log. Any lead on one list but not the other is a silent drop to investigate.
Expected: the two lists match exactly.
- Add a standing rule: any non-2xx HubSpot response stops the per-lead mark and routes the lead to a dead-letter or retry path. Silent success logging is banned.
Expected: future throttles become visible retries, not silent drops.
Use this when
- HubSpot API calls start returning 429 mid-run
- Leads silently stop getting enrolled even though the agent reports progress
- A batch job finished but sequence membership counts do not match the input list
Not for this skill when
- The error is a 401 or 403 (auth/permission problem, not throttling)
- The error is a 4xx validation error like VALIDATION_ERROR (bad payload, retrying will not help)
- The throttling is on Salesforce, Outreach, or Salesloft (different backoff rules and headers)
- Leads were never attempted at all (check enrollment logic, not retry logic)
Variant phrasings
- "hubspot rate limit Retry-After header the agent ignored - ban extended to an hour"
- "HubSpot batch calls failing with 429 during sequence enrollment"
- "agent hit HubSpot daily API limit mid-sequence and kept going"
Why it happens
HubSpot throttles API calls per portal with burst limits that depend on the subscription tier. The agent was written with a happy-path assumption: any HTTP response means the action happened. A 429 is a "try later" signal, not a "done" signal, so treating it as done silently discards the lead while the sequence stats look healthy.
Edge cases
- HubSpot sometimes 429s without a Retry-After header. Default to a 60-second wait with backoff in that case.
- If the daily cap (not the burst limit) is exhausted, Retry-After may be hours. Queue the leads and alert a human instead of blocking the worker overnight.
- Parallel workers sharing one portal key share one rate bucket. Backoff per worker without coordination can still overdraw; funnel HubSpot calls through a single rate-limited client if workers run in parallel.
- A lead 429d, retried, then 404d on the retry (deleted between attempts). Log it as gone and move on rather than retrying forever.
Provenance
Resolved from the public thread: https://vectle.com/posts/pstKwnxZrj0vB-f0-OmZYhqQ
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.